mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ML] Data Visualizer: Remove duplicated geo examples, support 'version' type, add filters for boolean fields, and add sticky header to Discover (#136236)
* [ML] Add filter to boolean content * [ML] Change to version type if detected from estypes * [ML] Remove duplicated geo examples * [ML] Change duplicated geo util to duplicated generic util * [ML] Use name for data view instead of title * [ML] Add sticky header for field stats table in Discover * [ML] Move unknown to bottom, rename JOB_FIELD_TYPES
This commit is contained in:
parent
4f13ea8435
commit
2a6f8e8130
27 changed files with 433 additions and 192 deletions
|
@ -29,16 +29,17 @@ export const FILE_FORMATS = {
|
|||
// XML: 'xml',
|
||||
};
|
||||
|
||||
export const JOB_FIELD_TYPES = {
|
||||
export const SUPPORTED_FIELD_TYPES = {
|
||||
BOOLEAN: 'boolean',
|
||||
DATE: 'date',
|
||||
GEO_POINT: 'geo_point',
|
||||
GEO_SHAPE: 'geo_shape',
|
||||
HISTOGRAM: 'histogram',
|
||||
IP: 'ip',
|
||||
KEYWORD: 'keyword',
|
||||
NUMBER: 'number',
|
||||
TEXT: 'text',
|
||||
HISTOGRAM: 'histogram',
|
||||
VERSION: 'version',
|
||||
UNKNOWN: 'unknown',
|
||||
} as const;
|
||||
|
||||
|
|
|
@ -29,6 +29,16 @@ export interface DocumentCounts {
|
|||
interval?: number;
|
||||
}
|
||||
|
||||
export interface LatLongExample {
|
||||
lat: number;
|
||||
lon: number;
|
||||
}
|
||||
|
||||
export interface GeoPointExample {
|
||||
coordinates: number[];
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface FieldVisStats {
|
||||
error?: Error;
|
||||
cardinality?: number;
|
||||
|
@ -56,7 +66,7 @@ export interface FieldVisStats {
|
|||
topValues?: Array<{ key: number | string; doc_count: number }>;
|
||||
topValuesSampleSize?: number;
|
||||
topValuesSamplerShardSize?: number;
|
||||
examples?: Array<string | object>;
|
||||
examples?: Array<string | GeoPointExample | object>;
|
||||
timeRangeEarliest?: number;
|
||||
timeRangeLatest?: number;
|
||||
}
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { JOB_FIELD_TYPES } from '../constants';
|
||||
export type JobFieldType = typeof JOB_FIELD_TYPES[keyof typeof JOB_FIELD_TYPES];
|
||||
import { SUPPORTED_FIELD_TYPES } from '../constants';
|
||||
export type JobFieldType = typeof SUPPORTED_FIELD_TYPES[keyof typeof SUPPORTED_FIELD_TYPES];
|
||||
|
|
|
@ -10,12 +10,19 @@ import React, { FC } from 'react';
|
|||
import { EuiListGroup, EuiListGroupItem } from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { GeoPointExample } from '../../../../../common/types/field_request_config';
|
||||
import { ExpandedRowFieldHeader } from '../stats_table/components/expanded_row_field_header';
|
||||
import { ExpandedRowPanel } from '../stats_table/components/field_data_expanded_row/expanded_row_panel';
|
||||
|
||||
interface Props {
|
||||
examples: Array<string | object>;
|
||||
examples: Array<string | GeoPointExample | object>;
|
||||
}
|
||||
|
||||
const EMPTY_EXAMPLE = i18n.translate(
|
||||
'xpack.dataVisualizer.dataGrid.field.examplesList.emptyExampleMessage',
|
||||
{ defaultMessage: '(empty)' }
|
||||
);
|
||||
export const ExamplesList: FC<Props> = ({ examples }) => {
|
||||
if (examples === undefined || examples === null || !Array.isArray(examples)) {
|
||||
return null;
|
||||
|
@ -34,7 +41,13 @@ export const ExamplesList: FC<Props> = ({ examples }) => {
|
|||
<EuiListGroupItem
|
||||
size="xs"
|
||||
key={`example_${i}`}
|
||||
label={typeof example === 'string' ? example : JSON.stringify(example)}
|
||||
label={
|
||||
typeof example === 'string'
|
||||
? example === ''
|
||||
? EMPTY_EXAMPLE
|
||||
: example
|
||||
: JSON.stringify(example)
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
NumberContent,
|
||||
} from '../stats_table/components/field_data_expanded_row';
|
||||
import { GeoPointContent } from './geo_point_content/geo_point_content';
|
||||
import { JOB_FIELD_TYPES } from '../../../../../common/constants';
|
||||
import { SUPPORTED_FIELD_TYPES } from '../../../../../common/constants';
|
||||
import type { FileBasedFieldVisConfig } from '../../../../../common/types/field_vis_config';
|
||||
|
||||
export const FileBasedDataVisualizerExpandedRow = ({ item }: { item: FileBasedFieldVisConfig }) => {
|
||||
|
@ -25,25 +25,26 @@ export const FileBasedDataVisualizerExpandedRow = ({ item }: { item: FileBasedFi
|
|||
|
||||
function getCardContent() {
|
||||
switch (type) {
|
||||
case JOB_FIELD_TYPES.NUMBER:
|
||||
case SUPPORTED_FIELD_TYPES.NUMBER:
|
||||
return <NumberContent config={config} />;
|
||||
|
||||
case JOB_FIELD_TYPES.BOOLEAN:
|
||||
case SUPPORTED_FIELD_TYPES.BOOLEAN:
|
||||
return <BooleanContent config={config} />;
|
||||
|
||||
case JOB_FIELD_TYPES.DATE:
|
||||
case SUPPORTED_FIELD_TYPES.DATE:
|
||||
return <DateContent config={config} />;
|
||||
|
||||
case JOB_FIELD_TYPES.GEO_POINT:
|
||||
case SUPPORTED_FIELD_TYPES.GEO_POINT:
|
||||
return <GeoPointContent config={config} />;
|
||||
|
||||
case JOB_FIELD_TYPES.IP:
|
||||
case SUPPORTED_FIELD_TYPES.IP:
|
||||
return <IpContent config={config} />;
|
||||
|
||||
case JOB_FIELD_TYPES.KEYWORD:
|
||||
case SUPPORTED_FIELD_TYPES.KEYWORD:
|
||||
case SUPPORTED_FIELD_TYPES.VERSION:
|
||||
return <KeywordContent config={config} />;
|
||||
|
||||
case JOB_FIELD_TYPES.TEXT:
|
||||
case SUPPORTED_FIELD_TYPES.TEXT:
|
||||
return <TextContent config={config} />;
|
||||
|
||||
default:
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC, useEffect, useState } from 'react';
|
||||
import { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { ES_GEO_FIELD_TYPE, LayerDescriptor } from '@kbn/maps-plugin/common';
|
||||
|
@ -14,7 +13,7 @@ import { DocumentStatsTable } from '../../stats_table/components/field_data_expa
|
|||
import { ExamplesList } from '../../examples_list';
|
||||
import { FieldVisConfig } from '../../stats_table/types';
|
||||
import { useDataVisualizerKibana } from '../../../../kibana_context';
|
||||
import { JOB_FIELD_TYPES } from '../../../../../../common/constants';
|
||||
import { SUPPORTED_FIELD_TYPES } from '../../../../../../common/constants';
|
||||
import { EmbeddedMapComponent } from '../../embedded_map';
|
||||
import { ExpandedRowPanel } from '../../stats_table/components/field_data_expanded_row/expanded_row_panel';
|
||||
|
||||
|
@ -36,7 +35,8 @@ export const GeoPointContentWithMap: FC<{
|
|||
dataView?.id !== undefined &&
|
||||
config !== undefined &&
|
||||
config.fieldName !== undefined &&
|
||||
(config.type === JOB_FIELD_TYPES.GEO_POINT || config.type === JOB_FIELD_TYPES.GEO_SHAPE)
|
||||
(config.type === SUPPORTED_FIELD_TYPES.GEO_POINT ||
|
||||
config.type === SUPPORTED_FIELD_TYPES.GEO_SHAPE)
|
||||
) {
|
||||
const params = {
|
||||
indexPatternId: dataView.id,
|
||||
|
@ -64,7 +64,7 @@ export const GeoPointContentWithMap: FC<{
|
|||
return (
|
||||
<ExpandedRowContent dataTestSubj={'dataVisualizerIndexBasedMapContent'}>
|
||||
<DocumentStatsTable config={config} />
|
||||
<ExamplesList examples={stats.examples} />
|
||||
<ExamplesList examples={stats?.examples} />
|
||||
<ExpandedRowPanel className={'dvPanel__wrapper dvMap__wrapper'} grow={true}>
|
||||
<EmbeddedMapComponent layerList={layerList} />
|
||||
</ExpandedRowPanel>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React from 'react';
|
||||
import { DataView, DataViewField } from '@kbn/data-views-plugin/public';
|
||||
import { GeoPointContentWithMap } from './geo_point_content_with_map';
|
||||
import { JOB_FIELD_TYPES } from '../../../../../common/constants';
|
||||
import { SUPPORTED_FIELD_TYPES } from '../../../../../common/constants';
|
||||
import {
|
||||
BooleanContent,
|
||||
DateContent,
|
||||
|
@ -51,17 +51,17 @@ export const IndexBasedDataVisualizerExpandedRow = ({
|
|||
}
|
||||
|
||||
switch (type) {
|
||||
case JOB_FIELD_TYPES.NUMBER:
|
||||
case SUPPORTED_FIELD_TYPES.NUMBER:
|
||||
return <NumberContent config={config} onAddFilter={onAddFilter} />;
|
||||
|
||||
case JOB_FIELD_TYPES.BOOLEAN:
|
||||
return <BooleanContent config={config} />;
|
||||
case SUPPORTED_FIELD_TYPES.BOOLEAN:
|
||||
return <BooleanContent config={config} onAddFilter={onAddFilter} />;
|
||||
|
||||
case JOB_FIELD_TYPES.DATE:
|
||||
case SUPPORTED_FIELD_TYPES.DATE:
|
||||
return <DateContent config={config} />;
|
||||
|
||||
case JOB_FIELD_TYPES.GEO_POINT:
|
||||
case JOB_FIELD_TYPES.GEO_SHAPE:
|
||||
case SUPPORTED_FIELD_TYPES.GEO_POINT:
|
||||
case SUPPORTED_FIELD_TYPES.GEO_SHAPE:
|
||||
return (
|
||||
<GeoPointContentWithMap
|
||||
config={config}
|
||||
|
@ -70,13 +70,14 @@ export const IndexBasedDataVisualizerExpandedRow = ({
|
|||
/>
|
||||
);
|
||||
|
||||
case JOB_FIELD_TYPES.IP:
|
||||
case SUPPORTED_FIELD_TYPES.IP:
|
||||
return <IpContent config={config} onAddFilter={onAddFilter} />;
|
||||
|
||||
case JOB_FIELD_TYPES.KEYWORD:
|
||||
case SUPPORTED_FIELD_TYPES.KEYWORD:
|
||||
case SUPPORTED_FIELD_TYPES.VERSION:
|
||||
return <KeywordContent config={config} onAddFilter={onAddFilter} />;
|
||||
|
||||
case JOB_FIELD_TYPES.TEXT:
|
||||
case SUPPORTED_FIELD_TYPES.TEXT:
|
||||
return <TextContent config={config} />;
|
||||
|
||||
default:
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
dataVisualizerRefresh$,
|
||||
Refresh,
|
||||
} from '../../../../index_data_visualizer/services/timefilter_refresh_service';
|
||||
import { JOB_FIELD_TYPES } from '../../../../../../common/constants';
|
||||
import { SUPPORTED_FIELD_TYPES } from '../../../../../../common/constants';
|
||||
import { APP_ID } from '../../../../../../common/constants';
|
||||
|
||||
export function getActions(
|
||||
|
@ -80,7 +80,10 @@ export function getActions(
|
|||
type: 'icon',
|
||||
icon: 'gisApp',
|
||||
available: (item: FieldVisConfig) => {
|
||||
return item.type === JOB_FIELD_TYPES.GEO_POINT || item.type === JOB_FIELD_TYPES.GEO_SHAPE;
|
||||
return (
|
||||
item.type === SUPPORTED_FIELD_TYPES.GEO_POINT ||
|
||||
item.type === SUPPORTED_FIELD_TYPES.GEO_SHAPE
|
||||
);
|
||||
},
|
||||
onClick: async (item: FieldVisConfig) => {
|
||||
if (services?.uiActions && dataView) {
|
||||
|
|
|
@ -19,7 +19,7 @@ import type {
|
|||
import { DOCUMENT_FIELD_NAME as RECORDS_FIELD } from '@kbn/lens-plugin/common/constants';
|
||||
import type { CombinedQuery } from '../../../../index_data_visualizer/types/combined_query';
|
||||
import { FieldVisConfig } from '../../stats_table/types';
|
||||
import { JOB_FIELD_TYPES } from '../../../../../../common/constants';
|
||||
import { SUPPORTED_FIELD_TYPES } from '../../../../../../common/constants';
|
||||
|
||||
interface ColumnsAndLayer {
|
||||
columns: Record<string, GenericIndexPatternColumn>;
|
||||
|
@ -200,19 +200,20 @@ export function getBooleanSettings(item: FieldVisConfig) {
|
|||
export function getCompatibleLensDataType(type: FieldVisConfig['type']): string | undefined {
|
||||
let lensType: string | undefined;
|
||||
switch (type) {
|
||||
case JOB_FIELD_TYPES.KEYWORD:
|
||||
case SUPPORTED_FIELD_TYPES.KEYWORD:
|
||||
case SUPPORTED_FIELD_TYPES.VERSION:
|
||||
lensType = 'string';
|
||||
break;
|
||||
case JOB_FIELD_TYPES.DATE:
|
||||
case SUPPORTED_FIELD_TYPES.DATE:
|
||||
lensType = 'date';
|
||||
break;
|
||||
case JOB_FIELD_TYPES.NUMBER:
|
||||
case SUPPORTED_FIELD_TYPES.NUMBER:
|
||||
lensType = 'number';
|
||||
break;
|
||||
case JOB_FIELD_TYPES.IP:
|
||||
case SUPPORTED_FIELD_TYPES.IP:
|
||||
lensType = 'ip';
|
||||
break;
|
||||
case JOB_FIELD_TYPES.BOOLEAN:
|
||||
case SUPPORTED_FIELD_TYPES.BOOLEAN:
|
||||
lensType = 'string';
|
||||
break;
|
||||
default:
|
||||
|
@ -228,16 +229,20 @@ function getColumnsAndLayer(
|
|||
): ColumnsAndLayer | undefined {
|
||||
if (item.fieldName === undefined) return;
|
||||
|
||||
if (fieldType === JOB_FIELD_TYPES.DATE) {
|
||||
if (fieldType === SUPPORTED_FIELD_TYPES.DATE) {
|
||||
return getDateSettings(item);
|
||||
}
|
||||
if (fieldType === JOB_FIELD_TYPES.NUMBER) {
|
||||
if (fieldType === SUPPORTED_FIELD_TYPES.NUMBER) {
|
||||
return getNumberSettings(item, defaultDataView);
|
||||
}
|
||||
if (fieldType === JOB_FIELD_TYPES.IP || fieldType === JOB_FIELD_TYPES.KEYWORD) {
|
||||
if (
|
||||
fieldType === SUPPORTED_FIELD_TYPES.IP ||
|
||||
fieldType === SUPPORTED_FIELD_TYPES.KEYWORD ||
|
||||
fieldType === SUPPORTED_FIELD_TYPES.VERSION
|
||||
) {
|
||||
return getKeywordSettings(item);
|
||||
}
|
||||
if (fieldType === JOB_FIELD_TYPES.BOOLEAN) {
|
||||
if (fieldType === SUPPORTED_FIELD_TYPES.BOOLEAN) {
|
||||
return getBooleanSettings(item);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,12 +9,12 @@ import React from 'react';
|
|||
import { mount, shallow } from 'enzyme';
|
||||
|
||||
import { FieldTypeIcon } from './field_type_icon';
|
||||
import { JOB_FIELD_TYPES } from '../../../../../common/constants';
|
||||
import { SUPPORTED_FIELD_TYPES } from '../../../../../common/constants';
|
||||
|
||||
describe('FieldTypeIcon', () => {
|
||||
test(`render component when type matches a field type`, () => {
|
||||
const typeIconComponent = shallow(
|
||||
<FieldTypeIcon type={JOB_FIELD_TYPES.KEYWORD} tooltipEnabled={true} />
|
||||
<FieldTypeIcon type={SUPPORTED_FIELD_TYPES.KEYWORD} tooltipEnabled={true} />
|
||||
);
|
||||
expect(typeIconComponent).toMatchSnapshot();
|
||||
});
|
||||
|
@ -24,7 +24,7 @@ describe('FieldTypeIcon', () => {
|
|||
jest.useFakeTimers();
|
||||
|
||||
const typeIconComponent = mount(
|
||||
<FieldTypeIcon type={JOB_FIELD_TYPES.KEYWORD} tooltipEnabled={true} />
|
||||
<FieldTypeIcon type={SUPPORTED_FIELD_TYPES.KEYWORD} tooltipEnabled={true} />
|
||||
);
|
||||
|
||||
expect(typeIconComponent.find('EuiToolTip').children()).toHaveLength(1);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { FindFileStructureResponse } from '@kbn/file-upload-plugin/common';
|
||||
import { getFieldNames, getSupportedFieldType } from './get_field_names';
|
||||
import { FileBasedFieldVisConfig } from '../stats_table/types';
|
||||
import { JOB_FIELD_TYPES } from '../../../../../common/constants';
|
||||
import { SUPPORTED_FIELD_TYPES } from '../../../../../common/constants';
|
||||
import { roundToDecimalPlace } from '../utils';
|
||||
|
||||
export function createFields(results: FindFileStructureResponse) {
|
||||
|
@ -28,20 +28,20 @@ export function createFields(results: FindFileStructureResponse) {
|
|||
if (fieldStats[name] !== undefined) {
|
||||
const field: FileBasedFieldVisConfig = {
|
||||
fieldName: name,
|
||||
type: JOB_FIELD_TYPES.UNKNOWN,
|
||||
type: SUPPORTED_FIELD_TYPES.UNKNOWN,
|
||||
};
|
||||
const f = fieldStats[name];
|
||||
const m = mappings.properties[name];
|
||||
|
||||
// sometimes the timestamp field is not in the mappings, and so our
|
||||
// collection of fields will be missing a time field with a type of date
|
||||
if (name === timestampField && field.type === JOB_FIELD_TYPES.UNKNOWN) {
|
||||
field.type = JOB_FIELD_TYPES.DATE;
|
||||
if (name === timestampField && field.type === SUPPORTED_FIELD_TYPES.UNKNOWN) {
|
||||
field.type = SUPPORTED_FIELD_TYPES.DATE;
|
||||
}
|
||||
|
||||
if (m !== undefined) {
|
||||
field.type = getSupportedFieldType(m.type);
|
||||
if (field.type === JOB_FIELD_TYPES.NUMBER) {
|
||||
if (field.type === SUPPORTED_FIELD_TYPES.NUMBER) {
|
||||
numericFieldsCount += 1;
|
||||
}
|
||||
if (m.format !== undefined) {
|
||||
|
@ -71,7 +71,7 @@ export function createFields(results: FindFileStructureResponse) {
|
|||
}
|
||||
|
||||
if (f.top_hits !== undefined) {
|
||||
if (field.type === JOB_FIELD_TYPES.TEXT) {
|
||||
if (field.type === SUPPORTED_FIELD_TYPES.TEXT) {
|
||||
_stats = {
|
||||
..._stats,
|
||||
examples: f.top_hits.map((hit) => hit.value),
|
||||
|
@ -84,7 +84,7 @@ export function createFields(results: FindFileStructureResponse) {
|
|||
}
|
||||
}
|
||||
|
||||
if (field.type === JOB_FIELD_TYPES.DATE) {
|
||||
if (field.type === SUPPORTED_FIELD_TYPES.DATE) {
|
||||
_stats = {
|
||||
..._stats,
|
||||
earliest: f.earliest,
|
||||
|
@ -99,9 +99,9 @@ export function createFields(results: FindFileStructureResponse) {
|
|||
// this could be the message field for a semi-structured log file or a
|
||||
// field which the endpoint has not been able to work out any information for
|
||||
const type =
|
||||
mappings.properties[name] && mappings.properties[name].type === JOB_FIELD_TYPES.TEXT
|
||||
? JOB_FIELD_TYPES.TEXT
|
||||
: JOB_FIELD_TYPES.UNKNOWN;
|
||||
mappings.properties[name] && mappings.properties[name].type === SUPPORTED_FIELD_TYPES.TEXT
|
||||
? SUPPORTED_FIELD_TYPES.TEXT
|
||||
: SUPPORTED_FIELD_TYPES.UNKNOWN;
|
||||
|
||||
return {
|
||||
fieldName: name,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { JOB_FIELD_TYPES } from '../../../../../common/constants';
|
||||
import { SUPPORTED_FIELD_TYPES } from '../../../../../common/constants';
|
||||
|
||||
interface CommonFieldConfig {
|
||||
type: string;
|
||||
|
@ -32,6 +32,6 @@ export function filterFields<T extends CommonFieldConfig>(
|
|||
return {
|
||||
filteredFields: items,
|
||||
visibleFieldsCount: items.length,
|
||||
visibleMetricsCount: items.filter((d) => d.type === JOB_FIELD_TYPES.NUMBER).length,
|
||||
visibleMetricsCount: items.filter((d) => d.type === SUPPORTED_FIELD_TYPES.NUMBER).length,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { difference } from 'lodash';
|
|||
import { ES_FIELD_TYPES } from '@kbn/data-plugin/common';
|
||||
import type { FindFileStructureResponse } from '@kbn/file-upload-plugin/common';
|
||||
import type { JobFieldType } from '../../../../../common/types';
|
||||
import { JOB_FIELD_TYPES } from '../../../../../common/constants';
|
||||
import { SUPPORTED_FIELD_TYPES } from '../../../../../common/constants';
|
||||
export function getFieldNames(results: FindFileStructureResponse) {
|
||||
const { mappings, field_stats: fieldStats, column_names: columnNames } = results;
|
||||
|
||||
|
@ -44,11 +44,11 @@ export function getSupportedFieldType(type: string): JobFieldType {
|
|||
case ES_FIELD_TYPES.LONG:
|
||||
case ES_FIELD_TYPES.SHORT:
|
||||
case ES_FIELD_TYPES.UNSIGNED_LONG:
|
||||
return JOB_FIELD_TYPES.NUMBER;
|
||||
return SUPPORTED_FIELD_TYPES.NUMBER;
|
||||
|
||||
case ES_FIELD_TYPES.DATE:
|
||||
case ES_FIELD_TYPES.DATE_NANOS:
|
||||
return JOB_FIELD_TYPES.DATE;
|
||||
return SUPPORTED_FIELD_TYPES.DATE;
|
||||
|
||||
default:
|
||||
return type as JobFieldType;
|
||||
|
|
|
@ -31,9 +31,8 @@ $panelWidthL: #{'max(40%, 450px)'};
|
|||
}
|
||||
|
||||
.euiTableRow > .euiTableRowCell {
|
||||
border-bottom: 0;
|
||||
border-top: $euiBorderThin;
|
||||
|
||||
border-top: 0;
|
||||
border-bottom: $euiBorderThin;
|
||||
}
|
||||
|
||||
.euiTableCellContent {
|
||||
|
|
|
@ -5,18 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC, ReactNode, useMemo } from 'react';
|
||||
import {
|
||||
EuiBasicTable,
|
||||
EuiSpacer,
|
||||
RIGHT_ALIGNMENT,
|
||||
LEFT_ALIGNMENT,
|
||||
HorizontalAlignment,
|
||||
} from '@elastic/eui';
|
||||
import React, { FC, useMemo } from 'react';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { Axis, BarSeries, Chart, Settings, ScaleType } from '@elastic/charts';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TopValues } from '../../../top_values';
|
||||
import type { FieldDataRowProps } from '../../types/field_data_row';
|
||||
import { ExpandedRowFieldHeader } from '../expanded_row_field_header';
|
||||
import { getTFPercentage } from '../../utils';
|
||||
|
@ -44,72 +39,42 @@ function getFormattedValue(value: number, totalCount: number): string {
|
|||
|
||||
const BOOLEAN_DISTRIBUTION_CHART_HEIGHT = 70;
|
||||
|
||||
export const BooleanContent: FC<FieldDataRowProps> = ({ config }) => {
|
||||
export const BooleanContent: FC<FieldDataRowProps> = ({ config, onAddFilter }) => {
|
||||
const fieldFormat = 'fieldFormat' in config ? config.fieldFormat : undefined;
|
||||
const formattedPercentages = useMemo(() => getTFPercentage(config), [config]);
|
||||
const theme = useDataVizChartTheme();
|
||||
if (!formattedPercentages) return null;
|
||||
|
||||
const { trueCount, falseCount, count } = formattedPercentages;
|
||||
const summaryTableItems = [
|
||||
{
|
||||
function: 'true',
|
||||
display: (
|
||||
<FormattedMessage
|
||||
id="xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.trueCountLabel"
|
||||
defaultMessage="true"
|
||||
/>
|
||||
),
|
||||
value: getFormattedValue(trueCount, count),
|
||||
},
|
||||
{
|
||||
function: 'false',
|
||||
display: (
|
||||
<FormattedMessage
|
||||
id="xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.falseCountLabel"
|
||||
defaultMessage="false"
|
||||
/>
|
||||
),
|
||||
value: getFormattedValue(falseCount, count),
|
||||
},
|
||||
];
|
||||
const summaryTableColumns = [
|
||||
{
|
||||
field: 'function',
|
||||
name: '',
|
||||
render: (_: string, summaryItem: { display: ReactNode }) => summaryItem.display,
|
||||
width: '25px',
|
||||
align: LEFT_ALIGNMENT as HorizontalAlignment,
|
||||
},
|
||||
{
|
||||
field: 'value',
|
||||
name: '',
|
||||
render: (v: string) => <strong>{v}</strong>,
|
||||
align: RIGHT_ALIGNMENT as HorizontalAlignment,
|
||||
},
|
||||
];
|
||||
|
||||
const summaryTableTitle = i18n.translate(
|
||||
'xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.summaryTableTitle',
|
||||
{
|
||||
defaultMessage: 'Summary',
|
||||
}
|
||||
);
|
||||
|
||||
const stats = {
|
||||
...config.stats,
|
||||
topValues: [
|
||||
{
|
||||
key: i18n.translate(
|
||||
'xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.trueCountLabel',
|
||||
{ defaultMessage: 'true' }
|
||||
),
|
||||
doc_count: trueCount ?? 0,
|
||||
},
|
||||
{
|
||||
key: i18n.translate(
|
||||
'xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.falseCountLabel',
|
||||
{ defaultMessage: 'false' }
|
||||
),
|
||||
doc_count: falseCount ?? 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
return (
|
||||
<ExpandedRowContent dataTestSubj={'dataVisualizerBooleanContent'}>
|
||||
<DocumentStatsTable config={config} />
|
||||
|
||||
<ExpandedRowPanel className={'dvSummaryTable__wrapper dvPanel__wrapper'}>
|
||||
<ExpandedRowFieldHeader>{summaryTableTitle}</ExpandedRowFieldHeader>
|
||||
<EuiBasicTable
|
||||
className={'dvSummaryTable'}
|
||||
compressed
|
||||
items={summaryTableItems}
|
||||
columns={summaryTableColumns}
|
||||
tableCaption={summaryTableTitle}
|
||||
/>
|
||||
</ExpandedRowPanel>
|
||||
<TopValues
|
||||
stats={stats}
|
||||
fieldFormat={fieldFormat}
|
||||
barColor="success"
|
||||
onAddFilter={onAddFilter}
|
||||
/>
|
||||
|
||||
<ExpandedRowPanel className={'dvPanel__wrapper dvPanel--uniform'}>
|
||||
<ExpandedRowFieldHeader>
|
||||
|
|
|
@ -20,11 +20,13 @@ import {
|
|||
RIGHT_ALIGNMENT,
|
||||
EuiResizeObserver,
|
||||
EuiLoadingSpinner,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiTableComputedColumnType } from '@elastic/eui/src/components/basic_table/table_types';
|
||||
import { throttle } from 'lodash';
|
||||
import { JOB_FIELD_TYPES } from '../../../../../common/constants';
|
||||
import { css } from '@emotion/react';
|
||||
import { SUPPORTED_FIELD_TYPES } from '../../../../../common/constants';
|
||||
import type { JobFieldType, DataVisualizerTableState } from '../../../../../common/types';
|
||||
import { DocumentStat } from './components/field_data_row/document_stats';
|
||||
import { IndexBasedNumberContentPreview } from './components/field_data_row/number_content_preview';
|
||||
|
@ -70,6 +72,8 @@ export const DataVisualizerTable = <T extends DataVisualizerTableItem>({
|
|||
onChange,
|
||||
loading,
|
||||
}: DataVisualizerTableProps<T>) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const [expandedRowItemIds, setExpandedRowItemIds] = useState<string[]>([]);
|
||||
const [expandAll, setExpandAll] = useState<boolean>(false);
|
||||
|
||||
|
@ -289,13 +293,14 @@ export const DataVisualizerTable = <T extends DataVisualizerTableItem>({
|
|||
}
|
||||
|
||||
if (
|
||||
(item.type === JOB_FIELD_TYPES.KEYWORD || item.type === JOB_FIELD_TYPES.IP) &&
|
||||
(item.type === SUPPORTED_FIELD_TYPES.KEYWORD ||
|
||||
item.type === SUPPORTED_FIELD_TYPES.IP) &&
|
||||
item.stats?.topValues !== undefined
|
||||
) {
|
||||
return <TopValuesPreview config={item} />;
|
||||
}
|
||||
|
||||
if (item.type === JOB_FIELD_TYPES.NUMBER) {
|
||||
if (item.type === SUPPORTED_FIELD_TYPES.NUMBER) {
|
||||
if (isIndexBasedFieldVisConfig(item) && item.stats?.distribution !== undefined) {
|
||||
// If the cardinality is only low, show the top values instead of a distribution chart
|
||||
return item.stats?.distribution?.percentiles.length <= 2 ? (
|
||||
|
@ -308,7 +313,7 @@ export const DataVisualizerTable = <T extends DataVisualizerTableItem>({
|
|||
}
|
||||
}
|
||||
|
||||
if (item.type === JOB_FIELD_TYPES.BOOLEAN) {
|
||||
if (item.type === SUPPORTED_FIELD_TYPES.BOOLEAN) {
|
||||
return <BooleanContentPreview config={item} />;
|
||||
}
|
||||
|
||||
|
@ -361,6 +366,18 @@ export const DataVisualizerTable = <T extends DataVisualizerTableItem>({
|
|||
rowProps={(item) => ({
|
||||
'data-test-subj': `dataVisualizerRow row-${item.fieldName}`,
|
||||
})}
|
||||
css={css`
|
||||
thead {
|
||||
position: sticky;
|
||||
inset-block-start: 0;
|
||||
z-index: 1;
|
||||
background-color: ${euiTheme.colors.emptyShade};
|
||||
box-shadow: inset 0 0px 0, inset 0 -1px 0 ${euiTheme.border.color};
|
||||
}
|
||||
.euiTableRow > .euiTableRowCel {
|
||||
border-top: 0px;
|
||||
}
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* 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 { getUniqGeoOrStrExamples } from './example_utils';
|
||||
|
||||
describe('example utils', () => {
|
||||
describe('getUniqGeoOrStrExamples', () => {
|
||||
test('should remove duplicated strings up to maxExamples', () => {
|
||||
expect(
|
||||
getUniqGeoOrStrExamples(
|
||||
[
|
||||
'deb',
|
||||
'',
|
||||
'css',
|
||||
'deb',
|
||||
'',
|
||||
'',
|
||||
'deb',
|
||||
'gz',
|
||||
'',
|
||||
'gz',
|
||||
'',
|
||||
'deb',
|
||||
'gz',
|
||||
'deb',
|
||||
'',
|
||||
'deb',
|
||||
'deb',
|
||||
'',
|
||||
'gz',
|
||||
'gz',
|
||||
],
|
||||
20
|
||||
)
|
||||
).toMatchObject(['deb', '', 'css', 'gz']);
|
||||
expect(
|
||||
getUniqGeoOrStrExamples(
|
||||
[
|
||||
'deb',
|
||||
'',
|
||||
'css',
|
||||
'deb',
|
||||
'',
|
||||
'',
|
||||
'deb',
|
||||
'gz',
|
||||
'',
|
||||
'gz',
|
||||
'',
|
||||
'deb',
|
||||
'gz',
|
||||
'deb',
|
||||
'',
|
||||
'deb',
|
||||
'deb',
|
||||
'',
|
||||
'gz',
|
||||
'gz',
|
||||
],
|
||||
2
|
||||
)
|
||||
).toMatchObject(['deb', '']);
|
||||
});
|
||||
|
||||
test('should remove duplicated coordinates up to maxExamples', () => {
|
||||
expect(
|
||||
getUniqGeoOrStrExamples([
|
||||
{ coordinates: [0.1, 2343], type: 'Point' },
|
||||
{ coordinates: [0.1, 2343], type: 'Point' },
|
||||
{ coordinates: [0.1, 2343], type: 'Point' },
|
||||
{ coordinates: [0.1, 2343], type: 'Shape' },
|
||||
{ coordinates: [0.1, 2343] },
|
||||
{ coordinates: [4321, 2343], type: 'Point' },
|
||||
{ coordinates: [4321, 2343], type: 'Point' },
|
||||
])
|
||||
).toMatchObject([
|
||||
{
|
||||
coordinates: [0.1, 2343],
|
||||
type: 'Point',
|
||||
},
|
||||
{
|
||||
coordinates: [0.1, 2343],
|
||||
type: 'Shape',
|
||||
},
|
||||
{
|
||||
coordinates: [0.1, 2343],
|
||||
},
|
||||
{
|
||||
coordinates: [4321, 2343],
|
||||
type: 'Point',
|
||||
},
|
||||
]);
|
||||
expect(
|
||||
getUniqGeoOrStrExamples([
|
||||
{ coordinates: [1, 2, 3], type: 'Point' },
|
||||
{ coordinates: [1, 2, 3], type: 'Point' },
|
||||
{ coordinates: [1, 2, 3], type: 'Point' },
|
||||
{ coordinates: [1, 2, 3, 4], type: 'Shape' },
|
||||
{ coordinates: [1, 2, 3, 4] },
|
||||
])
|
||||
).toMatchObject([
|
||||
{
|
||||
coordinates: [1, 2, 3],
|
||||
type: 'Point',
|
||||
},
|
||||
{ coordinates: [1, 2, 3, 4], type: 'Shape' },
|
||||
{ coordinates: [1, 2, 3, 4] },
|
||||
]);
|
||||
});
|
||||
|
||||
test('should remove duplicated lon/lat coordinates up to maxExamples', () => {
|
||||
expect(
|
||||
getUniqGeoOrStrExamples([
|
||||
{ lon: 0.1, lat: 2343 },
|
||||
{ lon: 0.1, lat: 2343 },
|
||||
{ lon: 0.1, lat: 2343 },
|
||||
{ lon: 0.1, lat: 2343 },
|
||||
{ lon: 0.1, lat: 2343 },
|
||||
{ lon: 4321, lat: 2343 },
|
||||
{ lon: 4321, lat: 2343 },
|
||||
])
|
||||
).toMatchObject([
|
||||
{ lon: 0.1, lat: 2343 },
|
||||
{ lon: 4321, lat: 2343 },
|
||||
]);
|
||||
expect(
|
||||
getUniqGeoOrStrExamples(
|
||||
[
|
||||
{ lon: 1, lat: 2 },
|
||||
{ lon: 1, lat: 2 },
|
||||
{ lon: 2, lat: 3 },
|
||||
{ lon: 2, lat: 3 },
|
||||
{ lon: 3, lat: 4 },
|
||||
{ lon: 3, lat: 4 },
|
||||
{ lon: 4, lat: 5 },
|
||||
{ lon: 4, lat: 5 },
|
||||
{ lon: 5, lat: 6 },
|
||||
{ lon: 5, lat: 6 },
|
||||
],
|
||||
3
|
||||
)
|
||||
).toMatchObject([
|
||||
{ lon: 1, lat: 2 },
|
||||
{ lon: 2, lat: 3 },
|
||||
{ lon: 3, lat: 4 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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 { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
||||
import { isDefined } from './is_defined';
|
||||
import { GeoPointExample, LatLongExample } from '../../../../common/types/field_request_config';
|
||||
|
||||
export function isGeoPointExample(arg: unknown): arg is GeoPointExample {
|
||||
return isPopulatedObject(arg, ['coordinates']) && Array.isArray(arg.coordinates);
|
||||
}
|
||||
|
||||
export function isLonLatExample(arg: unknown): arg is LatLongExample {
|
||||
return isPopulatedObject(arg, ['lon', 'lat']);
|
||||
}
|
||||
|
||||
export function getUniqGeoOrStrExamples(
|
||||
examples: Array<string | GeoPointExample | LatLongExample | object> | undefined,
|
||||
maxExamples = 10
|
||||
): Array<string | GeoPointExample | LatLongExample | object> {
|
||||
const uniqueCoordinates: Array<string | GeoPointExample | LatLongExample | object> = [];
|
||||
if (!isDefined(examples)) return uniqueCoordinates;
|
||||
for (let i = 0; i < examples.length; i++) {
|
||||
const example = examples[i];
|
||||
if (typeof example === 'string' && uniqueCoordinates.indexOf(example) === -1) {
|
||||
uniqueCoordinates.push(example);
|
||||
} else {
|
||||
if (
|
||||
isGeoPointExample(example) &&
|
||||
uniqueCoordinates.findIndex(
|
||||
(c) =>
|
||||
isGeoPointExample(c) &&
|
||||
c.type === example.type &&
|
||||
example.coordinates.every((coord, idx) => coord === c.coordinates[idx])
|
||||
) === -1
|
||||
) {
|
||||
uniqueCoordinates.push(example);
|
||||
}
|
||||
|
||||
if (
|
||||
isLonLatExample(example) &&
|
||||
uniqueCoordinates.findIndex(
|
||||
(c) => isLonLatExample(c) && example.lon === c.lon && example.lat === c.lat
|
||||
) === -1
|
||||
) {
|
||||
uniqueCoordinates.push(example);
|
||||
}
|
||||
}
|
||||
if (uniqueCoordinates.length === maxExamples) {
|
||||
return uniqueCoordinates;
|
||||
}
|
||||
}
|
||||
|
||||
return uniqueCoordinates;
|
||||
}
|
|
@ -5,24 +5,26 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { JOB_FIELD_TYPES } from '../../../../common/constants';
|
||||
import { SUPPORTED_FIELD_TYPES } from '../../../../common/constants';
|
||||
import { getJobTypeLabel, jobTypeLabels } from './field_types_utils';
|
||||
|
||||
describe('field type utils', () => {
|
||||
describe('getJobTypeLabel: Getting a field type aria label by passing what it is stored in constants', () => {
|
||||
test('should returns all JOB_FIELD_TYPES labels exactly as it is for each correct value', () => {
|
||||
const keys = Object.keys(JOB_FIELD_TYPES);
|
||||
test('should returns all SUPPORTED_FIELD_TYPES labels exactly as it is for each correct value', () => {
|
||||
const keys = Object.keys(SUPPORTED_FIELD_TYPES);
|
||||
const receivedLabels: Record<string, string | null> = {};
|
||||
const testStorage = jobTypeLabels;
|
||||
keys.forEach((key) => {
|
||||
const constant = key as keyof typeof JOB_FIELD_TYPES;
|
||||
receivedLabels[JOB_FIELD_TYPES[constant]] = getJobTypeLabel(JOB_FIELD_TYPES[constant]);
|
||||
const constant = key as keyof typeof SUPPORTED_FIELD_TYPES;
|
||||
receivedLabels[SUPPORTED_FIELD_TYPES[constant]] = getJobTypeLabel(
|
||||
SUPPORTED_FIELD_TYPES[constant]
|
||||
);
|
||||
});
|
||||
|
||||
expect(receivedLabels).toEqual(testStorage);
|
||||
});
|
||||
test('should returns NULL as JOB_FIELD_TYPES does not contain such a keyword', () => {
|
||||
expect(getJobTypeLabel('JOB_FIELD_TYPES')).toBe(null);
|
||||
test('should returns NULL as SUPPORTED_FIELD_TYPES does not contain such a keyword', () => {
|
||||
expect(getJobTypeLabel('SUPPORTED_FIELD_TYPES')).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,52 +8,70 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { DataViewField } from '@kbn/data-views-plugin/public';
|
||||
import { KBN_FIELD_TYPES } from '@kbn/data-plugin/common';
|
||||
import { JOB_FIELD_TYPES } from '../../../../common/constants';
|
||||
import { SUPPORTED_FIELD_TYPES } from '../../../../common/constants';
|
||||
|
||||
export const getJobTypeLabel = (type: string) => {
|
||||
return type in jobTypeLabels ? jobTypeLabels[type as keyof typeof jobTypeLabels] : null;
|
||||
};
|
||||
|
||||
export const jobTypeLabels = {
|
||||
[JOB_FIELD_TYPES.BOOLEAN]: i18n.translate('xpack.dataVisualizer.fieldTypeIcon.booleanTypeLabel', {
|
||||
defaultMessage: 'Boolean',
|
||||
}),
|
||||
[JOB_FIELD_TYPES.DATE]: i18n.translate('xpack.dataVisualizer.fieldTypeIcon.dateTypeLabel', {
|
||||
[SUPPORTED_FIELD_TYPES.BOOLEAN]: i18n.translate(
|
||||
'xpack.dataVisualizer.fieldTypeIcon.booleanTypeLabel',
|
||||
{
|
||||
defaultMessage: 'Boolean',
|
||||
}
|
||||
),
|
||||
[SUPPORTED_FIELD_TYPES.DATE]: i18n.translate('xpack.dataVisualizer.fieldTypeIcon.dateTypeLabel', {
|
||||
defaultMessage: 'Date',
|
||||
}),
|
||||
[JOB_FIELD_TYPES.GEO_POINT]: i18n.translate(
|
||||
[SUPPORTED_FIELD_TYPES.GEO_POINT]: i18n.translate(
|
||||
'xpack.dataVisualizer.fieldTypeIcon.geoPointTypeLabel',
|
||||
{
|
||||
defaultMessage: 'Geo point',
|
||||
}
|
||||
),
|
||||
[JOB_FIELD_TYPES.GEO_SHAPE]: i18n.translate(
|
||||
[SUPPORTED_FIELD_TYPES.GEO_SHAPE]: i18n.translate(
|
||||
'xpack.dataVisualizer.fieldTypeIcon.geoShapeTypeLabel',
|
||||
{
|
||||
defaultMessage: 'Geo shape',
|
||||
}
|
||||
),
|
||||
[JOB_FIELD_TYPES.IP]: i18n.translate('xpack.dataVisualizer.fieldTypeIcon.ipTypeLabel', {
|
||||
[SUPPORTED_FIELD_TYPES.IP]: i18n.translate('xpack.dataVisualizer.fieldTypeIcon.ipTypeLabel', {
|
||||
defaultMessage: 'IP',
|
||||
}),
|
||||
[JOB_FIELD_TYPES.KEYWORD]: i18n.translate('xpack.dataVisualizer.fieldTypeIcon.keywordTypeLabel', {
|
||||
defaultMessage: 'Keyword',
|
||||
}),
|
||||
[JOB_FIELD_TYPES.NUMBER]: i18n.translate('xpack.dataVisualizer.fieldTypeIcon.numberTypeLabel', {
|
||||
defaultMessage: 'Number',
|
||||
}),
|
||||
[JOB_FIELD_TYPES.HISTOGRAM]: i18n.translate(
|
||||
[SUPPORTED_FIELD_TYPES.KEYWORD]: i18n.translate(
|
||||
'xpack.dataVisualizer.fieldTypeIcon.keywordTypeLabel',
|
||||
{
|
||||
defaultMessage: 'Keyword',
|
||||
}
|
||||
),
|
||||
[SUPPORTED_FIELD_TYPES.NUMBER]: i18n.translate(
|
||||
'xpack.dataVisualizer.fieldTypeIcon.numberTypeLabel',
|
||||
{
|
||||
defaultMessage: 'Number',
|
||||
}
|
||||
),
|
||||
[SUPPORTED_FIELD_TYPES.HISTOGRAM]: i18n.translate(
|
||||
'xpack.dataVisualizer.fieldTypeIcon.histogramTypeLabel',
|
||||
{
|
||||
defaultMessage: 'Histogram',
|
||||
}
|
||||
),
|
||||
[JOB_FIELD_TYPES.TEXT]: i18n.translate('xpack.dataVisualizer.fieldTypeIcon.textTypeLabel', {
|
||||
[SUPPORTED_FIELD_TYPES.TEXT]: i18n.translate('xpack.dataVisualizer.fieldTypeIcon.textTypeLabel', {
|
||||
defaultMessage: 'Text',
|
||||
}),
|
||||
[JOB_FIELD_TYPES.UNKNOWN]: i18n.translate('xpack.dataVisualizer.fieldTypeIcon.unknownTypeLabel', {
|
||||
defaultMessage: 'Unknown',
|
||||
}),
|
||||
[SUPPORTED_FIELD_TYPES.UNKNOWN]: i18n.translate(
|
||||
'xpack.dataVisualizer.fieldTypeIcon.unknownTypeLabel',
|
||||
{
|
||||
defaultMessage: 'Unknown',
|
||||
}
|
||||
),
|
||||
[SUPPORTED_FIELD_TYPES.VERSION]: i18n.translate(
|
||||
'xpack.dataVisualizer.fieldTypeIcon.versionTypeLabel',
|
||||
{
|
||||
defaultMessage: 'Version',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
// convert kibana types to ML Job types
|
||||
|
@ -62,30 +80,35 @@ export const jobTypeLabels = {
|
|||
export function kbnTypeToJobType(field: DataViewField) {
|
||||
// Return undefined if not one of the supported data visualizer field types.
|
||||
let type;
|
||||
|
||||
switch (field.type) {
|
||||
case KBN_FIELD_TYPES.STRING:
|
||||
type = field.aggregatable ? JOB_FIELD_TYPES.KEYWORD : JOB_FIELD_TYPES.TEXT;
|
||||
type = field.aggregatable ? SUPPORTED_FIELD_TYPES.KEYWORD : SUPPORTED_FIELD_TYPES.TEXT;
|
||||
|
||||
if (field.esTypes?.includes(SUPPORTED_FIELD_TYPES.VERSION)) {
|
||||
type = SUPPORTED_FIELD_TYPES.VERSION;
|
||||
}
|
||||
break;
|
||||
case KBN_FIELD_TYPES.NUMBER:
|
||||
type = JOB_FIELD_TYPES.NUMBER;
|
||||
type = SUPPORTED_FIELD_TYPES.NUMBER;
|
||||
break;
|
||||
case KBN_FIELD_TYPES.DATE:
|
||||
type = JOB_FIELD_TYPES.DATE;
|
||||
type = SUPPORTED_FIELD_TYPES.DATE;
|
||||
break;
|
||||
case KBN_FIELD_TYPES.IP:
|
||||
type = JOB_FIELD_TYPES.IP;
|
||||
type = SUPPORTED_FIELD_TYPES.IP;
|
||||
break;
|
||||
case KBN_FIELD_TYPES.BOOLEAN:
|
||||
type = JOB_FIELD_TYPES.BOOLEAN;
|
||||
type = SUPPORTED_FIELD_TYPES.BOOLEAN;
|
||||
break;
|
||||
case KBN_FIELD_TYPES.GEO_POINT:
|
||||
type = JOB_FIELD_TYPES.GEO_POINT;
|
||||
type = SUPPORTED_FIELD_TYPES.GEO_POINT;
|
||||
break;
|
||||
case KBN_FIELD_TYPES.GEO_SHAPE:
|
||||
type = JOB_FIELD_TYPES.GEO_SHAPE;
|
||||
type = SUPPORTED_FIELD_TYPES.GEO_SHAPE;
|
||||
break;
|
||||
case KBN_FIELD_TYPES.HISTOGRAM:
|
||||
type = JOB_FIELD_TYPES.HISTOGRAM;
|
||||
type = SUPPORTED_FIELD_TYPES.HISTOGRAM;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
|
@ -399,7 +399,7 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
|||
<EuiPageContentHeaderSection>
|
||||
<div className="dataViewTitleHeader">
|
||||
<EuiTitle size={'s'}>
|
||||
<h2>{currentDataView.title}</h2>
|
||||
<h2>{currentDataView.getName()}</h2>
|
||||
</EuiTitle>
|
||||
<DataVisualizerDataViewManagement
|
||||
currentDataView={currentDataView}
|
||||
|
|
|
@ -20,7 +20,7 @@ import { dataVisualizerRefresh$ } from '../services/timefilter_refresh_service';
|
|||
import { TimeBuckets } from '../../../../common/services/time_buckets';
|
||||
import { FieldVisConfig } from '../../common/components/stats_table/types';
|
||||
import {
|
||||
JOB_FIELD_TYPES,
|
||||
SUPPORTED_FIELD_TYPES,
|
||||
NON_AGGREGATABLE_FIELD_TYPES,
|
||||
OMIT_FIELDS,
|
||||
} from '../../../../common/constants';
|
||||
|
@ -319,7 +319,7 @@ export const useDataVisualizerGridData = (
|
|||
const metricConfig: FieldVisConfig = {
|
||||
...fieldData,
|
||||
fieldFormat: currentDataView.getFormatterForField(field),
|
||||
type: JOB_FIELD_TYPES.NUMBER,
|
||||
type: SUPPORTED_FIELD_TYPES.NUMBER,
|
||||
loading: fieldData?.existsInDocs ?? true,
|
||||
aggregatable: true,
|
||||
deletable: field.runtimeField !== undefined,
|
||||
|
@ -394,7 +394,6 @@ export const useDataVisualizerGridData = (
|
|||
|
||||
nonMetricFieldsToShow.forEach((field) => {
|
||||
const fieldData = nonMetricFieldData.find((f) => f.fieldName === field.spec.name);
|
||||
|
||||
const nonMetricConfig: Partial<FieldVisConfig> = {
|
||||
...(fieldData ? fieldData : {}),
|
||||
fieldFormat: currentDataView.getFormatterForField(field),
|
||||
|
|
|
@ -15,6 +15,8 @@ import type {
|
|||
ISearchStart,
|
||||
} from '@kbn/data-plugin/public';
|
||||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
||||
import type { SearchHit } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { getUniqGeoOrStrExamples } from '../../../common/util/example_utils';
|
||||
import { buildBaseFilterCriteria } from '../../../../../common/utils/query_utils';
|
||||
import type {
|
||||
Field,
|
||||
|
@ -90,20 +92,11 @@ export const fetchFieldsExamples = (
|
|||
|
||||
if (body.hits.total > 0) {
|
||||
const hits = body.hits.hits;
|
||||
for (let i = 0; i < hits.length; i++) {
|
||||
// Use lodash get() to support field names containing dots.
|
||||
const doc: object[] | undefined = get(hits[i].fields, field.fieldName);
|
||||
// the results from fields query is always an array
|
||||
if (Array.isArray(doc) && doc.length > 0) {
|
||||
const example = doc[0];
|
||||
if (example !== undefined && stats.examples.indexOf(example) === -1) {
|
||||
stats.examples.push(example);
|
||||
if (stats.examples.length === maxExamples) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const processedDocs = hits.map((hit: SearchHit) => {
|
||||
const doc: object[] | undefined = get(hit.fields, field.fieldName);
|
||||
return Array.isArray(doc) && doc.length > 0 ? doc[0] : doc;
|
||||
});
|
||||
stats.examples = getUniqGeoOrStrExamples(processedDocs, maxExamples);
|
||||
}
|
||||
|
||||
return stats;
|
||||
|
|
|
@ -11,7 +11,7 @@ import { ISearchStart } from '@kbn/data-plugin/public';
|
|||
import type { FieldStatsCommonRequestParams } from '../../../../../common/types/field_stats';
|
||||
import type { FieldStatsError } from '../../../../../common/types/field_stats';
|
||||
import type { FieldStats } from '../../../../../common/types/field_stats';
|
||||
import { JOB_FIELD_TYPES } from '../../../../../common/constants';
|
||||
import { SUPPORTED_FIELD_TYPES } from '../../../../../common/constants';
|
||||
import { fetchDateFieldsStats } from './get_date_field_stats';
|
||||
import { fetchBooleanFieldsStats } from './get_boolean_field_stats';
|
||||
import { fetchFieldsExamples } from './get_field_examples';
|
||||
|
@ -31,16 +31,17 @@ export const getFieldsStats = (
|
|||
): Observable<FieldStats[] | FieldStatsError> | undefined => {
|
||||
const fieldType = fields[0].type;
|
||||
switch (fieldType) {
|
||||
case JOB_FIELD_TYPES.NUMBER:
|
||||
case SUPPORTED_FIELD_TYPES.NUMBER:
|
||||
return fetchNumericFieldsStats(dataSearch, params, fields, options);
|
||||
case JOB_FIELD_TYPES.KEYWORD:
|
||||
case JOB_FIELD_TYPES.IP:
|
||||
case SUPPORTED_FIELD_TYPES.KEYWORD:
|
||||
case SUPPORTED_FIELD_TYPES.IP:
|
||||
case SUPPORTED_FIELD_TYPES.VERSION:
|
||||
return fetchStringFieldsStats(dataSearch, params, fields, options);
|
||||
case JOB_FIELD_TYPES.DATE:
|
||||
case SUPPORTED_FIELD_TYPES.DATE:
|
||||
return fetchDateFieldsStats(dataSearch, params, fields, options);
|
||||
case JOB_FIELD_TYPES.BOOLEAN:
|
||||
case SUPPORTED_FIELD_TYPES.BOOLEAN:
|
||||
return fetchBooleanFieldsStats(dataSearch, params, fields, options);
|
||||
case JOB_FIELD_TYPES.TEXT:
|
||||
case SUPPORTED_FIELD_TYPES.TEXT:
|
||||
return fetchFieldsExamples(dataSearch, params, fields, options);
|
||||
default:
|
||||
// Use an exists filter on the the field name to get
|
||||
|
|
|
@ -10516,7 +10516,6 @@
|
|||
"xpack.dataVisualizer.dataGrid.field.topValues.calculatedFromSampleDescription": "Calculé à partir d'un échantillon de {topValuesSamplerShardSize} documents par partition",
|
||||
"xpack.dataVisualizer.dataGrid.field.topValuesLabel": "Valeurs les plus élevées",
|
||||
"xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.falseCountLabel": "faux",
|
||||
"xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.summaryTableTitle": "Résumé",
|
||||
"xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.trueCountLabel": "vrai",
|
||||
"xpack.dataVisualizer.dataGrid.fieldExpandedRow.choroplethMapTopValues.calculatedFromSampleDescription": "Calculé à partir d'un échantillon de {topValuesSamplerShardSize} documents par partition",
|
||||
"xpack.dataVisualizer.dataGrid.fieldExpandedRow.documentStatsTable.countLabel": "compte",
|
||||
|
|
|
@ -10508,7 +10508,6 @@
|
|||
"xpack.dataVisualizer.dataGrid.field.topValues.calculatedFromSampleDescription": "1 つのシャードにつき {topValuesSamplerShardSize} のドキュメントのサンプルで計算されています",
|
||||
"xpack.dataVisualizer.dataGrid.field.topValuesLabel": "トップの値",
|
||||
"xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.falseCountLabel": "false",
|
||||
"xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.summaryTableTitle": "まとめ",
|
||||
"xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.trueCountLabel": "true",
|
||||
"xpack.dataVisualizer.dataGrid.fieldExpandedRow.choroplethMapTopValues.calculatedFromSampleDescription": "1 つのシャードにつき {topValuesSamplerShardSize} のドキュメントのサンプルで計算されています",
|
||||
"xpack.dataVisualizer.dataGrid.fieldExpandedRow.documentStatsTable.countLabel": "カウント",
|
||||
|
|
|
@ -10523,7 +10523,6 @@
|
|||
"xpack.dataVisualizer.dataGrid.field.topValues.calculatedFromSampleDescription": "基于每个分片的 {topValuesSamplerShardSize} 文档样例计算",
|
||||
"xpack.dataVisualizer.dataGrid.field.topValuesLabel": "排名最前值",
|
||||
"xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.falseCountLabel": "false",
|
||||
"xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.summaryTableTitle": "摘要",
|
||||
"xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.trueCountLabel": "true",
|
||||
"xpack.dataVisualizer.dataGrid.fieldExpandedRow.choroplethMapTopValues.calculatedFromSampleDescription": "基于每个分片的 {topValuesSamplerShardSize} 文档样例计算",
|
||||
"xpack.dataVisualizer.dataGrid.fieldExpandedRow.documentStatsTable.countLabel": "计数",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue