mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[ML] Redesign file-based Data Visualizer (#87598)
This commit is contained in:
parent
86789dabb5
commit
27c77adf60
98 changed files with 1721 additions and 1199 deletions
|
@ -4,6 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ES_FIELD_TYPES } from '../../../../../src/plugins/data/common';
|
||||
|
||||
export interface InputOverrides {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
@ -29,15 +31,27 @@ export interface FindFileStructureResponse {
|
|||
count: number;
|
||||
cardinality: number;
|
||||
top_hits: Array<{ count: number; value: any }>;
|
||||
mean_value?: number;
|
||||
median_value?: number;
|
||||
max_value?: number;
|
||||
min_value?: number;
|
||||
earliest?: string;
|
||||
latest?: string;
|
||||
};
|
||||
};
|
||||
sample_start: string;
|
||||
num_messages_analyzed: number;
|
||||
mappings: {
|
||||
[fieldName: string]: {
|
||||
type: string;
|
||||
properties: {
|
||||
[fieldName: string]: {
|
||||
// including all possible Elasticsearch types
|
||||
// since find_file_structure API can be enhanced to include new fields in the future
|
||||
type: Exclude<
|
||||
ES_FIELD_TYPES,
|
||||
ES_FIELD_TYPES._ID | ES_FIELD_TYPES._INDEX | ES_FIELD_TYPES._SOURCE | ES_FIELD_TYPES._TYPE
|
||||
>;
|
||||
format?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
quote: string;
|
||||
|
|
|
@ -42,11 +42,7 @@ export interface MlGenericUrlPageState extends MlIndexBasedSearchState {
|
|||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface DataVisualizerIndexBasedAppState {
|
||||
pageIndex: number;
|
||||
pageSize: number;
|
||||
sortField: string;
|
||||
sortDirection: string;
|
||||
export interface DataVisualizerIndexBasedAppState extends Omit<ListingPageUrlState, 'queryText'> {
|
||||
searchString?: Query['query'];
|
||||
searchQuery?: Query['query'];
|
||||
searchQueryLanguage?: SearchQueryLanguage;
|
||||
|
@ -57,6 +53,13 @@ export interface DataVisualizerIndexBasedAppState {
|
|||
showAllFields?: boolean;
|
||||
showEmptyFields?: boolean;
|
||||
}
|
||||
|
||||
export interface DataVisualizerFileBasedAppState extends Omit<ListingPageUrlState, 'queryText'> {
|
||||
visibleFieldTypes?: string[];
|
||||
visibleFieldNames?: string[];
|
||||
showDistributions?: boolean;
|
||||
}
|
||||
|
||||
export type MlGenericUrlState = MLPageState<
|
||||
| typeof ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER
|
||||
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
.mlDataGridChart__legendBoolean {
|
||||
width: 100%;
|
||||
min-width: $euiButtonMinWidth;
|
||||
td { text-align: center }
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import { NON_AGGREGATABLE } from './common';
|
|||
|
||||
export const hoveredRow$ = new BehaviorSubject<any | null>(null);
|
||||
|
||||
const BAR_COLOR = euiPaletteColorBlind()[0];
|
||||
export const BAR_COLOR = euiPaletteColorBlind()[0];
|
||||
const BAR_COLOR_BLUR = euiPaletteColorBlind({ rotations: 2 })[10];
|
||||
const MAX_CHART_COLUMNS = 20;
|
||||
|
||||
|
|
|
@ -11,11 +11,15 @@ import { EuiText, EuiToolTip } from '@elastic/eui';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { FieldTypeIcon } from '../field_type_icon';
|
||||
import { FieldVisConfig } from '../../datavisualizer/index_based/common';
|
||||
import { getMLJobTypeAriaLabel } from '../../util/field_types_utils';
|
||||
import {
|
||||
FieldVisConfig,
|
||||
FileBasedFieldVisConfig,
|
||||
isIndexBasedFieldVisConfig,
|
||||
} from '../../datavisualizer/stats_table/types/field_vis_config';
|
||||
|
||||
interface Props {
|
||||
card: FieldVisConfig;
|
||||
card: FieldVisConfig | FileBasedFieldVisConfig;
|
||||
}
|
||||
|
||||
export const FieldTitleBar: FC<Props> = ({ card }) => {
|
||||
|
@ -30,13 +34,13 @@ export const FieldTitleBar: FC<Props> = ({ card }) => {
|
|||
|
||||
if (card.fieldName === undefined) {
|
||||
classNames.push('document_count');
|
||||
} else if (card.isUnsupportedType === true) {
|
||||
} else if (isIndexBasedFieldVisConfig(card) && card.isUnsupportedType === true) {
|
||||
classNames.push('type-other');
|
||||
} else {
|
||||
classNames.push(card.type);
|
||||
}
|
||||
|
||||
if (card.isUnsupportedType !== true) {
|
||||
if (isIndexBasedFieldVisConfig(card) && card.isUnsupportedType !== true) {
|
||||
// All the supported field types have aria labels.
|
||||
cardTitleAriaLabel.unshift(getMLJobTypeAriaLabel(card.type)!);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,10 @@
|
|||
import { Direction, EuiBasicTableProps, Pagination, PropertySort } from '@elastic/eui';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { ListingPageUrlState } from '../../../../../../../common/types/common';
|
||||
import { DataVisualizerIndexBasedAppState } from '../../../../../../../common/types/ml_url_generator';
|
||||
import {
|
||||
DataVisualizerFileBasedAppState,
|
||||
DataVisualizerIndexBasedAppState,
|
||||
} from '../../../../../../../common/types/ml_url_generator';
|
||||
|
||||
const PAGE_SIZE_OPTIONS = [10, 25, 50];
|
||||
|
||||
|
@ -38,7 +41,10 @@ interface UseTableSettingsReturnValue<T> {
|
|||
|
||||
export function useTableSettings<TypeOfItem>(
|
||||
items: TypeOfItem[],
|
||||
pageState: ListingPageUrlState | DataVisualizerIndexBasedAppState,
|
||||
pageState:
|
||||
| ListingPageUrlState
|
||||
| DataVisualizerIndexBasedAppState
|
||||
| DataVisualizerFileBasedAppState,
|
||||
updatePageState: (update: Partial<ListingPageUrlState>) => void
|
||||
): UseTableSettingsReturnValue<TypeOfItem> {
|
||||
const { pageIndex, pageSize, sortField, sortDirection } = pageState;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
@import 'file_based/index';
|
||||
@import 'index_based/index';
|
||||
@import 'stats_datagrid/index';
|
||||
@import 'stats_table/index';
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
@import 'file_datavisualizer_view/index';
|
||||
@import 'results_view/index';
|
||||
@import 'analysis_summary/index';
|
||||
@import 'fields_stats/index';
|
||||
@import 'about_panel/index';
|
||||
@import 'import_summary/index';
|
||||
@import 'experimental_badge/index';
|
||||
|
|
|
@ -4,45 +4,28 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiPanel } from '@elastic/eui';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types';
|
||||
|
||||
import { FieldVisConfig } from '../../common';
|
||||
import { FieldTitleBar } from '../../../../components/field_title_bar/index';
|
||||
import React from 'react';
|
||||
import {
|
||||
BooleanContent,
|
||||
DateContent,
|
||||
GeoPointContent,
|
||||
IpContent,
|
||||
KeywordContent,
|
||||
NotInDocsContent,
|
||||
NumberContent,
|
||||
OtherContent,
|
||||
TextContent,
|
||||
} from './content_types';
|
||||
import { LoadingIndicator } from './loading_indicator';
|
||||
NumberContent,
|
||||
} from '../../../stats_table/components/field_data_expanded_row';
|
||||
import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types';
|
||||
import type { FileBasedFieldVisConfig } from '../../../stats_table/types/field_vis_config';
|
||||
|
||||
export interface FieldDataCardProps {
|
||||
config: FieldVisConfig;
|
||||
}
|
||||
|
||||
export const FieldDataCard: FC<FieldDataCardProps> = ({ config }) => {
|
||||
const { fieldName, loading, type, existsInDocs } = config;
|
||||
export const FileBasedDataVisualizerExpandedRow = ({ item }: { item: FileBasedFieldVisConfig }) => {
|
||||
const config = item;
|
||||
const { type, fieldName } = config;
|
||||
|
||||
function getCardContent() {
|
||||
if (existsInDocs === false) {
|
||||
return <NotInDocsContent />;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case ML_JOB_FIELD_TYPES.NUMBER:
|
||||
if (fieldName !== undefined) {
|
||||
return <NumberContent config={config} />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return <NumberContent config={config} />;
|
||||
|
||||
case ML_JOB_FIELD_TYPES.BOOLEAN:
|
||||
return <BooleanContent config={config} />;
|
||||
|
@ -68,15 +51,11 @@ export const FieldDataCard: FC<FieldDataCardProps> = ({ config }) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<EuiPanel
|
||||
data-test-subj={`mlFieldDataCard ${fieldName} ${type}`}
|
||||
className="mlFieldDataCard"
|
||||
hasShadow={false}
|
||||
<div
|
||||
className="mlDataVisualizerFieldExpandedRow"
|
||||
data-test-subj={`mlDataVisualizerFieldExpandedRow-${fieldName}`}
|
||||
>
|
||||
<FieldTitleBar card={config} />
|
||||
<div className="mlFieldDataCard__content" data-test-subj="mlFieldDataCardContent">
|
||||
{loading === true ? <LoadingIndicator /> : getCardContent()}
|
||||
</div>
|
||||
</EuiPanel>
|
||||
{getCardContent()}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { FileBasedDataVisualizerExpandedRow } from './file_based_expanded_row';
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { FileBasedNumberContentPreview } from './number_content_preview';
|
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { FileBasedFieldVisConfig } from '../../../stats_table/types';
|
||||
|
||||
export const FileBasedNumberContentPreview = ({ config }: { config: FileBasedFieldVisConfig }) => {
|
||||
const stats = config.stats;
|
||||
if (
|
||||
stats === undefined ||
|
||||
stats.min === undefined ||
|
||||
stats.median === undefined ||
|
||||
stats.max === undefined
|
||||
)
|
||||
return null;
|
||||
return (
|
||||
<EuiFlexGroup direction={'column'} gutterSize={'xs'}>
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem>
|
||||
<b>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fileDatavisualizer.fieldStatsCard.minTitle"
|
||||
defaultMessage="min"
|
||||
/>
|
||||
</b>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<b>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fileDatavisualizer.fieldStatsCard.medianTitle"
|
||||
defaultMessage="median"
|
||||
/>
|
||||
</b>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<b>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fileDatavisualizer.fieldStatsCard.maxTitle"
|
||||
defaultMessage="max"
|
||||
/>
|
||||
</b>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem>{stats.min}</EuiFlexItem>
|
||||
<EuiFlexItem>{stats.median}</EuiFlexItem>
|
||||
<EuiFlexItem>{stats.max}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { FC, useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { MultiSelectPicker } from '../../../../components/multi_select_picker';
|
||||
import type {
|
||||
FileBasedFieldVisConfig,
|
||||
FileBasedUnknownFieldVisConfig,
|
||||
} from '../../../stats_table/types/field_vis_config';
|
||||
|
||||
interface Props {
|
||||
fields: Array<FileBasedFieldVisConfig | FileBasedUnknownFieldVisConfig>;
|
||||
setVisibleFieldNames(q: string[]): void;
|
||||
visibleFieldNames: string[];
|
||||
}
|
||||
|
||||
export const DataVisualizerFieldNamesFilter: FC<Props> = ({
|
||||
fields,
|
||||
setVisibleFieldNames,
|
||||
visibleFieldNames,
|
||||
}) => {
|
||||
const fieldNameTitle = useMemo(
|
||||
() =>
|
||||
i18n.translate('xpack.ml.dataVisualizer.fileBased.fieldNameSelect', {
|
||||
defaultMessage: 'Field name',
|
||||
}),
|
||||
[]
|
||||
);
|
||||
const options = useMemo(
|
||||
() => fields.filter((d) => d.fieldName !== undefined).map((d) => ({ value: d.fieldName! })),
|
||||
[fields]
|
||||
);
|
||||
|
||||
return (
|
||||
<MultiSelectPicker
|
||||
title={fieldNameTitle}
|
||||
options={options}
|
||||
onChange={setVisibleFieldNames}
|
||||
checkedOptions={visibleFieldNames}
|
||||
dataTestSubj={'mlDataVisualizerFieldNameSelect'}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { FieldDataCard, FieldDataCardProps } from './field_data_card';
|
||||
export { DataVisualizerFieldNamesFilter } from './field_names_filter';
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { FC, useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { MultiSelectPicker, Option } from '../../../../components/multi_select_picker';
|
||||
import type {
|
||||
FileBasedFieldVisConfig,
|
||||
FileBasedUnknownFieldVisConfig,
|
||||
} from '../../../stats_table/types/field_vis_config';
|
||||
import { FieldTypeIcon } from '../../../../components/field_type_icon';
|
||||
import { ML_JOB_FIELD_TYPES_OPTIONS } from '../../../index_based/components/search_panel/field_type_filter';
|
||||
|
||||
interface Props {
|
||||
fields: Array<FileBasedFieldVisConfig | FileBasedUnknownFieldVisConfig>;
|
||||
setVisibleFieldTypes(q: string[]): void;
|
||||
visibleFieldTypes: string[];
|
||||
}
|
||||
|
||||
export const DataVisualizerFieldTypesFilter: FC<Props> = ({
|
||||
fields,
|
||||
setVisibleFieldTypes,
|
||||
visibleFieldTypes,
|
||||
}) => {
|
||||
const fieldNameTitle = useMemo(
|
||||
() =>
|
||||
i18n.translate('xpack.ml.dataVisualizer.fileBased.fieldTypeSelect', {
|
||||
defaultMessage: 'Field type',
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const options = useMemo(() => {
|
||||
const fieldTypesTracker = new Set();
|
||||
const fieldTypes: Option[] = [];
|
||||
fields.forEach(({ type }) => {
|
||||
if (
|
||||
type !== undefined &&
|
||||
!fieldTypesTracker.has(type) &&
|
||||
ML_JOB_FIELD_TYPES_OPTIONS[type] !== undefined
|
||||
) {
|
||||
const item = ML_JOB_FIELD_TYPES_OPTIONS[type];
|
||||
|
||||
fieldTypesTracker.add(type);
|
||||
fieldTypes.push({
|
||||
value: type,
|
||||
name: (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={true}> {item.name}</EuiFlexItem>
|
||||
{type && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<FieldTypeIcon
|
||||
type={type}
|
||||
fieldName={item.name}
|
||||
tooltipEnabled={false}
|
||||
needsAria={true}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
});
|
||||
}
|
||||
});
|
||||
return fieldTypes;
|
||||
}, [fields]);
|
||||
return (
|
||||
<MultiSelectPicker
|
||||
title={fieldNameTitle}
|
||||
options={options}
|
||||
onChange={setVisibleFieldTypes}
|
||||
checkedOptions={visibleFieldTypes}
|
||||
dataTestSubj={'mlDataVisualizerFieldTypeSelect'}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { DataVisualizerFieldTypesFilter } from './field_types_filter';
|
|
@ -1,2 +0,0 @@
|
|||
@import 'fields_stats';
|
||||
@import 'field_stats_card';
|
|
@ -1,184 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiPanel,
|
||||
EuiProgress,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { FieldTypeIcon } from '../../../../components/field_type_icon';
|
||||
import { DisplayValue } from '../../../../components/display_value';
|
||||
import { getMLJobTypeAriaLabel } from '../../../../util/field_types_utils';
|
||||
|
||||
export function FieldStatsCard({ field }) {
|
||||
let type = field.type;
|
||||
if (type === 'double' || type === 'long') {
|
||||
type = 'number';
|
||||
}
|
||||
|
||||
const typeAriaLabel = getMLJobTypeAriaLabel(type);
|
||||
const cardTitleAriaLabel = [field.name];
|
||||
if (typeAriaLabel) {
|
||||
cardTitleAriaLabel.unshift(typeAriaLabel);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiPanel hasShadow={false} className="mlFieldDataCard">
|
||||
<div className="ml-field-data-card" data-test-subj="mlPageFileDataVisFieldDataCard">
|
||||
<div className={`ml-field-title-bar ${type}`}>
|
||||
<FieldTypeIcon type={type} needsAria={false} fieldName={field.name} />
|
||||
<div className="field-name" tabIndex="0" aria-label={`${cardTitleAriaLabel.join(', ')}`}>
|
||||
{field.name}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mlFieldDataCard__content">
|
||||
{field.count > 0 && (
|
||||
<React.Fragment>
|
||||
<div className="stats">
|
||||
<EuiFlexGroup
|
||||
gutterSize="xs"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
className="stat"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="document" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fileDatavisualizer.fieldStatsCard.documentsCountDescription"
|
||||
defaultMessage="{fieldCount, plural, zero {# document} one {# document} other {# documents}} ({fieldPercent}%)"
|
||||
values={{
|
||||
fieldCount: field.count,
|
||||
fieldPercent: field.percent,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
gutterSize="xs"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
className="stat"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="database" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fileDatavisualizer.fieldStatsCard.distinctCountDescription"
|
||||
defaultMessage="{fieldCardinality} distinct {fieldCardinality, plural, zero {value} one {value} other {values}}"
|
||||
values={{
|
||||
fieldCardinality: field.cardinality,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
{field.median_value && (
|
||||
<React.Fragment>
|
||||
<div>
|
||||
<div className="stat min heading">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fileDatavisualizer.fieldStatsCard.minTitle"
|
||||
defaultMessage="min"
|
||||
/>
|
||||
</div>
|
||||
<div className="stat median heading">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fileDatavisualizer.fieldStatsCard.medianTitle"
|
||||
defaultMessage="median"
|
||||
/>
|
||||
</div>
|
||||
<div className="stat max heading">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fileDatavisualizer.fieldStatsCard.maxTitle"
|
||||
defaultMessage="max"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="stat min value">
|
||||
<DisplayValue value={field.min_value} />
|
||||
</div>
|
||||
<div className="stat median value">
|
||||
<DisplayValue value={field.median_value} />
|
||||
</div>
|
||||
<div className="stat max value">
|
||||
<DisplayValue value={field.max_value} />
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{field.top_hits && (
|
||||
<React.Fragment>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<div className="stats">
|
||||
<div className="stat">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fileDatavisualizer.fieldStatsCard.topStatsValuesDescription"
|
||||
defaultMessage="top values"
|
||||
/>
|
||||
</div>
|
||||
{field.top_hits.map(({ count, value }) => {
|
||||
const pcnt = Math.round((count / field.count) * 100 * 100) / 100;
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="center" key={value}>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
style={{ width: 100 }}
|
||||
className="eui-textTruncate"
|
||||
>
|
||||
<EuiText size="xs" textAlign="right" color="subdued">
|
||||
{value}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiProgress value={count} max={field.count} color="primary" size="m" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
style={{ width: 70 }}
|
||||
className="eui-textTruncate"
|
||||
>
|
||||
<EuiText size="xs" textAlign="left" color="subdued">
|
||||
{pcnt}%
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
{field.count === 0 && (
|
||||
<div className="stats">
|
||||
<div className="stat">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fileDatavisualizer.fieldStatsCard.noFieldInformationAvailableDescription"
|
||||
defaultMessage="No field information available"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
|
@ -1,113 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { EuiFlexGrid, EuiFlexItem } from '@elastic/eui';
|
||||
import { FieldStatsCard } from './field_stats_card';
|
||||
import { getFieldNames } from './get_field_names';
|
||||
import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types';
|
||||
import { roundToDecimalPlace } from '../../../../formatters/round_to_decimal_place';
|
||||
|
||||
export class FieldsStats extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
fields: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({
|
||||
fields: createFields(this.props.results),
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="fields-stats">
|
||||
<EuiFlexGrid gutterSize="m">
|
||||
{this.state.fields.map((f) => (
|
||||
<EuiFlexItem key={f.name} style={{ width: '360px' }}>
|
||||
<FieldStatsCard field={f} />
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGrid>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function createFields(results) {
|
||||
const {
|
||||
mappings,
|
||||
field_stats: fieldStats,
|
||||
num_messages_analyzed: numMessagesAnalyzed,
|
||||
timestamp_field: timestampField,
|
||||
} = results;
|
||||
|
||||
if (mappings && mappings.properties && fieldStats) {
|
||||
const fieldNames = getFieldNames(results);
|
||||
|
||||
return fieldNames.map((name) => {
|
||||
if (fieldStats[name] !== undefined) {
|
||||
const field = { name };
|
||||
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 === undefined) {
|
||||
field.type = ML_JOB_FIELD_TYPES.DATE;
|
||||
}
|
||||
|
||||
if (f !== undefined) {
|
||||
Object.assign(field, f);
|
||||
}
|
||||
|
||||
if (m !== undefined) {
|
||||
field.type = m.type;
|
||||
if (m.format !== undefined) {
|
||||
field.format = m.format;
|
||||
}
|
||||
}
|
||||
|
||||
const percent = (field.count / numMessagesAnalyzed) * 100;
|
||||
field.percent = roundToDecimalPlace(percent);
|
||||
|
||||
// round min, max, median, mean to 2dp.
|
||||
if (field.median_value !== undefined) {
|
||||
field.median_value = roundToDecimalPlace(field.median_value);
|
||||
field.mean_value = roundToDecimalPlace(field.mean_value);
|
||||
field.min_value = roundToDecimalPlace(field.min_value);
|
||||
field.max_value = roundToDecimalPlace(field.max_value);
|
||||
}
|
||||
|
||||
return field;
|
||||
} else {
|
||||
// field is not in the field stats
|
||||
// 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 === ML_JOB_FIELD_TYPES.TEXT
|
||||
? ML_JOB_FIELD_TYPES.TEXT
|
||||
: ML_JOB_FIELD_TYPES.UNKNOWN;
|
||||
|
||||
return {
|
||||
name,
|
||||
type,
|
||||
mean_value: 0,
|
||||
count: 0,
|
||||
cardinality: 0,
|
||||
percent: 0,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { FindFileStructureResponse } from '../../../../../../common/types/file_datavisualizer';
|
||||
import { getFieldNames, getSupportedFieldType } from './get_field_names';
|
||||
import { FileBasedFieldVisConfig } from '../../../stats_table/types';
|
||||
import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types';
|
||||
import { roundToDecimalPlace } from '../../../../formatters/round_to_decimal_place';
|
||||
|
||||
export function createFields(results: FindFileStructureResponse) {
|
||||
const {
|
||||
mappings,
|
||||
field_stats: fieldStats,
|
||||
num_messages_analyzed: numMessagesAnalyzed,
|
||||
timestamp_field: timestampField,
|
||||
} = results;
|
||||
|
||||
let numericFieldsCount = 0;
|
||||
|
||||
if (mappings && mappings.properties && fieldStats) {
|
||||
const fieldNames = getFieldNames(results);
|
||||
|
||||
const items = fieldNames.map((name) => {
|
||||
if (fieldStats[name] !== undefined) {
|
||||
const field: FileBasedFieldVisConfig = {
|
||||
fieldName: name,
|
||||
type: ML_JOB_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 === ML_JOB_FIELD_TYPES.UNKNOWN) {
|
||||
field.type = ML_JOB_FIELD_TYPES.DATE;
|
||||
}
|
||||
|
||||
if (m !== undefined) {
|
||||
field.type = getSupportedFieldType(m.type);
|
||||
if (field.type === ML_JOB_FIELD_TYPES.NUMBER) {
|
||||
numericFieldsCount += 1;
|
||||
}
|
||||
if (m.format !== undefined) {
|
||||
field.format = m.format;
|
||||
}
|
||||
}
|
||||
|
||||
let _stats = {};
|
||||
|
||||
// round min, max, median, mean to 2dp.
|
||||
if (f.median_value !== undefined) {
|
||||
_stats = {
|
||||
..._stats,
|
||||
median: roundToDecimalPlace(f.median_value),
|
||||
mean: roundToDecimalPlace(f.mean_value),
|
||||
min: roundToDecimalPlace(f.min_value),
|
||||
max: roundToDecimalPlace(f.max_value),
|
||||
};
|
||||
}
|
||||
if (f.cardinality !== undefined) {
|
||||
_stats = {
|
||||
..._stats,
|
||||
cardinality: f.cardinality,
|
||||
count: f.count,
|
||||
sampleCount: numMessagesAnalyzed,
|
||||
};
|
||||
}
|
||||
|
||||
if (f.top_hits !== undefined) {
|
||||
if (field.type === ML_JOB_FIELD_TYPES.TEXT) {
|
||||
_stats = {
|
||||
..._stats,
|
||||
examples: f.top_hits.map((hit) => hit.value),
|
||||
};
|
||||
} else {
|
||||
_stats = {
|
||||
..._stats,
|
||||
topValues: f.top_hits.map((hit) => ({ key: hit.value, doc_count: hit.count })),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (field.type === ML_JOB_FIELD_TYPES.DATE) {
|
||||
_stats = {
|
||||
..._stats,
|
||||
earliest: f.earliest,
|
||||
latest: f.latest,
|
||||
};
|
||||
}
|
||||
|
||||
field.stats = _stats;
|
||||
return field;
|
||||
} else {
|
||||
// field is not in the field stats
|
||||
// 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 === ML_JOB_FIELD_TYPES.TEXT
|
||||
? ML_JOB_FIELD_TYPES.TEXT
|
||||
: ML_JOB_FIELD_TYPES.UNKNOWN;
|
||||
|
||||
return {
|
||||
fieldName: name,
|
||||
type,
|
||||
stats: {
|
||||
mean: 0,
|
||||
count: 0,
|
||||
sampleCount: numMessagesAnalyzed,
|
||||
cardinality: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
fields: items,
|
||||
totalFieldsCount: items.length,
|
||||
totalMetricFieldsCount: numericFieldsCount,
|
||||
};
|
||||
}
|
||||
|
||||
return { fields: [], totalFieldsCount: 0, totalMetricFieldsCount: 0 };
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useMemo, FC } from 'react';
|
||||
import { EuiFlexGroup, EuiSpacer } from '@elastic/eui';
|
||||
import type { FindFileStructureResponse } from '../../../../../../common/types/file_datavisualizer';
|
||||
import { DataVisualizerTable, ItemIdToExpandedRowMap } from '../../../stats_table';
|
||||
import type { FileBasedFieldVisConfig } from '../../../stats_table/types/field_vis_config';
|
||||
import { FileBasedDataVisualizerExpandedRow } from '../expanded_row';
|
||||
|
||||
import { DataVisualizerFieldNamesFilter } from '../field_names_filter';
|
||||
import { DataVisualizerFieldTypesFilter } from '../field_types_filter';
|
||||
import { createFields } from './create_fields';
|
||||
import { filterFields } from './filter_fields';
|
||||
import { usePageUrlState } from '../../../../util/url_state';
|
||||
import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator';
|
||||
import {
|
||||
MetricFieldsCount,
|
||||
TotalFieldsCount,
|
||||
} from '../../../stats_table/components/field_count_stats';
|
||||
import type { DataVisualizerFileBasedAppState } from '../../../../../../common/types/ml_url_generator';
|
||||
|
||||
interface Props {
|
||||
results: FindFileStructureResponse;
|
||||
}
|
||||
export const getDefaultDataVisualizerListState = (): Required<DataVisualizerFileBasedAppState> => ({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
sortField: 'fieldName',
|
||||
sortDirection: 'asc',
|
||||
visibleFieldTypes: [],
|
||||
visibleFieldNames: [],
|
||||
showDistributions: true,
|
||||
});
|
||||
|
||||
function getItemIdToExpandedRowMap(
|
||||
itemIds: string[],
|
||||
items: FileBasedFieldVisConfig[]
|
||||
): ItemIdToExpandedRowMap {
|
||||
return itemIds.reduce((m: ItemIdToExpandedRowMap, fieldName: string) => {
|
||||
const item = items.find((fieldVisConfig) => fieldVisConfig.fieldName === fieldName);
|
||||
if (item !== undefined) {
|
||||
m[fieldName] = <FileBasedDataVisualizerExpandedRow item={item} />;
|
||||
}
|
||||
return m;
|
||||
}, {} as ItemIdToExpandedRowMap);
|
||||
}
|
||||
|
||||
export const FieldsStatsGrid: FC<Props> = ({ results }) => {
|
||||
const restorableDefaults = getDefaultDataVisualizerListState();
|
||||
const [
|
||||
dataVisualizerListState,
|
||||
setDataVisualizerListState,
|
||||
] = usePageUrlState<DataVisualizerFileBasedAppState>(
|
||||
ML_PAGES.DATA_VISUALIZER_FILE,
|
||||
restorableDefaults
|
||||
);
|
||||
const visibleFieldTypes =
|
||||
dataVisualizerListState.visibleFieldTypes ?? restorableDefaults.visibleFieldTypes;
|
||||
const setVisibleFieldTypes = (values: string[]) => {
|
||||
setDataVisualizerListState({ ...dataVisualizerListState, visibleFieldTypes: values });
|
||||
};
|
||||
|
||||
const visibleFieldNames =
|
||||
dataVisualizerListState.visibleFieldNames ?? restorableDefaults.visibleFieldNames;
|
||||
const setVisibleFieldNames = (values: string[]) => {
|
||||
setDataVisualizerListState({ ...dataVisualizerListState, visibleFieldNames: values });
|
||||
};
|
||||
|
||||
const { fields, totalFieldsCount, totalMetricFieldsCount } = useMemo(
|
||||
() => createFields(results),
|
||||
[results, visibleFieldNames, visibleFieldTypes]
|
||||
);
|
||||
const { filteredFields, visibleFieldsCount, visibleMetricsCount } = useMemo(
|
||||
() => filterFields(fields, visibleFieldNames, visibleFieldTypes),
|
||||
[results, visibleFieldNames, visibleFieldTypes]
|
||||
);
|
||||
|
||||
const fieldsCountStats = { visibleFieldsCount, totalFieldsCount };
|
||||
const metricsStats = { visibleMetricsCount, totalMetricFieldsCount };
|
||||
|
||||
return (
|
||||
<div>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="xs"
|
||||
style={{ marginLeft: 4 }}
|
||||
data-test-subj="mlDataVisualizerFieldCountPanel"
|
||||
>
|
||||
<TotalFieldsCount fieldsCountStats={fieldsCountStats} />
|
||||
<MetricFieldsCount metricsStats={metricsStats} />
|
||||
|
||||
<EuiFlexGroup
|
||||
gutterSize="xs"
|
||||
data-test-subj="mlDataVisualizerFieldCountPanel"
|
||||
justifyContent={'flexEnd'}
|
||||
>
|
||||
<DataVisualizerFieldNamesFilter
|
||||
fields={fields}
|
||||
setVisibleFieldNames={setVisibleFieldNames}
|
||||
visibleFieldNames={visibleFieldNames}
|
||||
/>
|
||||
<DataVisualizerFieldTypesFilter
|
||||
fields={fields}
|
||||
setVisibleFieldTypes={setVisibleFieldTypes}
|
||||
visibleFieldTypes={visibleFieldTypes}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
<DataVisualizerTable<FileBasedFieldVisConfig>
|
||||
items={filteredFields}
|
||||
pageState={dataVisualizerListState}
|
||||
updatePageState={setDataVisualizerListState}
|
||||
getItemIdToExpandedRowMap={getItemIdToExpandedRowMap}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types';
|
||||
import type {
|
||||
FileBasedFieldVisConfig,
|
||||
FileBasedUnknownFieldVisConfig,
|
||||
} from '../../../stats_table/types/field_vis_config';
|
||||
|
||||
export function filterFields(
|
||||
fields: Array<FileBasedFieldVisConfig | FileBasedUnknownFieldVisConfig>,
|
||||
visibleFieldNames: string[],
|
||||
visibleFieldTypes: string[]
|
||||
) {
|
||||
let items = fields;
|
||||
|
||||
if (visibleFieldTypes && visibleFieldTypes.length > 0) {
|
||||
items = items.filter(
|
||||
(config) => visibleFieldTypes.findIndex((field) => field === config.type) > -1
|
||||
);
|
||||
}
|
||||
if (visibleFieldNames && visibleFieldNames.length > 0) {
|
||||
items = items.filter((config) => {
|
||||
return visibleFieldNames.findIndex((field) => field === config.fieldName) > -1;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
filteredFields: items,
|
||||
visibleFieldsCount: items.length,
|
||||
visibleMetricsCount: items.filter((d) => d.type === ML_JOB_FIELD_TYPES.NUMBER).length,
|
||||
};
|
||||
}
|
|
@ -5,8 +5,11 @@
|
|||
*/
|
||||
|
||||
import { difference } from 'lodash';
|
||||
|
||||
export function getFieldNames(results) {
|
||||
import type { FindFileStructureResponse } from '../../../../../../common/types/file_datavisualizer';
|
||||
import { MlJobFieldType } from '../../../../../../common/types/field_types';
|
||||
import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types';
|
||||
import { ES_FIELD_TYPES } from '../../../../../../../../../src/plugins/data/common';
|
||||
export function getFieldNames(results: FindFileStructureResponse) {
|
||||
const { mappings, field_stats: fieldStats, column_names: columnNames } = results;
|
||||
|
||||
// if columnNames exists (i.e delimited) use it for the field list
|
||||
|
@ -29,3 +32,24 @@ export function getFieldNames(results) {
|
|||
}
|
||||
return tempFields;
|
||||
}
|
||||
|
||||
export function getSupportedFieldType(type: string): MlJobFieldType {
|
||||
switch (type) {
|
||||
case ES_FIELD_TYPES.FLOAT:
|
||||
case ES_FIELD_TYPES.HALF_FLOAT:
|
||||
case ES_FIELD_TYPES.SCALED_FLOAT:
|
||||
case ES_FIELD_TYPES.DOUBLE:
|
||||
case ES_FIELD_TYPES.INTEGER:
|
||||
case ES_FIELD_TYPES.LONG:
|
||||
case ES_FIELD_TYPES.SHORT:
|
||||
case ES_FIELD_TYPES.UNSIGNED_LONG:
|
||||
return ML_JOB_FIELD_TYPES.NUMBER;
|
||||
|
||||
case ES_FIELD_TYPES.DATE:
|
||||
case ES_FIELD_TYPES.DATE_NANOS:
|
||||
return ML_JOB_FIELD_TYPES.DATE;
|
||||
|
||||
default:
|
||||
return type as MlJobFieldType;
|
||||
}
|
||||
}
|
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { FieldsStats } from './fields_stats';
|
||||
export { FieldsStatsGrid } from './fields_stats_grid';
|
|
@ -228,7 +228,6 @@ export class FileDataVisualizerView extends Component {
|
|||
};
|
||||
|
||||
setOverrides = (overrides) => {
|
||||
console.log('setOverrides', overrides);
|
||||
this.setState(
|
||||
{
|
||||
loading: true,
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import React, { FC } from 'react';
|
||||
|
@ -15,7 +14,6 @@ import {
|
|||
EuiPageBody,
|
||||
EuiPageContentHeader,
|
||||
EuiPanel,
|
||||
EuiTabbedContent,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
EuiFlexGroup,
|
||||
|
@ -25,8 +23,7 @@ import { FindFileStructureResponse } from '../../../../../../common/types/file_d
|
|||
|
||||
import { FileContents } from '../file_contents';
|
||||
import { AnalysisSummary } from '../analysis_summary';
|
||||
// @ts-ignore
|
||||
import { FieldsStats } from '../fields_stats';
|
||||
import { FieldsStatsGrid } from '../fields_stats_grid';
|
||||
|
||||
interface Props {
|
||||
data: string;
|
||||
|
@ -45,16 +42,6 @@ export const ResultsView: FC<Props> = ({
|
|||
showExplanationFlyout,
|
||||
disableButtons,
|
||||
}) => {
|
||||
const tabs = [
|
||||
{
|
||||
id: 'file-stats',
|
||||
name: i18n.translate('xpack.ml.fileDatavisualizer.resultsView.fileStatsTabName', {
|
||||
defaultMessage: 'File stats',
|
||||
}),
|
||||
content: <FieldsStats results={results} />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiPage data-test-subj="mlPageFileDataVisResults">
|
||||
<EuiPageBody>
|
||||
|
@ -103,7 +90,16 @@ export const ResultsView: FC<Props> = ({
|
|||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiPanel data-test-subj="mlFileDataVisFileStatsPanel">
|
||||
<EuiTabbedContent tabs={tabs} initialSelectedTab={tabs[0]} onTabClick={() => {}} />
|
||||
<EuiTitle size="s">
|
||||
<h2 data-test-subj="mlFileDataVisStatsTitle">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fileDatavisualizer.resultsView.fileStatsName"
|
||||
defaultMessage="File stats"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
|
||||
<FieldsStatsGrid results={results} />
|
||||
</EuiPanel>
|
||||
</div>
|
||||
</EuiPageBody>
|
||||
|
|
|
@ -1 +1 @@
|
|||
@import 'components/field_data_card/index';
|
||||
@import 'components/field_data_row/index';
|
||||
|
|
|
@ -4,5 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { FieldVisConfig } from './field_vis_config';
|
||||
export { FieldHistogramRequestConfig, FieldRequestConfig } from './request';
|
||||
|
|
|
@ -6,22 +6,23 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { FieldVisConfig } from '../index_based/common';
|
||||
import { FieldVisConfig } from '../../../stats_table/types';
|
||||
import {
|
||||
BooleanContent,
|
||||
DateContent,
|
||||
GeoPointContent,
|
||||
IpContent,
|
||||
KeywordContent,
|
||||
NotInDocsContent,
|
||||
NumberContent,
|
||||
OtherContent,
|
||||
TextContent,
|
||||
} from '../index_based/components/field_data_card/content_types';
|
||||
import { NumberContent } from './components/field_data_expanded_row/number_content';
|
||||
import { ML_JOB_FIELD_TYPES } from '../../../../common/constants/field_types';
|
||||
import { LoadingIndicator } from '../index_based/components/field_data_card/loading_indicator';
|
||||
} from '../../../stats_table/components/field_data_expanded_row';
|
||||
|
||||
export const DataVisualizerFieldExpandedRow = ({ item }: { item: FieldVisConfig }) => {
|
||||
import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types';
|
||||
import { LoadingIndicator } from '../field_data_row/loading_indicator';
|
||||
import { NotInDocsContent } from '../field_data_row/content_types';
|
||||
|
||||
export const IndexBasedDataVisualizerExpandedRow = ({ item }: { item: FieldVisConfig }) => {
|
||||
const config = item;
|
||||
const { loading, type, existsInDocs, fieldName } = config;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { IndexBasedDataVisualizerExpandedRow } from './expanded_row';
|
|
@ -4,19 +4,19 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiNotificationBadge, EuiSwitch, EuiText } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSwitch } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { FC } from 'react';
|
||||
import {
|
||||
MetricFieldsCount,
|
||||
TotalFieldsCount,
|
||||
} from '../../../stats_table/components/field_count_stats';
|
||||
import type {
|
||||
TotalFieldsCountProps,
|
||||
MetricFieldsCountProps,
|
||||
} from '../../../stats_table/components/field_count_stats';
|
||||
|
||||
interface Props {
|
||||
metricsStats?: {
|
||||
visibleMetricFields: number;
|
||||
totalMetricFields: number;
|
||||
};
|
||||
fieldsCountStats?: {
|
||||
visibleFieldsCount: number;
|
||||
totalFieldsCount: number;
|
||||
};
|
||||
interface Props extends TotalFieldsCountProps, MetricFieldsCountProps {
|
||||
showEmptyFields: boolean;
|
||||
toggleShowEmptyFields: () => void;
|
||||
}
|
||||
|
@ -33,83 +33,8 @@ export const FieldCountPanel: FC<Props> = ({
|
|||
style={{ marginLeft: 4 }}
|
||||
data-test-subj="mlDataVisualizerFieldCountPanel"
|
||||
>
|
||||
{fieldsCountStats && (
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
alignItems="center"
|
||||
style={{ maxWidth: 250 }}
|
||||
data-test-subj="mlDataVisualizerFieldsSummary"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.dataVisualizer.searchPanel.allFieldsLabel"
|
||||
defaultMessage="All fields"
|
||||
/>
|
||||
</h5>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiNotificationBadge
|
||||
color="subdued"
|
||||
size="m"
|
||||
data-test-subj="mlDataVisualizerVisibleFieldsCount"
|
||||
>
|
||||
<strong>{fieldsCountStats.visibleFieldsCount}</strong>
|
||||
</EuiNotificationBadge>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="subdued" size="s" data-test-subj="mlDataVisualizerTotalFieldsCount">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.dataVisualizer.searchPanel.ofFieldsTotal"
|
||||
defaultMessage="of {totalCount} total"
|
||||
values={{ totalCount: fieldsCountStats.totalFieldsCount }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
|
||||
{metricsStats && (
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
alignItems="center"
|
||||
style={{ maxWidth: 250 }}
|
||||
data-test-subj="mlDataVisualizerMetricFieldsSummary"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.dataVisualizer.searchPanel.numberFieldsLabel"
|
||||
defaultMessage="Number fields"
|
||||
/>
|
||||
</h5>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiNotificationBadge
|
||||
color="subdued"
|
||||
size="m"
|
||||
data-test-subj="mlDataVisualizerVisibleMetricFieldsCount"
|
||||
>
|
||||
<strong>{metricsStats.visibleMetricFields}</strong>
|
||||
</EuiNotificationBadge>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="subdued" size="s" data-test-subj="mlDataVisualizerMetricFieldsCount">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.dataVisualizer.searchPanel.ofFieldsTotal"
|
||||
defaultMessage="of {totalCount} total"
|
||||
values={{ totalCount: metricsStats.totalMetricFields }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
|
||||
<TotalFieldsCount fieldsCountStats={fieldsCountStats} />
|
||||
<MetricFieldsCount metricsStats={metricsStats} />
|
||||
<EuiFlexItem>
|
||||
<EuiSwitch
|
||||
data-test-subj="mlDataVisualizerShowEmptyFieldsSwitch"
|
||||
|
|
|
@ -1,84 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { Axis, BarSeries, Chart, Settings } from '@elastic/charts';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { FieldDataCardProps } from '../field_data_card';
|
||||
import { roundToDecimalPlace } from '../../../../../formatters/round_to_decimal_place';
|
||||
import { ExpandedRowFieldHeader } from '../../../../stats_datagrid/components/expanded_row_field_header';
|
||||
|
||||
function getPercentLabel(value: number): string {
|
||||
if (value === 0) {
|
||||
return '0%';
|
||||
}
|
||||
if (value >= 0.1) {
|
||||
return `${value}%`;
|
||||
} else {
|
||||
return '< 0.1%';
|
||||
}
|
||||
}
|
||||
|
||||
export const BooleanContent: FC<FieldDataCardProps> = ({ config }) => {
|
||||
const { stats } = config;
|
||||
if (stats === undefined) return null;
|
||||
const { count, trueCount, falseCount } = stats;
|
||||
if (count === undefined || trueCount === undefined || falseCount === undefined) return null;
|
||||
|
||||
return (
|
||||
<div className="mlFieldDataCard__stats">
|
||||
<ExpandedRowFieldHeader>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardBoolean.valuesLabel"
|
||||
defaultMessage="Values"
|
||||
/>
|
||||
</ExpandedRowFieldHeader>
|
||||
<EuiSpacer size="xs" />
|
||||
<Chart renderer="canvas" className="story-chart" size={{ height: 200 }}>
|
||||
<Axis id="bottom" position="bottom" showOverlappingTicks />
|
||||
<Settings
|
||||
showLegend={false}
|
||||
theme={{
|
||||
barSeriesStyle: {
|
||||
displayValue: {
|
||||
fill: '#000',
|
||||
fontSize: 12,
|
||||
fontStyle: 'normal',
|
||||
offsetX: 0,
|
||||
offsetY: -5,
|
||||
padding: 0,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<BarSeries
|
||||
id={config.fieldName || config.fieldFormat}
|
||||
data={[
|
||||
{ x: 'true', y: roundToDecimalPlace((trueCount / count) * 100) },
|
||||
{ x: 'false', y: roundToDecimalPlace((falseCount / count) * 100) },
|
||||
]}
|
||||
displayValueSettings={{
|
||||
hideClippedValue: true,
|
||||
isAlternatingValueLabel: true,
|
||||
valueFormatter: getPercentLabel,
|
||||
isValueContainedInElement: false,
|
||||
showValueLabel: true,
|
||||
}}
|
||||
color={['rgba(230, 194, 32, 0.5)', 'rgba(224, 187, 20, 0.71)']}
|
||||
splitSeriesAccessors={['x']}
|
||||
stackAccessors={['x']}
|
||||
xAccessor="x"
|
||||
xScaleType="ordinal"
|
||||
yAccessors={['y']}
|
||||
yScaleType="linear"
|
||||
/>
|
||||
</Chart>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,41 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import { FieldDataCardProps } from '../field_data_card';
|
||||
import { ExamplesList } from '../examples_list';
|
||||
|
||||
export const GeoPointContent: FC<FieldDataCardProps> = ({ config }) => {
|
||||
// TODO - adjust server-side query to get examples using:
|
||||
|
||||
// GET /filebeat-apache-2019.01.30/_search
|
||||
// {
|
||||
// "size":10,
|
||||
// "_source": false,
|
||||
// "docvalue_fields": ["source.geo.location"],
|
||||
// "query": {
|
||||
// "bool":{
|
||||
// "must":[
|
||||
// {
|
||||
// "exists":{
|
||||
// "field":"source.geo.location"
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
const { stats } = config;
|
||||
if (stats?.examples === undefined) return null;
|
||||
|
||||
return (
|
||||
<div className="mlFieldDataCard__stats">
|
||||
<ExamplesList examples={stats.examples} />
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,34 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { FieldDataCardProps } from '../field_data_card';
|
||||
import { TopValues } from '../top_values';
|
||||
import { ExpandedRowFieldHeader } from '../../../../stats_datagrid/components/expanded_row_field_header';
|
||||
|
||||
export const IpContent: FC<FieldDataCardProps> = ({ config }) => {
|
||||
const { stats, fieldFormat } = config;
|
||||
if (stats === undefined) return null;
|
||||
const { count, sampleCount, cardinality } = stats;
|
||||
if (count === undefined || sampleCount === undefined || cardinality === undefined) return null;
|
||||
|
||||
return (
|
||||
<div className="mlFieldDataCard__stats">
|
||||
<ExpandedRowFieldHeader>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardIp.topValuesLabel"
|
||||
defaultMessage="Top values"
|
||||
/>
|
||||
</ExpandedRowFieldHeader>
|
||||
<EuiSpacer size="xs" />
|
||||
<TopValues stats={stats} fieldFormat={fieldFormat} barColor="secondary" />
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,29 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { FieldDataCardProps } from '../field_data_card';
|
||||
import { TopValues } from '../top_values';
|
||||
import { ExpandedRowFieldHeader } from '../../../../stats_datagrid/components/expanded_row_field_header';
|
||||
|
||||
export const KeywordContent: FC<FieldDataCardProps> = ({ config }) => {
|
||||
const { stats, fieldFormat } = config;
|
||||
|
||||
return (
|
||||
<div className="mlFieldDataCard__stats">
|
||||
<ExpandedRowFieldHeader>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardKeyword.topValuesLabel"
|
||||
defaultMessage="Top values"
|
||||
/>
|
||||
</ExpandedRowFieldHeader>
|
||||
<EuiSpacer size="xs" />
|
||||
<TopValues stats={stats} fieldFormat={fieldFormat} barColor="secondary" />
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,200 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FC, Fragment, useEffect, useState } from 'react';
|
||||
import {
|
||||
EuiButtonGroup,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { FieldDataCardProps } from '../field_data_card';
|
||||
import { DisplayValue } from '../../../../../components/display_value';
|
||||
import { kibanaFieldFormat } from '../../../../../formatters/kibana_field_format';
|
||||
import { numberAsOrdinal } from '../../../../../formatters/number_as_ordinal';
|
||||
import { roundToDecimalPlace } from '../../../../../formatters/round_to_decimal_place';
|
||||
import {
|
||||
MetricDistributionChart,
|
||||
MetricDistributionChartData,
|
||||
buildChartDataFromStats,
|
||||
} from '../metric_distribution_chart';
|
||||
import { TopValues } from '../top_values';
|
||||
|
||||
const DETAILS_MODE = {
|
||||
DISTRIBUTION: 'distribution',
|
||||
TOP_VALUES: 'top_values',
|
||||
} as const;
|
||||
|
||||
type DetailsModeType = typeof DETAILS_MODE[keyof typeof DETAILS_MODE];
|
||||
|
||||
const METRIC_DISTRIBUTION_CHART_WIDTH = 325;
|
||||
const METRIC_DISTRIBUTION_CHART_HEIGHT = 210;
|
||||
const DEFAULT_TOP_VALUES_THRESHOLD = 100;
|
||||
|
||||
export const NumberContent: FC<FieldDataCardProps> = ({ config }) => {
|
||||
const { stats, fieldFormat } = config;
|
||||
|
||||
useEffect(() => {
|
||||
const chartData = buildChartDataFromStats(stats, METRIC_DISTRIBUTION_CHART_WIDTH);
|
||||
setDistributionChartData(chartData);
|
||||
}, []);
|
||||
const [detailsMode, setDetailsMode] = useState(
|
||||
stats?.cardinality ?? 0 <= DEFAULT_TOP_VALUES_THRESHOLD
|
||||
? DETAILS_MODE.TOP_VALUES
|
||||
: DETAILS_MODE.DISTRIBUTION
|
||||
);
|
||||
const defaultChartData: MetricDistributionChartData[] = [];
|
||||
const [distributionChartData, setDistributionChartData] = useState(defaultChartData);
|
||||
|
||||
if (stats === undefined) return null;
|
||||
const { count, sampleCount, cardinality, min, median, max, distribution } = stats;
|
||||
if (count === undefined || sampleCount === undefined) return null;
|
||||
|
||||
const docsPercent = roundToDecimalPlace((count / sampleCount) * 100);
|
||||
|
||||
const detailsOptions = [
|
||||
{
|
||||
id: DETAILS_MODE.TOP_VALUES,
|
||||
label: i18n.translate('xpack.ml.fieldDataCard.cardNumber.details.topValuesLabel', {
|
||||
defaultMessage: 'Top values',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: DETAILS_MODE.DISTRIBUTION,
|
||||
label: i18n.translate('xpack.ml.fieldDataCard.cardNumber.details.distributionOfValuesLabel', {
|
||||
defaultMessage: 'Distribution',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="mlFieldDataCard__stats">
|
||||
<div>
|
||||
<EuiText size="xs" color="subdued" data-test-subj="mlFieldDataCardDocCount">
|
||||
<EuiIcon type="document" />
|
||||
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardNumber.documentsCountDescription"
|
||||
defaultMessage="{count, plural, zero {# document} one {# document} other {# documents}} ({docsPercent}%)"
|
||||
values={{
|
||||
count,
|
||||
docsPercent,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</div>
|
||||
<EuiSpacer size="xs" />
|
||||
<div>
|
||||
<EuiText size="xs" color="subdued" data-test-subj="mlFieldDataCardCardinality">
|
||||
<EuiIcon type="database" />
|
||||
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardNumber.distinctCountDescription"
|
||||
defaultMessage="{cardinality} distinct {cardinality, plural, zero {value} one {value} other {values}}"
|
||||
values={{
|
||||
cardinality,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</div>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup gutterSize="xs" justifyContent="center">
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiText color="subdued" size="xs">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardNumber.minLabel"
|
||||
defaultMessage="min"
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiText color="subdued" size="xs">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardNumber.medianLabel"
|
||||
defaultMessage="median"
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiText color="subdued" size="xs">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardNumber.maxLabel"
|
||||
defaultMessage="max"
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup gutterSize="xs" justifyContent="center">
|
||||
<EuiFlexItem grow={1} className="eui-textTruncate" data-test-subj="mlFieldDataCardMin">
|
||||
<DisplayValue value={kibanaFieldFormat(min, fieldFormat)} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={1} className="eui-textTruncate" data-test-subj="mlFieldDataCardMedian">
|
||||
<DisplayValue value={kibanaFieldFormat(median, fieldFormat)} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={1} className="eui-textTruncate" data-test-subj="mlFieldDataCardMax">
|
||||
<DisplayValue value={kibanaFieldFormat(max, fieldFormat)} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiButtonGroup
|
||||
options={detailsOptions}
|
||||
idSelected={detailsMode}
|
||||
onChange={(optionId) => setDetailsMode(optionId as DetailsModeType)}
|
||||
legend={i18n.translate(
|
||||
'xpack.ml.fieldDataCard.cardNumber.selectMetricDetailsDisplayAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Select display option for metric details',
|
||||
}
|
||||
)}
|
||||
data-test-subj="mlFieldDataCardDetailsSelect"
|
||||
isFullWidth={true}
|
||||
buttonSize="compressed"
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
{distribution && detailsMode === DETAILS_MODE.DISTRIBUTION && (
|
||||
<Fragment>
|
||||
<EuiFlexGroup justifyContent="spaceAround" gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="xs">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardNumber.displayingPercentilesLabel"
|
||||
defaultMessage="Displaying {minPercent} - {maxPercent} percentiles"
|
||||
values={{
|
||||
minPercent: numberAsOrdinal(distribution.minPercentile),
|
||||
maxPercent: numberAsOrdinal(distribution.maxPercentile),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup justifyContent="center" gutterSize="xs">
|
||||
<EuiFlexItem grow={true}>
|
||||
<MetricDistributionChart
|
||||
width={METRIC_DISTRIBUTION_CHART_WIDTH}
|
||||
height={METRIC_DISTRIBUTION_CHART_HEIGHT}
|
||||
chartData={distributionChartData}
|
||||
fieldFormat={fieldFormat}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</Fragment>
|
||||
)}
|
||||
{detailsMode === DETAILS_MODE.TOP_VALUES && (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<TopValues stats={stats} fieldFormat={fieldFormat} barColor="secondary" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,83 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FC, Fragment } from 'react';
|
||||
import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { FieldDataCardProps } from '../field_data_card';
|
||||
import { roundToDecimalPlace } from '../../../../../formatters/round_to_decimal_place';
|
||||
import { ExamplesList } from '../examples_list';
|
||||
|
||||
export const OtherContent: FC<FieldDataCardProps> = ({ config }) => {
|
||||
const { stats, type, aggregatable } = config;
|
||||
if (stats === undefined) return null;
|
||||
|
||||
const { count, sampleCount, cardinality, examples } = stats;
|
||||
if (
|
||||
count === undefined ||
|
||||
sampleCount === undefined ||
|
||||
cardinality === undefined ||
|
||||
examples === undefined
|
||||
)
|
||||
return null;
|
||||
|
||||
const docsPercent = roundToDecimalPlace((count / sampleCount) * 100);
|
||||
|
||||
return (
|
||||
<div className="mlFieldDataCard__stats">
|
||||
<div>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardOther.cardTypeLabel"
|
||||
defaultMessage="{cardType} type"
|
||||
values={{
|
||||
cardType: type,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</div>
|
||||
{aggregatable === true && (
|
||||
<Fragment>
|
||||
<EuiSpacer size="s" />
|
||||
<div>
|
||||
<EuiText size="xs" color="subdued">
|
||||
<EuiIcon type="document" />
|
||||
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardOther.documentsCountDescription"
|
||||
defaultMessage="{count, plural, zero {# document} one {# document} other {# documents}} ({docsPercent}%)"
|
||||
values={{
|
||||
count,
|
||||
docsPercent,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</div>
|
||||
|
||||
<EuiSpacer size="xs" />
|
||||
|
||||
<div>
|
||||
<EuiText size="xs" color="subdued">
|
||||
<EuiIcon type="database" />
|
||||
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardOther.distinctCountDescription"
|
||||
defaultMessage="{cardinality} distinct {cardinality, plural, zero {value} one {value} other {values}}"
|
||||
values={{
|
||||
cardinality,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
<EuiSpacer size="m" />
|
||||
<ExamplesList examples={examples} />
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,62 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FC, Fragment } from 'react';
|
||||
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { FieldDataCardProps } from '../field_data_card';
|
||||
import { ExamplesList } from '../examples_list';
|
||||
|
||||
export const TextContent: FC<FieldDataCardProps> = ({ config }) => {
|
||||
const { stats } = config;
|
||||
if (stats === undefined) return null;
|
||||
|
||||
const { examples } = stats;
|
||||
if (examples === undefined) return null;
|
||||
|
||||
const numExamples = examples.length;
|
||||
|
||||
return (
|
||||
<div className="mlFieldDataCard__stats">
|
||||
{numExamples > 0 && <ExamplesList examples={examples} />}
|
||||
{numExamples === 0 && (
|
||||
<Fragment>
|
||||
<EuiSpacer size="xl" />
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.ml.fieldDataCard.cardText.noExamplesForFieldsTitle', {
|
||||
defaultMessage: 'No examples were obtained for this field',
|
||||
})}
|
||||
iconType="alert"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardText.fieldNotPresentDescription"
|
||||
defaultMessage="This field was not present in the {sourceParam} field of documents queried."
|
||||
values={{
|
||||
sourceParam: <span className="mlFieldDataCard__codeContent">_source</span>,
|
||||
}}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardText.fieldMayBePopulatedDescription"
|
||||
defaultMessage="It may be populated, for example, using a {copyToParam} parameter in the document mapping, or be pruned from the {sourceParam} field after indexing through the use of {includesParam} and {excludesParam} parameters."
|
||||
values={{
|
||||
copyToParam: <span className="mlFieldDataCard__codeContent">copy_to</span>,
|
||||
sourceParam: <span className="mlFieldDataCard__codeContent">_source</span>,
|
||||
includesParam: <span className="mlFieldDataCard__codeContent">includes</span>,
|
||||
excludesParam: <span className="mlFieldDataCard__codeContent">excludes</span>,
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,2 +1 @@
|
|||
@import 'field_data_card';
|
||||
@import 'top_values/top_values';
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import type { FieldDataCardProps } from '../field_data_card';
|
||||
import type { FieldDataRowProps } from '../../../../stats_table/types/field_data_row';
|
||||
import { DocumentCountChart, DocumentCountChartPoint } from '../document_count_chart';
|
||||
import { TotalCountHeader } from '../../total_count_header';
|
||||
|
||||
export interface Props extends FieldDataCardProps {
|
||||
export interface Props extends FieldDataRowProps {
|
||||
totalCount: number;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { DocumentCountContent } from './document_count_content';
|
||||
export { NotInDocsContent } from './not_in_docs_content';
|
|
@ -25,7 +25,6 @@ export interface DocumentCountChartPoint {
|
|||
|
||||
interface Props {
|
||||
width?: number;
|
||||
height?: number;
|
||||
chartPoints: DocumentCountChartPoint[];
|
||||
timeRangeEarliest: number;
|
||||
timeRangeLatest: number;
|
||||
|
@ -35,7 +34,6 @@ const SPEC_ID = 'document_count';
|
|||
|
||||
export const DocumentCountChart: FC<Props> = ({
|
||||
width,
|
||||
height,
|
||||
chartPoints,
|
||||
timeRangeEarliest,
|
||||
timeRangeLatest,
|
|
@ -9,7 +9,7 @@ import React, { FC } from 'react';
|
|||
import { EuiListGroup, EuiListGroupItem } from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ExpandedRowFieldHeader } from '../../../../stats_datagrid/components/expanded_row_field_header';
|
||||
import { ExpandedRowFieldHeader } from '../../../../stats_table/components/expanded_row_field_header';
|
||||
interface Props {
|
||||
examples: Array<string | object>;
|
||||
}
|
|
@ -157,8 +157,6 @@ export const SearchPanel: FC<Props> = ({
|
|||
setVisibleFieldTypes={setVisibleFieldTypes}
|
||||
visibleFieldTypes={visibleFieldTypes}
|
||||
/>
|
||||
|
||||
<EuiFlexItem grow={false} />
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -45,17 +45,23 @@ import { getToastNotifications } from '../../util/dependency_cache';
|
|||
import { usePageUrlState, useUrlState } from '../../util/url_state';
|
||||
import { ActionsPanel } from './components/actions_panel';
|
||||
import { SearchPanel } from './components/search_panel';
|
||||
import { DocumentCountContent } from './components/field_data_card/content_types/document_count_content';
|
||||
import { DataVisualizerDataGrid } from '../stats_datagrid';
|
||||
import { DocumentCountContent } from './components/field_data_row/content_types/document_count_content';
|
||||
import { DataVisualizerTable, ItemIdToExpandedRowMap } from '../stats_table';
|
||||
import { FieldCountPanel } from './components/field_count_panel';
|
||||
import { ML_PAGES } from '../../../../common/constants/ml_url_generator';
|
||||
import { DataLoader } from './data_loader';
|
||||
import type { FieldRequestConfig, FieldVisConfig } from './common';
|
||||
import type { FieldRequestConfig } from './common';
|
||||
import type { DataVisualizerIndexBasedAppState } from '../../../../common/types/ml_url_generator';
|
||||
import type { OverallStats } from '../../../../common/types/datavisualizer';
|
||||
import { MlJobFieldType } from '../../../../common/types/field_types';
|
||||
import { HelpMenu } from '../../components/help_menu';
|
||||
import { useMlKibana } from '../../contexts/kibana';
|
||||
import { IndexBasedDataVisualizerExpandedRow } from './components/expanded_row';
|
||||
import { FieldVisConfig } from '../stats_table/types';
|
||||
import type {
|
||||
MetricFieldsStats,
|
||||
TotalFieldsStats,
|
||||
} from '../stats_table/components/field_count_stats';
|
||||
|
||||
interface DataVisualizerPageState {
|
||||
overallStats: OverallStats;
|
||||
|
@ -106,6 +112,19 @@ export const getDefaultDataVisualizerListState = (): Required<DataVisualizerInde
|
|||
showEmptyFields: false,
|
||||
});
|
||||
|
||||
function getItemIdToExpandedRowMap(
|
||||
itemIds: string[],
|
||||
items: FieldVisConfig[]
|
||||
): ItemIdToExpandedRowMap {
|
||||
return itemIds.reduce((m: ItemIdToExpandedRowMap, fieldName: string) => {
|
||||
const item = items.find((fieldVisConfig) => fieldVisConfig.fieldName === fieldName);
|
||||
if (item !== undefined) {
|
||||
m[fieldName] = <IndexBasedDataVisualizerExpandedRow item={item} />;
|
||||
}
|
||||
return m;
|
||||
}, {} as ItemIdToExpandedRowMap);
|
||||
}
|
||||
|
||||
export const Page: FC = () => {
|
||||
const mlContext = useMlContext();
|
||||
const restorableDefaults = getDefaultDataVisualizerListState();
|
||||
|
@ -228,9 +247,7 @@ export const Page: FC = () => {
|
|||
const [documentCountStats, setDocumentCountStats] = useState(defaults.documentCountStats);
|
||||
const [metricConfigs, setMetricConfigs] = useState(defaults.metricConfigs);
|
||||
const [metricsLoaded, setMetricsLoaded] = useState(defaults.metricsLoaded);
|
||||
const [metricsStats, setMetricsStats] = useState<
|
||||
undefined | { visibleMetricFields: number; totalMetricFields: number }
|
||||
>();
|
||||
const [metricsStats, setMetricsStats] = useState<undefined | MetricFieldsStats>();
|
||||
|
||||
const [nonMetricConfigs, setNonMetricConfigs] = useState(defaults.nonMetricConfigs);
|
||||
const [nonMetricsLoaded, setNonMetricsLoaded] = useState(defaults.nonMetricsLoaded);
|
||||
|
@ -537,8 +554,8 @@ export const Page: FC = () => {
|
|||
});
|
||||
|
||||
setMetricsStats({
|
||||
totalMetricFields: allMetricFields.length,
|
||||
visibleMetricFields: metricFieldsToShow.length,
|
||||
totalMetricFieldsCount: allMetricFields.length,
|
||||
visibleMetricsCount: metricFieldsToShow.length,
|
||||
});
|
||||
setMetricConfigs(configs);
|
||||
}
|
||||
|
@ -642,7 +659,7 @@ export const Page: FC = () => {
|
|||
return combinedConfigs;
|
||||
}, [nonMetricConfigs, metricConfigs, visibleFieldTypes, visibleFieldNames]);
|
||||
|
||||
const fieldsCountStats = useMemo(() => {
|
||||
const fieldsCountStats: TotalFieldsStats | undefined = useMemo(() => {
|
||||
let _visibleFieldsCount = 0;
|
||||
let _totalFieldsCount = 0;
|
||||
Object.keys(overallStats).forEach((key) => {
|
||||
|
@ -736,10 +753,11 @@ export const Page: FC = () => {
|
|||
metricsStats={metricsStats}
|
||||
/>
|
||||
<EuiSpacer size={'m'} />
|
||||
<DataVisualizerDataGrid
|
||||
<DataVisualizerTable<FieldVisConfig>
|
||||
items={configs}
|
||||
pageState={dataVisualizerListState}
|
||||
updatePageState={setDataVisualizerListState}
|
||||
getItemIdToExpandedRowMap={getItemIdToExpandedRowMap}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import 'components/field_data_expanded_row/number_content';
|
||||
@import 'components/field_count_stats/index';
|
||||
|
||||
.mlDataVisualizerFieldExpandedRow {
|
||||
padding-left: $euiSize * 4;
|
||||
|
@ -35,6 +36,7 @@
|
|||
}
|
||||
}
|
||||
.mlDataVisualizerSummaryTable {
|
||||
max-width: 350px;
|
||||
.euiTableRow > .euiTableRowCell {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
@ -42,4 +44,7 @@
|
|||
display: none;
|
||||
}
|
||||
}
|
||||
.mlDataVisualizerSummaryTableWrapper {
|
||||
max-width: 350px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
.mlDataVisualizerFieldCountContainer {
|
||||
max-width: 300px;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { TotalFieldsCount, TotalFieldsCountProps, TotalFieldsStats } from './total_fields_count';
|
||||
export {
|
||||
MetricFieldsCount,
|
||||
MetricFieldsCountProps,
|
||||
MetricFieldsStats,
|
||||
} from './metric_fields_count';
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiNotificationBadge, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
export interface MetricFieldsStats {
|
||||
visibleMetricsCount: number;
|
||||
totalMetricFieldsCount: number;
|
||||
}
|
||||
export interface MetricFieldsCountProps {
|
||||
metricsStats?: MetricFieldsStats;
|
||||
}
|
||||
|
||||
export const MetricFieldsCount: FC<MetricFieldsCountProps> = ({ metricsStats }) => {
|
||||
if (
|
||||
!metricsStats ||
|
||||
metricsStats.visibleMetricsCount === undefined ||
|
||||
metricsStats.totalMetricFieldsCount === undefined
|
||||
)
|
||||
return null;
|
||||
return (
|
||||
<>
|
||||
{metricsStats && (
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
alignItems="center"
|
||||
className="mlDataVisualizerFieldCountContainer"
|
||||
data-test-subj="mlDataVisualizerMetricFieldsSummary"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.dataVisualizer.searchPanel.numberFieldsLabel"
|
||||
defaultMessage="Number fields"
|
||||
/>
|
||||
</h5>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiNotificationBadge
|
||||
color="subdued"
|
||||
size="m"
|
||||
data-test-subj="mlDataVisualizerVisibleMetricFieldsCount"
|
||||
>
|
||||
<strong>{metricsStats.visibleMetricsCount}</strong>
|
||||
</EuiNotificationBadge>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="subdued" size="s" data-test-subj="mlDataVisualizerMetricFieldsCount">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.dataVisualizer.searchPanel.ofFieldsTotal"
|
||||
defaultMessage="of {totalCount} total"
|
||||
values={{ totalCount: metricsStats.totalMetricFieldsCount }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiNotificationBadge, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
export interface TotalFieldsStats {
|
||||
visibleFieldsCount: number;
|
||||
totalFieldsCount: number;
|
||||
}
|
||||
|
||||
export interface TotalFieldsCountProps {
|
||||
fieldsCountStats?: TotalFieldsStats;
|
||||
}
|
||||
|
||||
export const TotalFieldsCount: FC<TotalFieldsCountProps> = ({ fieldsCountStats }) => {
|
||||
if (
|
||||
!fieldsCountStats ||
|
||||
fieldsCountStats.visibleFieldsCount === undefined ||
|
||||
fieldsCountStats.totalFieldsCount === undefined
|
||||
)
|
||||
return null;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
alignItems="center"
|
||||
className="mlDataVisualizerFieldCountContainer"
|
||||
data-test-subj="mlDataVisualizerFieldsSummary"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.dataVisualizer.searchPanel.allFieldsLabel"
|
||||
defaultMessage="All fields"
|
||||
/>
|
||||
</h5>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiNotificationBadge
|
||||
color="subdued"
|
||||
size="m"
|
||||
data-test-subj="mlDataVisualizerVisibleFieldsCount"
|
||||
>
|
||||
<strong>{fieldsCountStats.visibleFieldsCount}</strong>
|
||||
</EuiNotificationBadge>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="subdued" size="s" data-test-subj="mlDataVisualizerTotalFieldsCount">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.dataVisualizer.searchPanel.ofFieldsTotal"
|
||||
defaultMessage="of {totalCount} total"
|
||||
values={{ totalCount: fieldsCountStats.totalFieldsCount }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FC, ReactNode, useMemo } from 'react';
|
||||
import { EuiBasicTable, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import { Axis, BarSeries, Chart, Settings } from '@elastic/charts';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { FieldDataRowProps } from '../../types/field_data_row';
|
||||
import { ExpandedRowFieldHeader } from '../expanded_row_field_header';
|
||||
import { getTFPercentage } from '../../utils';
|
||||
import { roundToDecimalPlace } from '../../../../formatters/round_to_decimal_place';
|
||||
import { useDataVizChartTheme } from '../../hooks';
|
||||
import { DocumentStatsTable } from './document_stats';
|
||||
|
||||
function getPercentLabel(value: number): string {
|
||||
if (value === 0) {
|
||||
return '0%';
|
||||
}
|
||||
if (value >= 0.1) {
|
||||
return `${roundToDecimalPlace(value)}%`;
|
||||
} else {
|
||||
return '< 0.1%';
|
||||
}
|
||||
}
|
||||
|
||||
function getFormattedValue(value: number, totalCount: number): string {
|
||||
const percentage = (value / totalCount) * 100;
|
||||
return `${value} (${getPercentLabel(percentage)})`;
|
||||
}
|
||||
|
||||
const BOOLEAN_DISTRIBUTION_CHART_HEIGHT = 100;
|
||||
|
||||
export const BooleanContent: FC<FieldDataRowProps> = ({ config }) => {
|
||||
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.ml.fieldDataCardExpandedRow.booleanContent.trueCountLabel"
|
||||
defaultMessage="true"
|
||||
/>
|
||||
),
|
||||
value: getFormattedValue(trueCount, count),
|
||||
},
|
||||
{
|
||||
function: 'false',
|
||||
display: (
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCardExpandedRow.booleanContent.falseCountLabel"
|
||||
defaultMessage="false"
|
||||
/>
|
||||
),
|
||||
value: getFormattedValue(falseCount, count),
|
||||
},
|
||||
];
|
||||
const summaryTableColumns = [
|
||||
{
|
||||
name: '',
|
||||
render: (summaryItem: { display: ReactNode }) => summaryItem.display,
|
||||
width: '75px',
|
||||
},
|
||||
{
|
||||
field: 'value',
|
||||
name: '',
|
||||
render: (v: string) => <strong>{v}</strong>,
|
||||
},
|
||||
];
|
||||
|
||||
const summaryTableTitle = i18n.translate(
|
||||
'xpack.ml.fieldDataCardExpandedRow.booleanContent.summaryTableTitle',
|
||||
{
|
||||
defaultMessage: 'Summary',
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup data-test-subj={'mlDVBooleanContent'} gutterSize={'xl'}>
|
||||
<DocumentStatsTable config={config} />
|
||||
|
||||
<EuiFlexItem className={'mlDataVisualizerSummaryTableWrapper'}>
|
||||
<ExpandedRowFieldHeader>{summaryTableTitle}</ExpandedRowFieldHeader>
|
||||
<EuiBasicTable
|
||||
className={'mlDataVisualizerSummaryTable'}
|
||||
compressed
|
||||
items={summaryTableItems}
|
||||
columns={summaryTableColumns}
|
||||
tableCaption={summaryTableTitle}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<ExpandedRowFieldHeader>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardBoolean.valuesLabel"
|
||||
defaultMessage="Values"
|
||||
/>
|
||||
</ExpandedRowFieldHeader>
|
||||
<EuiSpacer size="xs" />
|
||||
<Chart renderer="canvas" size={{ height: BOOLEAN_DISTRIBUTION_CHART_HEIGHT }}>
|
||||
<Axis id="bottom" position="bottom" showOverlappingTicks />
|
||||
<Axis
|
||||
id="left2"
|
||||
title="Left axis"
|
||||
hide={true}
|
||||
tickFormat={(d: any) => getFormattedValue(d, count)}
|
||||
/>
|
||||
|
||||
<Settings showLegend={false} theme={theme} />
|
||||
<BarSeries
|
||||
id={config.fieldName || fieldFormat}
|
||||
data={[
|
||||
{
|
||||
x: 'true',
|
||||
count: formattedPercentages.trueCount,
|
||||
},
|
||||
{
|
||||
x: 'false',
|
||||
count: formattedPercentages.falseCount,
|
||||
},
|
||||
]}
|
||||
splitSeriesAccessors={['x']}
|
||||
stackAccessors={['x']}
|
||||
xAccessor="x"
|
||||
xScaleType="ordinal"
|
||||
yAccessors={['count']}
|
||||
yScaleType="linear"
|
||||
/>
|
||||
</Chart>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -5,14 +5,15 @@
|
|||
*/
|
||||
|
||||
import React, { FC, ReactNode } from 'react';
|
||||
import { EuiBasicTable } from '@elastic/eui';
|
||||
import { EuiBasicTable, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
// @ts-ignore
|
||||
import { formatDate } from '@elastic/eui/lib/services/format';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FieldDataCardProps } from '../field_data_card';
|
||||
import { ExpandedRowFieldHeader } from '../../../../stats_datagrid/components/expanded_row_field_header';
|
||||
import type { FieldDataRowProps } from '../../types/field_data_row';
|
||||
import { ExpandedRowFieldHeader } from '../expanded_row_field_header';
|
||||
import { DocumentStatsTable } from './document_stats';
|
||||
const TIME_FORMAT = 'MMM D YYYY, HH:mm:ss.SSS';
|
||||
interface SummaryTableItem {
|
||||
function: string;
|
||||
|
@ -20,7 +21,7 @@ interface SummaryTableItem {
|
|||
value: number | string | undefined | null;
|
||||
}
|
||||
|
||||
export const DateContent: FC<FieldDataCardProps> = ({ config }) => {
|
||||
export const DateContent: FC<FieldDataRowProps> = ({ config }) => {
|
||||
const { stats } = config;
|
||||
if (stats === undefined) return null;
|
||||
|
||||
|
@ -38,7 +39,7 @@ export const DateContent: FC<FieldDataCardProps> = ({ config }) => {
|
|||
defaultMessage="earliest"
|
||||
/>
|
||||
),
|
||||
value: formatDate(earliest, TIME_FORMAT),
|
||||
value: typeof earliest === 'string' ? earliest : formatDate(earliest, TIME_FORMAT),
|
||||
},
|
||||
{
|
||||
function: 'latest',
|
||||
|
@ -48,7 +49,7 @@ export const DateContent: FC<FieldDataCardProps> = ({ config }) => {
|
|||
defaultMessage="latest"
|
||||
/>
|
||||
),
|
||||
value: formatDate(latest, TIME_FORMAT),
|
||||
value: typeof latest === 'string' ? latest : formatDate(latest, TIME_FORMAT),
|
||||
},
|
||||
];
|
||||
const summaryTableColumns = [
|
||||
|
@ -65,17 +66,20 @@ export const DateContent: FC<FieldDataCardProps> = ({ config }) => {
|
|||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<ExpandedRowFieldHeader>{summaryTableTitle}</ExpandedRowFieldHeader>
|
||||
<EuiBasicTable<SummaryTableItem>
|
||||
className={'mlDataVisualizerSummaryTable'}
|
||||
data-test-subj={'mlDateSummaryTable'}
|
||||
compressed
|
||||
items={summaryTableItems}
|
||||
columns={summaryTableColumns}
|
||||
tableCaption={summaryTableTitle}
|
||||
tableLayout="auto"
|
||||
/>
|
||||
</>
|
||||
<EuiFlexGroup data-test-subj={'mlDVDateContent'} gutterSize={'xl'}>
|
||||
<DocumentStatsTable config={config} />
|
||||
<EuiFlexItem className={'mlDataVisualizerSummaryTableWrapper'}>
|
||||
<ExpandedRowFieldHeader>{summaryTableTitle}</ExpandedRowFieldHeader>
|
||||
<EuiBasicTable<SummaryTableItem>
|
||||
className={'mlDataVisualizerSummaryTable'}
|
||||
data-test-subj={'mlDateSummaryTable'}
|
||||
compressed
|
||||
items={summaryTableItems}
|
||||
columns={summaryTableColumns}
|
||||
tableCaption={summaryTableTitle}
|
||||
tableLayout="auto"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { FC, ReactNode } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiBasicTable, EuiFlexItem } from '@elastic/eui';
|
||||
import { ExpandedRowFieldHeader } from '../expanded_row_field_header';
|
||||
import { FieldDataRowProps } from '../../types';
|
||||
|
||||
const metaTableColumns = [
|
||||
{
|
||||
name: '',
|
||||
render: (metaItem: { display: ReactNode }) => metaItem.display,
|
||||
width: '75px',
|
||||
},
|
||||
{
|
||||
field: 'value',
|
||||
name: '',
|
||||
render: (v: string) => <strong>{v}</strong>,
|
||||
},
|
||||
];
|
||||
|
||||
const metaTableTitle = i18n.translate(
|
||||
'xpack.ml.fieldDataCardExpandedRow.documentStatsTable.metaTableTitle',
|
||||
{
|
||||
defaultMessage: 'Documents stats',
|
||||
}
|
||||
);
|
||||
|
||||
export const DocumentStatsTable: FC<FieldDataRowProps> = ({ config }) => {
|
||||
if (
|
||||
config?.stats === undefined ||
|
||||
config.stats.cardinality === undefined ||
|
||||
config.stats.count === undefined ||
|
||||
config.stats.sampleCount === undefined
|
||||
)
|
||||
return null;
|
||||
const { cardinality, count, sampleCount } = config.stats;
|
||||
const metaTableItems = [
|
||||
{
|
||||
function: 'count',
|
||||
display: (
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCardExpandedRow.documentStatsTable.countLabel"
|
||||
defaultMessage="count"
|
||||
/>
|
||||
),
|
||||
value: count,
|
||||
},
|
||||
{
|
||||
function: 'percentage',
|
||||
display: (
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCardExpandedRow.documentStatsTable.percentageLabel"
|
||||
defaultMessage="percentage"
|
||||
/>
|
||||
),
|
||||
value: `${(count / sampleCount) * 100}%`,
|
||||
},
|
||||
{
|
||||
function: 'distinctValues',
|
||||
display: (
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCardExpandedRow.documentStatsTable.distinctValueLabel"
|
||||
defaultMessage="distinct values"
|
||||
/>
|
||||
),
|
||||
value: cardinality,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiFlexItem
|
||||
data-test-subj={'mlDVDocumentStatsContent'}
|
||||
className={'mlDataVisualizerSummaryTableWrapper'}
|
||||
>
|
||||
<ExpandedRowFieldHeader>{metaTableTitle}</ExpandedRowFieldHeader>
|
||||
<EuiBasicTable
|
||||
className={'mlDataVisualizerSummaryTable'}
|
||||
compressed
|
||||
items={metaTableItems}
|
||||
columns={metaTableColumns}
|
||||
tableCaption={metaTableTitle}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import type { FieldDataRowProps } from '../../types/field_data_row';
|
||||
import { ExamplesList } from '../../../index_based/components/field_data_row/examples_list';
|
||||
import { DocumentStatsTable } from './document_stats';
|
||||
import { TopValues } from '../../../index_based/components/field_data_row/top_values';
|
||||
|
||||
export const GeoPointContent: FC<FieldDataRowProps> = ({ config }) => {
|
||||
const { stats } = config;
|
||||
if (stats === undefined || (stats?.examples === undefined && stats?.topValues === undefined))
|
||||
return null;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup data-test-subj={'mlDVGeoPointContent'} gutterSize={'xl'}>
|
||||
<DocumentStatsTable config={config} />
|
||||
{Array.isArray(stats.examples) && (
|
||||
<EuiFlexItem>
|
||||
<ExamplesList examples={stats.examples!} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{Array.isArray(stats.topValues) && (
|
||||
<EuiFlexItem>
|
||||
<TopValues stats={stats} barColor="secondary" />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -6,11 +6,9 @@
|
|||
|
||||
export { BooleanContent } from './boolean_content';
|
||||
export { DateContent } from './date_content';
|
||||
export { DocumentCountContent } from './document_count_content';
|
||||
export { GeoPointContent } from './geo_point_content';
|
||||
export { KeywordContent } from './keyword_content';
|
||||
export { IpContent } from './ip_content';
|
||||
export { NotInDocsContent } from './not_in_docs_content';
|
||||
export { NumberContent } from './number_content';
|
||||
export { OtherContent } from './other_content';
|
||||
export { TextContent } from './text_content';
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import type { FieldDataRowProps } from '../../types/field_data_row';
|
||||
import { TopValues } from '../../../index_based/components/field_data_row/top_values';
|
||||
import { ExpandedRowFieldHeader } from '../expanded_row_field_header';
|
||||
import { DocumentStatsTable } from './document_stats';
|
||||
|
||||
export const IpContent: FC<FieldDataRowProps> = ({ config }) => {
|
||||
const { stats } = config;
|
||||
if (stats === undefined) return null;
|
||||
const { count, sampleCount, cardinality } = stats;
|
||||
if (count === undefined || sampleCount === undefined || cardinality === undefined) return null;
|
||||
const fieldFormat = 'fieldFormat' in config ? config.fieldFormat : undefined;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize={'xl'}>
|
||||
<DocumentStatsTable config={config} />
|
||||
<EuiFlexItem>
|
||||
<ExpandedRowFieldHeader>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardIp.topValuesLabel"
|
||||
defaultMessage="Top values"
|
||||
/>
|
||||
</ExpandedRowFieldHeader>
|
||||
<TopValues stats={stats} fieldFormat={fieldFormat} barColor="secondary" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import type { FieldDataRowProps } from '../../types/field_data_row';
|
||||
import { TopValues } from '../../../index_based/components/field_data_row/top_values';
|
||||
import { ExpandedRowFieldHeader } from '../expanded_row_field_header';
|
||||
import { DocumentStatsTable } from './document_stats';
|
||||
|
||||
export const KeywordContent: FC<FieldDataRowProps> = ({ config }) => {
|
||||
const { stats } = config;
|
||||
const fieldFormat = 'fieldFormat' in config ? config.fieldFormat : undefined;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup data-test-subj={'mlDVKeywordContent'} gutterSize={'xl'}>
|
||||
<DocumentStatsTable config={config} />
|
||||
|
||||
<EuiFlexItem>
|
||||
<ExpandedRowFieldHeader>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardKeyword.topValuesLabel"
|
||||
defaultMessage="Top values"
|
||||
/>
|
||||
</ExpandedRowFieldHeader>
|
||||
<EuiSpacer size="xs" />
|
||||
<TopValues stats={stats} fieldFormat={fieldFormat} barColor="secondary" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -9,17 +9,17 @@ import { EuiBasicTable, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'
|
|||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { FieldDataCardProps } from '../../../index_based/components/field_data_card';
|
||||
import type { FieldDataRowProps } from '../../types/field_data_row';
|
||||
import { kibanaFieldFormat } from '../../../../formatters/kibana_field_format';
|
||||
import { numberAsOrdinal } from '../../../../formatters/number_as_ordinal';
|
||||
import {
|
||||
MetricDistributionChart,
|
||||
MetricDistributionChartData,
|
||||
buildChartDataFromStats,
|
||||
} from '../../../index_based/components/field_data_card/metric_distribution_chart';
|
||||
import { TopValues } from '../../../index_based/components/field_data_card/top_values';
|
||||
} from '../metric_distribution_chart';
|
||||
import { TopValues } from '../../../index_based/components/field_data_row/top_values';
|
||||
import { ExpandedRowFieldHeader } from '../expanded_row_field_header';
|
||||
import { DocumentStatsTable } from './document_stats';
|
||||
|
||||
const METRIC_DISTRIBUTION_CHART_WIDTH = 325;
|
||||
const METRIC_DISTRIBUTION_CHART_HEIGHT = 200;
|
||||
|
@ -30,8 +30,8 @@ interface SummaryTableItem {
|
|||
value: number | string | undefined | null;
|
||||
}
|
||||
|
||||
export const NumberContent: FC<FieldDataCardProps> = ({ config }) => {
|
||||
const { stats, fieldFormat } = config;
|
||||
export const NumberContent: FC<FieldDataRowProps> = ({ config }) => {
|
||||
const { stats } = config;
|
||||
|
||||
useEffect(() => {
|
||||
const chartData = buildChartDataFromStats(stats, METRIC_DISTRIBUTION_CHART_WIDTH);
|
||||
|
@ -43,6 +43,7 @@ export const NumberContent: FC<FieldDataCardProps> = ({ config }) => {
|
|||
|
||||
if (stats === undefined) return null;
|
||||
const { min, median, max, distribution } = stats;
|
||||
const fieldFormat = 'fieldFormat' in config ? config.fieldFormat : undefined;
|
||||
|
||||
const summaryTableItems = [
|
||||
{
|
||||
|
@ -96,8 +97,9 @@ export const NumberContent: FC<FieldDataCardProps> = ({ config }) => {
|
|||
}
|
||||
);
|
||||
return (
|
||||
<EuiFlexGroup direction={'row'} data-test-subj={'mlNumberSummaryTable'} gutterSize={'xl'}>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup data-test-subj={'mlDVNumberContent'} gutterSize={'xl'}>
|
||||
<DocumentStatsTable config={config} />
|
||||
<EuiFlexItem className={'mlDataVisualizerSummaryTableWrapper'}>
|
||||
<ExpandedRowFieldHeader>{summaryTableTitle}</ExpandedRowFieldHeader>
|
||||
<EuiBasicTable<SummaryTableItem>
|
||||
className={'mlDataVisualizerSummaryTable'}
|
||||
|
@ -105,8 +107,10 @@ export const NumberContent: FC<FieldDataCardProps> = ({ config }) => {
|
|||
items={summaryTableItems}
|
||||
columns={summaryTableColumns}
|
||||
tableCaption={summaryTableTitle}
|
||||
data-test-subj={'mlNumberSummaryTable'}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
{stats && (
|
||||
<EuiFlexItem data-test-subj={'mlTopValues'}>
|
||||
<EuiFlexItem grow={false}>
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { EuiFlexGroup } from '@elastic/eui';
|
||||
import type { FieldDataRowProps } from '../../types/field_data_row';
|
||||
import { ExamplesList } from '../../../index_based/components/field_data_row/examples_list';
|
||||
import { DocumentStatsTable } from './document_stats';
|
||||
|
||||
export const OtherContent: FC<FieldDataRowProps> = ({ config }) => {
|
||||
const { stats } = config;
|
||||
if (stats === undefined) return null;
|
||||
return (
|
||||
<EuiFlexGroup gutterSize={'xl'} data-test-subj={'mlDVOtherContent'}>
|
||||
<DocumentStatsTable config={config} />
|
||||
{Array.isArray(stats.examples) && <ExamplesList examples={stats.examples} />}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FC, Fragment } from 'react';
|
||||
import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import type { FieldDataRowProps } from '../../types/field_data_row';
|
||||
import { ExamplesList } from '../../../index_based/components/field_data_row/examples_list';
|
||||
|
||||
export const TextContent: FC<FieldDataRowProps> = ({ config }) => {
|
||||
const { stats } = config;
|
||||
if (stats === undefined) return null;
|
||||
|
||||
const { examples } = stats;
|
||||
if (examples === undefined) return null;
|
||||
|
||||
const numExamples = examples.length;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize={'xl'} data-test-subj={'mlDVTextContent'}>
|
||||
<EuiFlexItem>
|
||||
{numExamples > 0 && <ExamplesList examples={examples} />}
|
||||
{numExamples === 0 && (
|
||||
<Fragment>
|
||||
<EuiSpacer size="xl" />
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.ml.fieldDataCard.cardText.noExamplesForFieldsTitle', {
|
||||
defaultMessage: 'No examples were obtained for this field',
|
||||
})}
|
||||
iconType="alert"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardText.fieldNotPresentDescription"
|
||||
defaultMessage="This field was not present in the {sourceParam} field of documents queried."
|
||||
values={{
|
||||
sourceParam: <span className="mlFieldDataCard__codeContent">_source</span>,
|
||||
}}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fieldDataCard.cardText.fieldMayBePopulatedDescription"
|
||||
defaultMessage="It may be populated, for example, using a {copyToParam} parameter in the document mapping, or be pruned from the {sourceParam} field after indexing through the use of {includesParam} and {excludesParam} parameters."
|
||||
values={{
|
||||
copyToParam: <span className="mlFieldDataCard__codeContent">copy_to</span>,
|
||||
sourceParam: <span className="mlFieldDataCard__codeContent">_source</span>,
|
||||
includesParam: <span className="mlFieldDataCard__codeContent">includes</span>,
|
||||
excludesParam: <span className="mlFieldDataCard__codeContent">excludes</span>,
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
</Fragment>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { FC, useMemo } from 'react';
|
||||
import { EuiDataGridColumn } from '@elastic/eui';
|
||||
import { FieldDataRowProps } from '../../types';
|
||||
import { getTFPercentage } from '../../utils';
|
||||
import { ColumnChart } from '../../../../components/data_grid/column_chart';
|
||||
import { OrdinalChartData } from '../../../../components/data_grid/use_column_chart';
|
||||
|
||||
export const BooleanContentPreview: FC<FieldDataRowProps> = ({ config }) => {
|
||||
const chartData = useMemo(() => {
|
||||
const results = getTFPercentage(config);
|
||||
if (results) {
|
||||
const data = [
|
||||
{ key: 'true', key_as_string: 'true', doc_count: results.trueCount },
|
||||
{ key: 'false', key_as_string: 'false', doc_count: results.falseCount },
|
||||
];
|
||||
return { id: config.fieldName, cardinality: 2, data, type: 'boolean' } as OrdinalChartData;
|
||||
}
|
||||
}, [config]);
|
||||
if (!chartData || config.fieldName === undefined) return null;
|
||||
|
||||
const columnType: EuiDataGridColumn = {
|
||||
id: config.fieldName,
|
||||
schema: undefined,
|
||||
};
|
||||
const dataTestSubj = `mlDataGridChart-${config.fieldName}`;
|
||||
|
||||
return (
|
||||
<ColumnChart
|
||||
dataTestSubj={dataTestSubj}
|
||||
chartData={chartData}
|
||||
columnType={columnType}
|
||||
hideLabel={true}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -7,10 +7,10 @@
|
|||
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui';
|
||||
|
||||
import React from 'react';
|
||||
import { FieldDataCardProps } from '../../../index_based/components/field_data_card';
|
||||
import type { FieldDataRowProps } from '../../types/field_data_row';
|
||||
import { roundToDecimalPlace } from '../../../../formatters/round_to_decimal_place';
|
||||
|
||||
export const DocumentStat = ({ config }: FieldDataCardProps) => {
|
||||
export const DocumentStat = ({ config }: FieldDataRowProps) => {
|
||||
const { stats } = config;
|
||||
if (stats === undefined) return null;
|
||||
|
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { DataVisualizerDataGrid } from './stats_datagrid';
|
||||
export { BooleanContentPreview } from './boolean_content_preview';
|
|
@ -7,18 +7,22 @@
|
|||
import React, { FC, useEffect, useState } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import classNames from 'classnames';
|
||||
import { FieldDataCardProps } from '../../../index_based/components/field_data_card';
|
||||
import {
|
||||
MetricDistributionChart,
|
||||
MetricDistributionChartData,
|
||||
buildChartDataFromStats,
|
||||
} from '../../../index_based/components/field_data_card/metric_distribution_chart';
|
||||
} from '../metric_distribution_chart';
|
||||
import { formatSingleValue } from '../../../../formatters/format_value';
|
||||
import { FieldVisConfig } from '../../types';
|
||||
|
||||
const METRIC_DISTRIBUTION_CHART_WIDTH = 150;
|
||||
const METRIC_DISTRIBUTION_CHART_HEIGHT = 80;
|
||||
|
||||
export const NumberContentPreview: FC<FieldDataCardProps> = ({ config }) => {
|
||||
export interface NumberContentPreviewProps {
|
||||
config: FieldVisConfig;
|
||||
}
|
||||
|
||||
export const IndexBasedNumberContentPreview: FC<NumberContentPreviewProps> = ({ config }) => {
|
||||
const { stats, fieldFormat, fieldName } = config;
|
||||
const defaultChartData: MetricDistributionChartData[] = [];
|
||||
const [distributionChartData, setDistributionChartData] = useState(defaultChartData);
|
|
@ -6,12 +6,12 @@
|
|||
|
||||
import React, { FC } from 'react';
|
||||
import { EuiDataGridColumn } from '@elastic/eui';
|
||||
import { FieldDataCardProps } from '../../../index_based/components/field_data_card';
|
||||
import type { FieldDataRowProps } from '../../types/field_data_row';
|
||||
import { ColumnChart } from '../../../../components/data_grid/column_chart';
|
||||
import { ChartData } from '../../../../components/data_grid';
|
||||
import { OrdinalDataItem } from '../../../../components/data_grid/use_column_chart';
|
||||
|
||||
export const TopValuesPreview: FC<FieldDataCardProps> = ({ config }) => {
|
||||
export const TopValuesPreview: FC<FieldDataRowProps> = ({ config }) => {
|
||||
const { stats } = config;
|
||||
if (stats === undefined) return null;
|
||||
const { topValues, cardinality } = stats;
|
|
@ -19,13 +19,10 @@ import {
|
|||
TooltipValueFormatter,
|
||||
} from '@elastic/charts';
|
||||
|
||||
import darkTheme from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import lightTheme from '@elastic/eui/dist/eui_theme_light.json';
|
||||
|
||||
import { MetricDistributionChartTooltipHeader } from './metric_distribution_chart_tooltip_header';
|
||||
import { useUiSettings } from '../../../../../contexts/kibana/use_ui_settings_context';
|
||||
import { kibanaFieldFormat } from '../../../../../formatters/kibana_field_format';
|
||||
import type { ChartTooltipValue } from '../../../../../components/chart_tooltip/chart_tooltip_service';
|
||||
import { kibanaFieldFormat } from '../../../../formatters/kibana_field_format';
|
||||
import type { ChartTooltipValue } from '../../../../components/chart_tooltip/chart_tooltip_service';
|
||||
import { useDataVizChartTheme } from '../../hooks';
|
||||
|
||||
export interface MetricDistributionChartData {
|
||||
x: number;
|
||||
|
@ -59,9 +56,7 @@ export const MetricDistributionChart: FC<Props> = ({
|
|||
defaultMessage: 'distribution',
|
||||
});
|
||||
|
||||
const IS_DARK_THEME = useUiSettings().get('theme:darkMode');
|
||||
const themeName = IS_DARK_THEME ? darkTheme : lightTheme;
|
||||
const AREA_SERIES_COLOR = themeName.euiColorVis0;
|
||||
const theme = useDataVizChartTheme();
|
||||
|
||||
const headerFormatter: TooltipValueFormatter = (tooltipData: ChartTooltipValue) => {
|
||||
const xValue = tooltipData.value;
|
||||
|
@ -81,47 +76,7 @@ export const MetricDistributionChart: FC<Props> = ({
|
|||
return (
|
||||
<div data-test-subj="mlFieldDataMetricDistributionChart">
|
||||
<Chart size={{ width, height }}>
|
||||
<Settings
|
||||
theme={{
|
||||
axes: {
|
||||
tickLabel: {
|
||||
fontSize: parseInt(themeName.euiFontSizeXS, 10),
|
||||
fontFamily: themeName.euiFontFamily,
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
},
|
||||
background: { color: 'transparent' },
|
||||
chartMargins: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
chartPaddings: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 4,
|
||||
bottom: 0,
|
||||
},
|
||||
scales: { barsPadding: 0.1 },
|
||||
colors: {
|
||||
vizColors: [AREA_SERIES_COLOR],
|
||||
},
|
||||
areaSeriesStyle: {
|
||||
line: {
|
||||
strokeWidth: 1,
|
||||
visible: true,
|
||||
},
|
||||
point: {
|
||||
visible: false,
|
||||
radius: 0,
|
||||
opacity: 0,
|
||||
},
|
||||
area: { visible: true, opacity: 1 },
|
||||
},
|
||||
}}
|
||||
tooltip={{ headerFormatter }}
|
||||
/>
|
||||
<Settings theme={theme} tooltip={{ headerFormatter }} />
|
||||
<Axis
|
||||
id="bottom"
|
||||
position={Position.Bottom}
|
|
@ -9,7 +9,7 @@ import React, { FC } from 'react';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { MetricDistributionChartData } from './metric_distribution_chart';
|
||||
import { kibanaFieldFormat } from '../../../../../formatters/kibana_field_format';
|
||||
import { kibanaFieldFormat } from '../../../../formatters/kibana_field_format';
|
||||
|
||||
interface Props {
|
||||
chartPoint: MetricDistributionChartData | undefined;
|
|
@ -19,53 +19,56 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiTableComputedColumnType } from '@elastic/eui/src/components/basic_table/table_types';
|
||||
import { ML_JOB_FIELD_TYPES } from '../../../../common/constants/field_types';
|
||||
import { FieldTypeIcon } from '../../components/field_type_icon';
|
||||
import { FieldVisConfig } from '../index_based/common';
|
||||
import { DataVisualizerFieldExpandedRow } from './expanded_row';
|
||||
import { DocumentStat } from './components/field_data_row/document_stats';
|
||||
import { DistinctValues } from './components/field_data_row/distinct_values';
|
||||
import { NumberContentPreview } from './components/field_data_row/number_content_preview';
|
||||
import { DataVisualizerIndexBasedAppState } from '../../../../common/types/ml_url_generator';
|
||||
import { IndexBasedNumberContentPreview } from './components/field_data_row/number_content_preview';
|
||||
import {
|
||||
DataVisualizerFileBasedAppState,
|
||||
DataVisualizerIndexBasedAppState,
|
||||
} from '../../../../common/types/ml_url_generator';
|
||||
import { useTableSettings } from '../../data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings';
|
||||
import { TopValuesPreview } from './components/field_data_row/top_values_preview';
|
||||
import type { MlJobFieldType } from '../../../../common/types/field_types';
|
||||
const FIELD_NAME = 'fieldName';
|
||||
import {
|
||||
FieldVisConfig,
|
||||
FileBasedFieldVisConfig,
|
||||
isIndexBasedFieldVisConfig,
|
||||
} from './types/field_vis_config';
|
||||
import { FileBasedNumberContentPreview } from '../file_based/components/field_data_row';
|
||||
import { BooleanContentPreview } from './components/field_data_row';
|
||||
|
||||
interface DataVisualizerDataGrid {
|
||||
items: FieldVisConfig[];
|
||||
pageState: DataVisualizerIndexBasedAppState;
|
||||
updatePageState: (update: Partial<DataVisualizerIndexBasedAppState>) => void;
|
||||
}
|
||||
const FIELD_NAME = 'fieldName';
|
||||
|
||||
export type ItemIdToExpandedRowMap = Record<string, JSX.Element>;
|
||||
|
||||
function getItemIdToExpandedRowMap(
|
||||
itemIds: string[],
|
||||
items: FieldVisConfig[]
|
||||
): ItemIdToExpandedRowMap {
|
||||
return itemIds.reduce((m: ItemIdToExpandedRowMap, fieldName: string) => {
|
||||
const item = items.find((fieldVisConfig) => fieldVisConfig[FIELD_NAME] === fieldName);
|
||||
if (item !== undefined) {
|
||||
m[fieldName] = <DataVisualizerFieldExpandedRow item={item} />;
|
||||
}
|
||||
return m;
|
||||
}, {} as ItemIdToExpandedRowMap);
|
||||
type DataVisualizerTableItem = FieldVisConfig | FileBasedFieldVisConfig;
|
||||
interface DataVisualizerTableProps<T> {
|
||||
items: T[];
|
||||
pageState: DataVisualizerIndexBasedAppState | DataVisualizerFileBasedAppState;
|
||||
updatePageState: (
|
||||
update: Partial<DataVisualizerIndexBasedAppState | DataVisualizerFileBasedAppState>
|
||||
) => void;
|
||||
getItemIdToExpandedRowMap: (itemIds: string[], items: T[]) => ItemIdToExpandedRowMap;
|
||||
}
|
||||
|
||||
export const DataVisualizerDataGrid = ({
|
||||
export const DataVisualizerTable = <T extends DataVisualizerTableItem>({
|
||||
items,
|
||||
pageState,
|
||||
updatePageState,
|
||||
}: DataVisualizerDataGrid) => {
|
||||
getItemIdToExpandedRowMap,
|
||||
}: DataVisualizerTableProps<T>) => {
|
||||
const [expandedRowItemIds, setExpandedRowItemIds] = useState<string[]>([]);
|
||||
const [expandAll, toggleExpandAll] = useState<boolean>(false);
|
||||
|
||||
const { onTableChange, pagination, sorting } = useTableSettings<FieldVisConfig>(
|
||||
const { onTableChange, pagination, sorting } = useTableSettings<DataVisualizerTableItem>(
|
||||
items,
|
||||
pageState,
|
||||
updatePageState
|
||||
);
|
||||
const showDistributions: boolean = pageState.showDistributions ?? true;
|
||||
const showDistributions: boolean =
|
||||
('showDistributions' in pageState && pageState.showDistributions) ?? true;
|
||||
const toggleShowDistribution = () => {
|
||||
updatePageState({
|
||||
...pageState,
|
||||
|
@ -73,7 +76,7 @@ export const DataVisualizerDataGrid = ({
|
|||
});
|
||||
};
|
||||
|
||||
function toggleDetails(item: FieldVisConfig) {
|
||||
function toggleDetails(item: DataVisualizerTableItem) {
|
||||
if (item.fieldName === undefined) return;
|
||||
const index = expandedRowItemIds.indexOf(item.fieldName);
|
||||
if (index !== -1) {
|
||||
|
@ -87,7 +90,7 @@ export const DataVisualizerDataGrid = ({
|
|||
}
|
||||
|
||||
const columns = useMemo(() => {
|
||||
const expanderColumn: EuiTableComputedColumnType<FieldVisConfig> = {
|
||||
const expanderColumn: EuiTableComputedColumnType<DataVisualizerTableItem> = {
|
||||
name: (
|
||||
<EuiButtonIcon
|
||||
data-test-subj={`mlToggleDetailsForAllRowsButton ${expandAll ? 'expanded' : 'collapsed'}`}
|
||||
|
@ -107,7 +110,7 @@ export const DataVisualizerDataGrid = ({
|
|||
align: RIGHT_ALIGNMENT,
|
||||
width: '40px',
|
||||
isExpander: true,
|
||||
render: (item: FieldVisConfig) => {
|
||||
render: (item: DataVisualizerTableItem) => {
|
||||
if (item.fieldName === undefined) return null;
|
||||
const direction = expandedRowItemIds.includes(item.fieldName) ? 'arrowUp' : 'arrowDown';
|
||||
return (
|
||||
|
@ -167,8 +170,10 @@ export const DataVisualizerDataGrid = ({
|
|||
name: i18n.translate('xpack.ml.datavisualizer.dataGrid.documentsCountColumnName', {
|
||||
defaultMessage: 'Documents (%)',
|
||||
}),
|
||||
render: (value: number | undefined, item: FieldVisConfig) => <DocumentStat config={item} />,
|
||||
sortable: (item: FieldVisConfig) => item?.stats?.count,
|
||||
render: (value: number | undefined, item: DataVisualizerTableItem) => (
|
||||
<DocumentStat config={item} />
|
||||
),
|
||||
sortable: (item: DataVisualizerTableItem) => item?.stats?.count,
|
||||
align: LEFT_ALIGNMENT as HorizontalAlignment,
|
||||
'data-test-subj': 'mlDataVisualizerTableColumnDocumentsCount',
|
||||
},
|
||||
|
@ -203,15 +208,27 @@ export const DataVisualizerDataGrid = ({
|
|||
/>
|
||||
</div>
|
||||
),
|
||||
render: (item: FieldVisConfig) => {
|
||||
render: (item: DataVisualizerTableItem) => {
|
||||
if (item === undefined || showDistributions === false) return null;
|
||||
if (item.type === 'keyword' && item.stats?.topValues !== undefined) {
|
||||
if (
|
||||
(item.type === ML_JOB_FIELD_TYPES.KEYWORD || item.type === ML_JOB_FIELD_TYPES.IP) &&
|
||||
item.stats?.topValues !== undefined
|
||||
) {
|
||||
return <TopValuesPreview config={item} />;
|
||||
}
|
||||
|
||||
if (item.type === 'number' && item.stats?.distribution !== undefined) {
|
||||
return <NumberContentPreview config={item} />;
|
||||
if (item.type === ML_JOB_FIELD_TYPES.NUMBER) {
|
||||
if (isIndexBasedFieldVisConfig(item) && item.stats?.distribution !== undefined) {
|
||||
return <IndexBasedNumberContentPreview config={item} />;
|
||||
} else {
|
||||
return <FileBasedNumberContentPreview config={item} />;
|
||||
}
|
||||
}
|
||||
|
||||
if (item.type === ML_JOB_FIELD_TYPES.BOOLEAN) {
|
||||
return <BooleanContentPreview config={item} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
align: LEFT_ALIGNMENT as HorizontalAlignment,
|
||||
|
@ -230,7 +247,7 @@ export const DataVisualizerDataGrid = ({
|
|||
|
||||
return (
|
||||
<EuiFlexItem data-test-subj="mlDataVisualizerTableContainer">
|
||||
<EuiInMemoryTable<FieldVisConfig>
|
||||
<EuiInMemoryTable<DataVisualizerTableItem>
|
||||
className={'mlDataVisualizer'}
|
||||
items={items}
|
||||
itemId={FIELD_NAME}
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { useDataVizChartTheme } from './use_data_viz_chart_theme';
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import type { PartialTheme } from '@elastic/charts';
|
||||
import { useMemo } from 'react';
|
||||
import { useCurrentEuiTheme } from '../../../components/color_range_legend';
|
||||
export const useDataVizChartTheme = (): PartialTheme => {
|
||||
const { euiTheme } = useCurrentEuiTheme();
|
||||
const chartTheme = useMemo(() => {
|
||||
const AREA_SERIES_COLOR = euiTheme.euiColorVis0;
|
||||
return {
|
||||
axes: {
|
||||
tickLabel: {
|
||||
fontSize: parseInt(euiTheme.euiFontSizeXS, 10),
|
||||
fontFamily: euiTheme.euiFontFamily,
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
},
|
||||
background: { color: 'transparent' },
|
||||
chartMargins: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
chartPaddings: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 4,
|
||||
bottom: 0,
|
||||
},
|
||||
scales: { barsPadding: 0.1 },
|
||||
colors: {
|
||||
vizColors: [AREA_SERIES_COLOR],
|
||||
},
|
||||
areaSeriesStyle: {
|
||||
line: {
|
||||
strokeWidth: 1,
|
||||
visible: true,
|
||||
},
|
||||
point: {
|
||||
visible: false,
|
||||
radius: 0,
|
||||
opacity: 0,
|
||||
},
|
||||
area: { visible: true, opacity: 1 },
|
||||
},
|
||||
};
|
||||
}, [euiTheme]);
|
||||
return chartTheme;
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { DataVisualizerTable, ItemIdToExpandedRowMap } from './data_visualizer_stats_table';
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import type { FieldVisConfig, FileBasedFieldVisConfig } from './field_vis_config';
|
||||
|
||||
export interface FieldDataRowProps {
|
||||
config: FieldVisConfig | FileBasedFieldVisConfig;
|
||||
}
|
|
@ -50,7 +50,7 @@ export interface FieldVisStats {
|
|||
max?: number;
|
||||
median?: number;
|
||||
min?: number;
|
||||
topValues?: Array<{ key: number; doc_count: number }>;
|
||||
topValues?: Array<{ key: number | string; doc_count: number }>;
|
||||
topValuesSampleSize?: number;
|
||||
topValuesSamplerShardSize?: number;
|
||||
examples?: Array<string | object>;
|
||||
|
@ -70,3 +70,28 @@ export interface FieldVisConfig {
|
|||
fieldFormat?: any;
|
||||
isUnsupportedType?: boolean;
|
||||
}
|
||||
|
||||
export interface FileBasedFieldVisConfig {
|
||||
type: MlJobFieldType;
|
||||
fieldName?: string;
|
||||
stats?: FieldVisStats;
|
||||
format?: string;
|
||||
}
|
||||
|
||||
export interface FileBasedUnknownFieldVisConfig {
|
||||
fieldName: string;
|
||||
type: 'text' | 'unknown';
|
||||
stats: { mean: number; count: number; sampleCount: number; cardinality: number };
|
||||
}
|
||||
|
||||
export function isFileBasedFieldVisConfig(
|
||||
field: FieldVisConfig | FileBasedFieldVisConfig
|
||||
): field is FileBasedFieldVisConfig {
|
||||
return !field.hasOwnProperty('existsInDocs');
|
||||
}
|
||||
|
||||
export function isIndexBasedFieldVisConfig(
|
||||
field: FieldVisConfig | FileBasedFieldVisConfig
|
||||
): field is FieldVisConfig {
|
||||
return field.hasOwnProperty('existsInDocs');
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { FieldDataRowProps } from './field_data_row';
|
||||
export {
|
||||
FieldVisConfig,
|
||||
FileBasedFieldVisConfig,
|
||||
FieldVisStats,
|
||||
MetricFieldVisStats,
|
||||
isFileBasedFieldVisConfig,
|
||||
isIndexBasedFieldVisConfig,
|
||||
} from './field_vis_config';
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { FileBasedFieldVisConfig } from './types';
|
||||
|
||||
export const getTFPercentage = (config: FileBasedFieldVisConfig) => {
|
||||
const { stats } = config;
|
||||
if (stats === undefined) return null;
|
||||
const { count } = stats;
|
||||
// use stats from index based config
|
||||
let { trueCount, falseCount } = stats;
|
||||
|
||||
// use stats from file based find structure results
|
||||
if (stats.trueCount === undefined || stats.falseCount === undefined) {
|
||||
if (config?.stats?.topValues) {
|
||||
config.stats.topValues.forEach((doc) => {
|
||||
if (doc.doc_count !== undefined) {
|
||||
if (doc.key.toString().toLowerCase() === 'false') {
|
||||
falseCount = doc.doc_count;
|
||||
}
|
||||
if (doc.key.toString().toLowerCase() === 'true') {
|
||||
trueCount = doc.doc_count;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (count === undefined || trueCount === undefined || falseCount === undefined) return null;
|
||||
return {
|
||||
count,
|
||||
trueCount,
|
||||
falseCount,
|
||||
};
|
||||
};
|
|
@ -4,7 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export function roundToDecimalPlace(num: number, dp: number = 2): number | string {
|
||||
export function roundToDecimalPlace(num?: number, dp: number = 2): number | string {
|
||||
if (num === undefined) return '';
|
||||
if (num % 1 === 0) {
|
||||
// no decimal place
|
||||
return num;
|
||||
|
|
|
@ -13126,18 +13126,6 @@
|
|||
"xpack.ml.fieldDataCard.cardDate.summaryTableTitle": "まとめ",
|
||||
"xpack.ml.fieldDataCard.cardIp.topValuesLabel": "トップの値",
|
||||
"xpack.ml.fieldDataCard.cardKeyword.topValuesLabel": "トップの値",
|
||||
"xpack.ml.fieldDataCard.cardNumber.details.distributionOfValuesLabel": "分布",
|
||||
"xpack.ml.fieldDataCard.cardNumber.details.topValuesLabel": "トップの値",
|
||||
"xpack.ml.fieldDataCard.cardNumber.displayingPercentilesLabel": "{minPercent} - {maxPercent} パーセンタイルを表示中",
|
||||
"xpack.ml.fieldDataCard.cardNumber.distinctCountDescription": "{cardinality} 個の特徴的な {cardinality, plural, other {値}}",
|
||||
"xpack.ml.fieldDataCard.cardNumber.documentsCountDescription": "{count, plural, other {# 個のドキュメント}} ({docsPercent}%)",
|
||||
"xpack.ml.fieldDataCard.cardNumber.maxLabel": "最高",
|
||||
"xpack.ml.fieldDataCard.cardNumber.medianLabel": "中間",
|
||||
"xpack.ml.fieldDataCard.cardNumber.minLabel": "分",
|
||||
"xpack.ml.fieldDataCard.cardNumber.selectMetricDetailsDisplayAriaLabel": "メトリック詳細の表示オプションを選択してください",
|
||||
"xpack.ml.fieldDataCard.cardOther.cardTypeLabel": "{cardType} タイプ",
|
||||
"xpack.ml.fieldDataCard.cardOther.distinctCountDescription": "{cardinality} 個の特徴的な {cardinality, plural, other {値}}",
|
||||
"xpack.ml.fieldDataCard.cardOther.documentsCountDescription": "{count, plural, other {# 個のドキュメント}} ({docsPercent}%)",
|
||||
"xpack.ml.fieldDataCard.cardText.fieldMayBePopulatedDescription": "たとえば、ドキュメントマッピングで {copyToParam} パラメーターを使ったり、{includesParam} と {excludesParam} パラメーターを使用してインデックスした後に {sourceParam} フィールドから切り取ったりして入力される場合があります。",
|
||||
"xpack.ml.fieldDataCard.cardText.fieldNotPresentDescription": "このフィールドはクエリが実行されたドキュメントの {sourceParam} フィールドにありませんでした。",
|
||||
"xpack.ml.fieldDataCard.cardText.noExamplesForFieldsTitle": "このフィールドの例が取得されませんでした",
|
||||
|
@ -13221,13 +13209,9 @@
|
|||
"xpack.ml.fileDatavisualizer.explanationFlyout.closeButton": "閉じる",
|
||||
"xpack.ml.fileDatavisualizer.explanationFlyout.content": "分析結果を生成した論理ステップ。",
|
||||
"xpack.ml.fileDatavisualizer.explanationFlyout.title": "分析説明",
|
||||
"xpack.ml.fileDatavisualizer.fieldStatsCard.distinctCountDescription": "{fieldCardinality} 個の特徴的な {fieldCardinality, plural, other {値}}",
|
||||
"xpack.ml.fileDatavisualizer.fieldStatsCard.documentsCountDescription": "{fieldCount, plural, other {# 個のドキュメント}} ({fieldPercent}%)",
|
||||
"xpack.ml.fileDatavisualizer.fieldStatsCard.maxTitle": "最高",
|
||||
"xpack.ml.fileDatavisualizer.fieldStatsCard.medianTitle": "中間",
|
||||
"xpack.ml.fileDatavisualizer.fieldStatsCard.minTitle": "分",
|
||||
"xpack.ml.fileDatavisualizer.fieldStatsCard.noFieldInformationAvailableDescription": "フィールド情報がありません",
|
||||
"xpack.ml.fileDatavisualizer.fieldStatsCard.topStatsValuesDescription": "トップの値",
|
||||
"xpack.ml.fileDatavisualizer.fileBeatConfig.paths": "ファイルのパスをここに追加してください",
|
||||
"xpack.ml.fileDatavisualizer.fileBeatConfigFlyout.closeButton": "閉じる",
|
||||
"xpack.ml.fileDatavisualizer.fileBeatConfigFlyout.copyButton": "クリップボードにコピー",
|
||||
|
@ -13314,7 +13298,6 @@
|
|||
"xpack.ml.fileDatavisualizer.resultsLinks.openInDataVisualizerTitle": "データビジュアライザーを開く",
|
||||
"xpack.ml.fileDatavisualizer.resultsLinks.viewIndexInDiscoverTitle": "インデックスをディスカバリで表示",
|
||||
"xpack.ml.fileDatavisualizer.resultsView.analysisExplanationButtonLabel": "分析説明",
|
||||
"xpack.ml.fileDatavisualizer.resultsView.fileStatsTabName": "ファイル統計",
|
||||
"xpack.ml.fileDatavisualizer.resultsView.overrideSettingsButtonLabel": "上書き設定",
|
||||
"xpack.ml.fileDatavisualizer.simpleImportSettings.createIndexPatternLabel": "インデックスパターンを作成",
|
||||
"xpack.ml.fileDatavisualizer.simpleImportSettings.indexNameAriaLabel": "インデックス名、必須フィールド",
|
||||
|
|
|
@ -13157,18 +13157,6 @@
|
|||
"xpack.ml.fieldDataCard.cardDate.summaryTableTitle": "摘要",
|
||||
"xpack.ml.fieldDataCard.cardIp.topValuesLabel": "排名最前值",
|
||||
"xpack.ml.fieldDataCard.cardKeyword.topValuesLabel": "排名最前值",
|
||||
"xpack.ml.fieldDataCard.cardNumber.details.distributionOfValuesLabel": "分布",
|
||||
"xpack.ml.fieldDataCard.cardNumber.details.topValuesLabel": "排名最前值",
|
||||
"xpack.ml.fieldDataCard.cardNumber.displayingPercentilesLabel": "显示 {minPercent} - {maxPercent} 百分位数",
|
||||
"xpack.ml.fieldDataCard.cardNumber.distinctCountDescription": "{cardinality} 个不同的 {cardinality, plural, other {值}}",
|
||||
"xpack.ml.fieldDataCard.cardNumber.documentsCountDescription": "{count, plural, other {# 个文档}} ({docsPercent}%)",
|
||||
"xpack.ml.fieldDataCard.cardNumber.maxLabel": "最大值",
|
||||
"xpack.ml.fieldDataCard.cardNumber.medianLabel": "中值",
|
||||
"xpack.ml.fieldDataCard.cardNumber.minLabel": "最小值",
|
||||
"xpack.ml.fieldDataCard.cardNumber.selectMetricDetailsDisplayAriaLabel": "选择指标详情的显示选项",
|
||||
"xpack.ml.fieldDataCard.cardOther.cardTypeLabel": "{cardType} 类型",
|
||||
"xpack.ml.fieldDataCard.cardOther.distinctCountDescription": "{cardinality} 个不同的 {cardinality, plural, other {值}}",
|
||||
"xpack.ml.fieldDataCard.cardOther.documentsCountDescription": "{count, plural, other {# 个文档}} ({docsPercent}%)",
|
||||
"xpack.ml.fieldDataCard.cardText.fieldMayBePopulatedDescription": "例如,可以使用文档映射中的 {copyToParam} 参数进行填充,也可以在索引后通过使用 {includesParam} 和 {excludesParam} 参数从 {sourceParam} 字段中修剪。",
|
||||
"xpack.ml.fieldDataCard.cardText.fieldNotPresentDescription": "查询的文档的 {sourceParam} 字段中不存在此字段。",
|
||||
"xpack.ml.fieldDataCard.cardText.noExamplesForFieldsTitle": "没有获取此字段的示例",
|
||||
|
@ -13252,13 +13240,9 @@
|
|||
"xpack.ml.fileDatavisualizer.explanationFlyout.closeButton": "关闭",
|
||||
"xpack.ml.fileDatavisualizer.explanationFlyout.content": "产生分析结果的逻辑步骤。",
|
||||
"xpack.ml.fileDatavisualizer.explanationFlyout.title": "分析说明",
|
||||
"xpack.ml.fileDatavisualizer.fieldStatsCard.distinctCountDescription": "{fieldCardinality} 个不同的{fieldCardinality, plural, other {值}}",
|
||||
"xpack.ml.fileDatavisualizer.fieldStatsCard.documentsCountDescription": "{fieldCount, plural, other {# 个文档}} ({fieldPercent}%)",
|
||||
"xpack.ml.fileDatavisualizer.fieldStatsCard.maxTitle": "最大值",
|
||||
"xpack.ml.fileDatavisualizer.fieldStatsCard.medianTitle": "中值",
|
||||
"xpack.ml.fileDatavisualizer.fieldStatsCard.minTitle": "最小值",
|
||||
"xpack.ml.fileDatavisualizer.fieldStatsCard.noFieldInformationAvailableDescription": "没有可用的字段信息",
|
||||
"xpack.ml.fileDatavisualizer.fieldStatsCard.topStatsValuesDescription": "排在前面的值",
|
||||
"xpack.ml.fileDatavisualizer.fileBeatConfig.paths": "在此处将路径添加您的文件中",
|
||||
"xpack.ml.fileDatavisualizer.fileBeatConfigFlyout.closeButton": "关闭",
|
||||
"xpack.ml.fileDatavisualizer.fileBeatConfigFlyout.copyButton": "复制到剪贴板",
|
||||
|
@ -13346,7 +13330,6 @@
|
|||
"xpack.ml.fileDatavisualizer.resultsLinks.openInDataVisualizerTitle": "在数据可视化工具中打开",
|
||||
"xpack.ml.fileDatavisualizer.resultsLinks.viewIndexInDiscoverTitle": "在 Discover 中查看索引",
|
||||
"xpack.ml.fileDatavisualizer.resultsView.analysisExplanationButtonLabel": "分析说明",
|
||||
"xpack.ml.fileDatavisualizer.resultsView.fileStatsTabName": "文件统计",
|
||||
"xpack.ml.fileDatavisualizer.resultsView.overrideSettingsButtonLabel": "替代设置",
|
||||
"xpack.ml.fileDatavisualizer.simpleImportSettings.createIndexPatternLabel": "创建索引模式",
|
||||
"xpack.ml.fileDatavisualizer.simpleImportSettings.indexNameAriaLabel": "索引名称,必填字段",
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import path from 'path';
|
||||
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { ML_JOB_FIELD_TYPES } from '../../../../../plugins/ml/common/constants/field_types';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const ml = getService('ml');
|
||||
|
@ -17,11 +18,98 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
filePath: path.join(__dirname, 'files_to_import', 'artificial_server_log'),
|
||||
indexName: 'user-import_1',
|
||||
createIndexPattern: false,
|
||||
fieldTypeFilters: [ML_JOB_FIELD_TYPES.NUMBER, ML_JOB_FIELD_TYPES.DATE],
|
||||
fieldNameFilters: ['clientip'],
|
||||
expected: {
|
||||
results: {
|
||||
title: 'artificial_server_log',
|
||||
numberOfFields: 4,
|
||||
},
|
||||
metricFields: [
|
||||
{
|
||||
fieldName: 'bytes',
|
||||
type: ML_JOB_FIELD_TYPES.NUMBER,
|
||||
docCountFormatted: '19 (100%)',
|
||||
statsMaxDecimalPlaces: 3,
|
||||
topValuesCount: 8,
|
||||
},
|
||||
{
|
||||
fieldName: 'httpversion',
|
||||
type: ML_JOB_FIELD_TYPES.NUMBER,
|
||||
docCountFormatted: '19 (100%)',
|
||||
statsMaxDecimalPlaces: 3,
|
||||
topValuesCount: 1,
|
||||
},
|
||||
{
|
||||
fieldName: 'response',
|
||||
type: ML_JOB_FIELD_TYPES.NUMBER,
|
||||
docCountFormatted: '19 (100%)',
|
||||
statsMaxDecimalPlaces: 3,
|
||||
topValuesCount: 3,
|
||||
},
|
||||
],
|
||||
nonMetricFields: [
|
||||
{
|
||||
fieldName: 'timestamp',
|
||||
type: ML_JOB_FIELD_TYPES.DATE,
|
||||
docCountFormatted: '19 (100%)',
|
||||
exampleCount: 10,
|
||||
},
|
||||
{
|
||||
fieldName: 'agent',
|
||||
type: ML_JOB_FIELD_TYPES.KEYWORD,
|
||||
exampleCount: 8,
|
||||
docCountFormatted: '19 (100%)',
|
||||
},
|
||||
{
|
||||
fieldName: 'auth',
|
||||
type: ML_JOB_FIELD_TYPES.KEYWORD,
|
||||
exampleCount: 1,
|
||||
docCountFormatted: '19 (100%)',
|
||||
},
|
||||
{
|
||||
fieldName: 'ident',
|
||||
type: ML_JOB_FIELD_TYPES.KEYWORD,
|
||||
exampleCount: 1,
|
||||
docCountFormatted: '19 (100%)',
|
||||
},
|
||||
{
|
||||
fieldName: 'verb',
|
||||
type: ML_JOB_FIELD_TYPES.KEYWORD,
|
||||
exampleCount: 1,
|
||||
docCountFormatted: '19 (100%)',
|
||||
},
|
||||
{
|
||||
fieldName: 'request',
|
||||
type: ML_JOB_FIELD_TYPES.KEYWORD,
|
||||
exampleCount: 2,
|
||||
docCountFormatted: '19 (100%)',
|
||||
},
|
||||
{
|
||||
fieldName: 'referrer',
|
||||
type: ML_JOB_FIELD_TYPES.KEYWORD,
|
||||
exampleCount: 1,
|
||||
docCountFormatted: '19 (100%)',
|
||||
},
|
||||
{
|
||||
fieldName: 'clientip',
|
||||
type: ML_JOB_FIELD_TYPES.IP,
|
||||
exampleCount: 7,
|
||||
docCountFormatted: '19 (100%)',
|
||||
},
|
||||
{
|
||||
fieldName: 'message',
|
||||
type: ML_JOB_FIELD_TYPES.TEXT,
|
||||
exampleCount: 10,
|
||||
docCountFormatted: '19 (100%)',
|
||||
},
|
||||
],
|
||||
visibleMetricFieldsCount: 3,
|
||||
totalMetricFieldsCount: 3,
|
||||
populatedFieldsCount: 12,
|
||||
totalFieldsCount: 12,
|
||||
fieldTypeFiltersResultCount: 4,
|
||||
fieldNameFiltersResultCount: 1,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@ -63,8 +151,65 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
await ml.dataVisualizerFileBased.assertFileContentPanelExists();
|
||||
await ml.dataVisualizerFileBased.assertSummaryPanelExists();
|
||||
await ml.dataVisualizerFileBased.assertFileStatsPanelExists();
|
||||
await ml.dataVisualizerFileBased.assertNumberOfFieldCards(
|
||||
testData.expected.results.numberOfFields
|
||||
|
||||
await ml.testExecution.logTestStep(
|
||||
`displays elements in the data visualizer table correctly`
|
||||
);
|
||||
await ml.dataVisualizerIndexBased.assertDataVisualizerTableExist();
|
||||
|
||||
await ml.dataVisualizerIndexBased.assertVisibleMetricFieldsCount(
|
||||
testData.expected.visibleMetricFieldsCount
|
||||
);
|
||||
await ml.dataVisualizerIndexBased.assertTotalMetricFieldsCount(
|
||||
testData.expected.totalMetricFieldsCount
|
||||
);
|
||||
await ml.dataVisualizerIndexBased.assertVisibleFieldsCount(
|
||||
testData.expected.totalFieldsCount
|
||||
);
|
||||
await ml.dataVisualizerIndexBased.assertTotalFieldsCount(
|
||||
testData.expected.totalFieldsCount
|
||||
);
|
||||
|
||||
await ml.testExecution.logTestStep(
|
||||
'displays details for metric fields and non-metric fields correctly'
|
||||
);
|
||||
await ml.dataVisualizerTable.ensureNumRowsPerPage(25);
|
||||
|
||||
for (const fieldRow of testData.expected.metricFields) {
|
||||
await ml.dataVisualizerTable.assertNumberFieldContents(
|
||||
fieldRow.fieldName,
|
||||
fieldRow.docCountFormatted,
|
||||
fieldRow.topValuesCount,
|
||||
false
|
||||
);
|
||||
}
|
||||
for (const fieldRow of testData.expected.nonMetricFields!) {
|
||||
await ml.dataVisualizerTable.assertNonMetricFieldContents(
|
||||
fieldRow.type,
|
||||
fieldRow.fieldName!,
|
||||
fieldRow.docCountFormatted,
|
||||
fieldRow.exampleCount
|
||||
);
|
||||
}
|
||||
|
||||
await ml.testExecution.logTestStep('sets and resets field type filter correctly');
|
||||
await ml.dataVisualizerTable.setFieldTypeFilter(
|
||||
testData.fieldTypeFilters,
|
||||
testData.expected.fieldTypeFiltersResultCount
|
||||
);
|
||||
await ml.dataVisualizerTable.removeFieldTypeFilter(
|
||||
testData.fieldTypeFilters,
|
||||
testData.expected.totalFieldsCount
|
||||
);
|
||||
|
||||
await ml.testExecution.logTestStep('sets and resets field name filter correctly');
|
||||
await ml.dataVisualizerTable.setFieldNameFilter(
|
||||
testData.fieldNameFilters,
|
||||
testData.expected.fieldNameFiltersResultCount
|
||||
);
|
||||
await ml.dataVisualizerTable.removeFieldNameFilter(
|
||||
testData.fieldNameFilters,
|
||||
testData.expected.totalFieldsCount
|
||||
);
|
||||
|
||||
await ml.testExecution.logTestStep('loads the import settings page');
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
2018-01-06 16:56:14.295748 INFO host:'Server A' Incoming connection from ip 123.456.789.0
|
||||
2018-01-06 16:56:15.295748 INFO host:'Server A' Incoming connection from ip 123.456.789.1
|
||||
2018-01-06 16:56:16.295748 INFO host:'Server A' Incoming connection from ip 123.456.789.2
|
||||
2018-01-06 16:56:17.295748 INFO host:'Server A' Incoming connection from ip 123.456.789.3
|
||||
2018-01-06 16:56:18.295748 INFO host:'Server B' Incoming connection from ip 123.456.789.0
|
||||
2018-01-06 16:56:19.295748 INFO host:'Server B' Incoming connection from ip 123.456.789.2
|
||||
2018-01-06 16:56:20.295748 INFO host:'Server B' Incoming connection from ip 123.456.789.3
|
||||
2018-01-06 16:56:21.295748 INFO host:'Server B' Incoming connection from ip 123.456.789.4
|
||||
2018-01-06 16:56:22.295748 WARN host:'Server A' Disk watermark 80%
|
||||
2018-01-06 17:16:23.295748 WARN host:'Server A' Disk watermark 90%
|
||||
2018-01-06 17:36:10.295748 ERROR host:'Server A' Main process crashed
|
||||
2018-01-06 17:36:14.295748 INFO host:'Server A' Connection from ip 123.456.789.0 closed
|
||||
2018-01-06 17:36:15.295748 INFO host:'Server A' Connection from ip 123.456.789.1 closed
|
||||
2018-01-06 17:36:16.295748 INFO host:'Server A' Connection from ip 123.456.789.2 closed
|
||||
2018-01-06 17:36:17.295748 INFO host:'Server A' Connection from ip 123.456.789.3 closed
|
||||
2018-01-06 17:46:11.295748 INFO host:'Server B' Some special characters °!"§$%&/()=?`'^²³{[]}\+*~#'-_.:,;µ|<>äöüß
|
||||
2018-01-06 17:46:12.295748 INFO host:'Server B' Shutting down
|
||||
|
||||
|
||||
93.180.71.3 - - [17/May/2015:08:05:32 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)"
|
||||
93.180.71.3 - - [17/May/2015:08:05:23 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)"
|
||||
80.91.33.133 - - [17/May/2015:08:05:24 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.17)"
|
||||
217.168.17.5 - - [17/May/2015:08:05:34 +0000] "GET /downloads/product_1 HTTP/1.1" 200 490 "-" "Debian APT-HTTP/1.3 (0.8.10.3)"
|
||||
217.168.17.5 - - [17/May/2015:08:05:09 +0000] "GET /downloads/product_2 HTTP/1.1" 200 490 "-" "Debian APT-HTTP/1.3 (0.8.10.3)"
|
||||
93.180.71.3 - - [17/May/2015:08:05:57 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)"
|
||||
217.168.17.5 - - [17/May/2015:08:05:02 +0000] "GET /downloads/product_2 HTTP/1.1" 404 337 "-" "Debian APT-HTTP/1.3 (0.8.10.3)"
|
||||
217.168.17.5 - - [17/May/2015:08:05:42 +0000] "GET /downloads/product_1 HTTP/1.1" 404 332 "-" "Debian APT-HTTP/1.3 (0.8.10.3)"
|
||||
80.91.33.133 - - [17/May/2015:08:05:01 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.17)"
|
||||
93.180.71.3 - - [17/May/2015:08:05:27 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)"
|
||||
217.168.17.5 - - [17/May/2015:08:05:12 +0000] "GET /downloads/product_2 HTTP/1.1" 200 3316 "-" "Some special characters °!"§$%&/()=?`'^²³{[]}\+*~#'-_.:,;µ|<>äöüß"
|
||||
188.138.60.101 - - [17/May/2015:08:05:49 +0000] "GET /downloads/product_2 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.9.7.9)"
|
||||
80.91.33.133 - - [17/May/2015:08:05:14 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.16)"
|
||||
46.4.66.76 - - [17/May/2015:08:05:45 +0000] "GET /downloads/product_1 HTTP/1.1" 404 318 "-" "Debian APT-HTTP/1.3 (1.0.1ubuntu2)"
|
||||
93.180.71.3 - - [17/May/2015:08:05:26 +0000] "GET /downloads/product_1 HTTP/1.1" 404 324 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)"
|
||||
91.234.194.89 - - [17/May/2015:08:05:22 +0000] "GET /downloads/product_2 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.9.7.9)"
|
||||
80.91.33.133 - - [17/May/2015:08:05:07 +0000] "GET /downloads/product_1 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.17)"
|
||||
37.26.93.214 - - [17/May/2015:08:05:38 +0000] "GET /downloads/product_2 HTTP/1.1" 404 319 "-" "Go 1.1 package http"
|
||||
188.138.60.101 - - [17/May/2015:08:05:25 +0000] "GET /downloads/product_2 HTTP/1.1" 304 0 "-" "Debian APT-HTTP/1.3 (0.9.7.9)"
|
||||
93.180.71.3 - - [17/May/2015:08:05:11 +0000] "GET /downloads/product_1 HTTP/1.1" 404 340 "-" "Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { ML_JOB_FIELD_TYPES } from '../../../../../plugins/ml/common/constants/field_types';
|
||||
import { FieldVisConfig } from '../../../../../plugins/ml/public/application/datavisualizer/index_based/common';
|
||||
import { FieldVisConfig } from '../../../../../plugins/ml/public/application/datavisualizer/stats_table/types';
|
||||
|
||||
interface MetricFieldVisConfig extends FieldVisConfig {
|
||||
statsMaxDecimalPlaces: number;
|
||||
|
|
|
@ -246,7 +246,8 @@ export function MachineLearningDataVisualizerTableProvider(
|
|||
public async assertNumberFieldContents(
|
||||
fieldName: string,
|
||||
docCountFormatted: string,
|
||||
topValuesCount: number
|
||||
topValuesCount: number,
|
||||
checkDistributionPreviewExist = true
|
||||
) {
|
||||
await this.assertRowExists(fieldName);
|
||||
await this.assertFieldDocCount(fieldName, docCountFormatted);
|
||||
|
@ -257,7 +258,9 @@ export function MachineLearningDataVisualizerTableProvider(
|
|||
await testSubjects.existOrFail(this.detailsSelector(fieldName, 'mlTopValues'));
|
||||
await this.assertTopValuesContents(fieldName, topValuesCount);
|
||||
|
||||
await this.assertDistributionPreviewExist(fieldName);
|
||||
if (checkDistributionPreviewExist) {
|
||||
await this.assertDistributionPreviewExist(fieldName);
|
||||
}
|
||||
|
||||
await this.ensureDetailsClosed(fieldName);
|
||||
}
|
||||
|
@ -320,5 +323,19 @@ export function MachineLearningDataVisualizerTableProvider(
|
|||
await this.assertTextFieldContents(fieldName, docCountFormatted, exampleCount);
|
||||
}
|
||||
}
|
||||
|
||||
public async ensureNumRowsPerPage(n: 10 | 25 | 100) {
|
||||
const paginationButton = 'mlDataVisualizerTable > tablePaginationPopoverButton';
|
||||
await retry.tryForTime(10000, async () => {
|
||||
await testSubjects.existOrFail(paginationButton);
|
||||
await testSubjects.click(paginationButton);
|
||||
await testSubjects.click(`tablePagination-${n}-rows`);
|
||||
|
||||
const visibleTexts = await testSubjects.getVisibleText(paginationButton);
|
||||
|
||||
const [, pagination] = visibleTexts.split(': ');
|
||||
expect(pagination).to.eql(n.toString());
|
||||
});
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue