[ML] Data Visualizer redesign (#54358)

* [ML] change basic page structure

* [ML] adjust search panel

* [ML] adjust fields_panel.tsx

* [ML] card icon styles

* [ML] styles

* [ML] adjust actions_panel.tsx

* Update styling of panels, spacing

* [ML] change basic page structure

* [ML] adjust search panel

* [ML] adjust fields_panel.tsx

* [ML] card icon styles

* [ML] styles

* [ML] adjust actions_panel.tsx

* [ML] fix i18n

* [ML] fix styles

* [ML] adjust top values styles

* [ML] remove conflicts artifacts

* Use EuiBorderColor

* [ML] fix i18n

* [ML] fix i18n

* [ML] fix counters

* [ML] fixed width for sample size select

* [ML] fix layout for file viz

* [ML] fix empty cards rendering

* Update text styling and spacing

* [ML] fix field stats card

* [ML] fix counter for showAllFields

* [ML] reset title for the badge

* [ML] boolean_content.tsx with the bar chart

* [ML] fix counters

Co-authored-by: DeFazio <michael.defazio@elastic.co>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Dima Arnautov 2020-01-14 19:52:58 +01:00 committed by GitHub
parent 5738855389
commit 1b076171f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 803 additions and 632 deletions

View file

@ -13,11 +13,11 @@ export const DisplayValue: FC<{ value: any }> = ({ value }) => {
const length = String(value).length;
if (length <= MAX_CHARS) {
return value;
return <b>{value}</b>;
} else {
return (
<EuiToolTip content={value} anchorClassName="valueWrapper">
<span>{value}</span>
<b>{value}</b>
</EuiToolTip>
);
}

View file

@ -1,9 +1,14 @@
.ml-field-title-bar {
color: $euiColorEmptyShade;
@include euiFontSizeL;
@include euiFontSizeM;
font-family: Roboto Mono, serif;
font-style: normal;
font-weight: bold;
font-size: $euiFontSizeS;
border-radius: $euiBorderRadius $euiBorderRadius 0 0;
padding: $euiSizeXS;
margin: (-$euiSize) (-$euiSize) 0 (-$euiSize);
border-top: 3px solid;
text-align: center;
border-radius: $euiBorderRadius $euiBorderRadius 0px 0px;
padding-bottom: $euiSizeXS/2;
.field-type-icon {
vertical-align: middle;
@ -18,5 +23,6 @@
padding-right: $euiSizeS;
max-width: 290px; // SASSTODO: Calculate value
display: inline-block;
margin-left: $euiSizeS;
}
}

View file

@ -1,8 +1,18 @@
$icon-size: 20px;
.field-type-icon-container {
display: inline !important;
display: inline-block !important;
vertical-align: middle;
border: 1px solid;
border-radius: 4px;
width: $icon-size;
height: $icon-size;
line-height: $icon-size;;
text-align: center;
.field-type-icon {
padding-right: $euiSizeXS / 2;
padding: 0;
display: inline !important;
vertical-align: initial;
}
}

View file

@ -8,56 +8,96 @@
// These styles should all be removed once the file data visualizer is using
// the same field_data_card component as the index based data visualizer.
height: 408px;
box-shadow: none;
border-color: $euiBorderColor;
// Note the names of these styles need to match the type of the field they are displaying.
.boolean {
background-color: #e6c220;
color: $euiColorVis5;
border-color: $euiColorVis5;
.field-type-icon-container {
background-color: rgba($euiColorVis5, 0.5);
}
}
.date {
background-color: #f98510;
color: $euiColorVis7;
border-color: $euiColorVis7;
.field-type-icon-container {
background-color: rgba($euiColorVis7, 0.5);
}
}
.document_count {
background-color: #db1374;
color: $euiColorVis2;
border-color: $euiColorVis2;
.field-type-icon-container {
background-color: rgba($euiColorVis2, 0.5);
}
}
.geo_point {
background-color: #461a0a;
color: $euiColorVis8;
border-color: $euiColorVis8;
.field-type-icon-container {
background-color: rgba($euiColorVis8, 0.5);
}
}
.ip {
background-color: #490092;
color: $euiColorVis3;
border-color: $euiColorVis3;
.field-type-icon-container {
background-color: rgba($euiColorVis3, 0.5);
}
}
.keyword {
background-color: #00b3a4;
color: $euiColorVis0;
border-color: $euiColorVis0;
.field-type-icon-container {
background-color: rgba($euiColorVis0, 0.5);
}
}
.number {
background-color: #3185fc;
color: $euiColorVis1;
border-color: $euiColorVis1;
.field-type-icon-container {
background-color: rgba($euiColorVis1, 0.5);
}
}
.text {
background-color: #920000;
color: $euiColorVis9;
border-color: $euiColorVis9;
.field-type-icon-container {
background-color: rgba($euiColorVis9, 0.5);
}
}
.type-other,
.unknown {
background-color: #bfa180;
color: $euiColorVis6;
border-color: $euiColorVis6;
.field-type-icon-container {
background-color: rgba($euiColorVis6, 0.5);
}
}
// Use euiPanel styling
@include euiPanel($selector: '.card-contents');
.card-contents {
height: 378px;
line-height: 21px;
border-radius: 0px 0px $euiBorderRadius $euiBorderRadius;
overflow: hidden;
}
.stats {
padding: 10px 10px 0px 10px;
text-align: center;
}

View file

@ -5,7 +5,7 @@
*/
import React from 'react';
import { EuiSpacer } from '@elastic/eui';
import { EuiSpacer, EuiPanel, EuiFlexGroup, EuiFlexItem, EuiText, EuiProgress } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { FieldTypeIcon } from '../../../../components/field_type_icon';
@ -25,129 +25,136 @@ export function FieldStatsCard({ field }) {
}
return (
<React.Fragment>
<div className="card-container">
<div className="ml-field-data-card">
<div className={`ml-field-title-bar ${type}`}>
<FieldTypeIcon type={type} needsAria={false} />
<div
className="field-name"
tabIndex="0"
aria-label={`${cardTitleAriaLabel.join(', ')}`}
>
{field.name}
</div>
<EuiPanel hasShadow={false} className="mlFieldDataCard">
<div className="ml-field-data-card">
<div className={`ml-field-title-bar ${type}`}>
<FieldTypeIcon type={type} needsAria={false} />
<div className="field-name" tabIndex="0" aria-label={`${cardTitleAriaLabel.join(', ')}`}>
{field.name}
</div>
</div>
<div className="card-contents">
{field.count > 0 && (
<React.Fragment>
<div className="stats">
<div className="stat">
<i className="fa fa-files-o" aria-hidden="true" />
&nbsp;
<FormattedMessage
id="xpack.ml.fileDatavisualizer.fieldStatsCard.documentsCountDescription"
defaultMessage="{fieldCount, plural, zero {# document} one {# document} other {# documents}} ({fieldPercent}%)"
values={{
fieldCount: field.count,
fieldPercent: field.percent,
}}
/>
</div>
<div className="stat">
<i className="fa fa-cubes" aria-hidden="true" />
&nbsp;
<FormattedMessage
id="xpack.ml.fileDatavisualizer.fieldStatsCard.distinctCountDescription"
defaultMessage="{fieldCardinality} distinct {fieldCardinality, plural, zero {value} one {value} other {values}}"
values={{
fieldCardinality: field.cardinality,
}}
/>
</div>
{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 className="mlFieldDataCard__content">
{field.count > 0 && (
<React.Fragment>
<div className="stats">
<div className="stat">
<i className="fa fa-files-o" aria-hidden="true" />
&nbsp;
<FormattedMessage
id="xpack.ml.fileDatavisualizer.fieldStatsCard.documentsCountDescription"
defaultMessage="{fieldCount, plural, zero {# document} one {# document} other {# documents}} ({fieldPercent}%)"
values={{
fieldCount: field.count,
fieldPercent: field.percent,
}}
/>
</div>
<div className="stat">
<i className="fa fa-cubes" aria-hidden="true" />
&nbsp;
<FormattedMessage
id="xpack.ml.fileDatavisualizer.fieldStatsCard.distinctCountDescription"
defaultMessage="{fieldCardinality} distinct {fieldCardinality, plural, zero {value} one {value} other {values}}"
values={{
fieldCardinality: field.cardinality,
}}
/>
</div>
{field.top_hits && (
{field.median_value && (
<React.Fragment>
<EuiSpacer size="s" />
<div className="stats">
<div className="stat">
<div>
<div className="stat min heading">
<FormattedMessage
id="xpack.ml.fileDatavisualizer.fieldStatsCard.topStatsValuesDescription"
defaultMessage="top values"
id="xpack.ml.fileDatavisualizer.fieldStatsCard.minTitle"
defaultMessage="min"
/>
</div>
{field.top_hits.map(({ count, value }) => {
const pcnt = Math.round((count / field.count) * 100 * 100) / 100;
return (
<div key={value} className="top-value">
<div className="field-label">{value}&nbsp;</div>
<div className="top-value-bar-holder">
<div
className={`top-value-bar ${type}`}
style={{ width: `${pcnt}%` }}
/>
</div>
<div className="count-label">{pcnt}%</div>
</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>
)}
</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>
{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}&nbsp;
</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>
</React.Fragment>
</EuiPanel>
);
}

View file

@ -6,6 +6,7 @@
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';
@ -29,9 +30,13 @@ export class FieldsStats extends Component {
render() {
return (
<div className="fields-stats">
{this.state.fields.map(f => (
<FieldStatsCard field={f} key={f.name} />
))}
<EuiFlexGrid gutterSize="m">
{this.state.fields.map(f => (
<EuiFlexItem key={f.name} style={{ minWidth: '360px' }}>
<FieldStatsCard field={f} />
</EuiFlexItem>
))}
</EuiFlexGrid>
</div>
);
}

View file

@ -8,7 +8,7 @@ import React, { FC, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { EuiPanel, EuiSpacer, EuiText, EuiTitle, EuiFlexGroup } from '@elastic/eui';
import { EuiSpacer, EuiText, EuiTitle, EuiFlexGroup } from '@elastic/eui';
import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public';
import { CreateJobLinkCard } from '../../../../components/create_job_link_card';
@ -38,8 +38,8 @@ export const ActionsPanel: FC<Props> = ({ indexPattern }) => {
// passed the recognizerResults object, and then run the recognizer check which
// controls whether the recognizer section is ultimately displayed.
return (
<EuiPanel data-test-subj="mlDataVisualizerActionsPanel">
<EuiTitle>
<div data-test-subj="mlDataVisualizerActionsPanel">
<EuiTitle size="s">
<h2>
<FormattedMessage
id="xpack.ml.datavisualizer.actionsPanel.createJobTitle"
@ -49,7 +49,7 @@ export const ActionsPanel: FC<Props> = ({ indexPattern }) => {
</EuiTitle>
<EuiSpacer size="s" />
<div style={recognizerResultsCount === 0 ? { display: 'none' } : {}}>
<EuiText>
<EuiText size="s" color="subdued">
<p>
<FormattedMessage
id="xpack.ml.datavisualizer.actionsPanel.selectKnownConfigurationDescription"
@ -67,7 +67,7 @@ export const ActionsPanel: FC<Props> = ({ indexPattern }) => {
</EuiFlexGroup>
<EuiSpacer size="l" />
</div>
<EuiText>
<EuiText size="s" color="subdued">
<p>
<FormattedMessage
id="xpack.ml.datavisualizer.actionsPanel.createJobDescription"
@ -88,6 +88,6 @@ export const ActionsPanel: FC<Props> = ({ indexPattern }) => {
onClick={openAdvancedJobWizard}
href={`#/jobs/new_job/advanced?index=${indexPattern}`}
/>
</EuiPanel>
</div>
);
};

View file

@ -1,52 +1,94 @@
.mlFieldDataCard {
height: 420px;
width: 360px;
box-shadow: none;
border-color: $euiBorderColor;
// Note the names of these styles need to match the type of the field they are displaying.
.boolean {
background-color: $euiColorVis5;
color: $euiColorVis5;
border-color: $euiColorVis5;
.field-type-icon-container {
background-color: rgba($euiColorVis5, 0.5);
}
}
.date {
background-color: $euiColorVis7;
color: $euiColorVis7;
border-color: $euiColorVis7;
.field-type-icon-container {
background-color: rgba($euiColorVis7, 0.5);
}
}
.document_count {
background-color: $euiColorVis2;
color: $euiColorVis2;
border-color: $euiColorVis2;
.field-type-icon-container {
background-color: rgba($euiColorVis2, 0.5);
}
}
.geo_point {
background-color: $euiColorVis8;
color: $euiColorVis8;
border-color: $euiColorVis8;
.field-type-icon-container {
background-color: rgba($euiColorVis8, 0.5);
}
}
.ip {
background-color: $euiColorVis3;
color: $euiColorVis3;
border-color: $euiColorVis3;
.field-type-icon-container {
background-color: rgba($euiColorVis3, 0.5);
}
}
.keyword {
background-color: $euiColorVis0;
color: $euiColorVis0;
border-color: $euiColorVis0;
.field-type-icon-container {
background-color: rgba($euiColorVis0, 0.5);
}
}
.number {
background-color: $euiColorVis1;
color: $euiColorVis1;
border-color: $euiColorVis1;
.field-type-icon-container {
background-color: rgba($euiColorVis1, 0.5);
}
}
.text {
background-color: $euiColorVis9;
color: $euiColorVis9;
border-color: $euiColorVis9;
.field-type-icon-container {
background-color: rgba($euiColorVis9, 0.5);
}
}
.type-other,
.unknown {
background-color: $euiColorVis6;
}
color: $euiColorVis6;
border-color: $euiColorVis6;
// Use euiPanel styling
@include euiPanel($selector: '.mlFieldDataCard__content');
.field-type-icon-container {
background-color: rgba($euiColorVis6, 0.5);
}
}
.mlFieldDataCard__content {
@include euiFontSizeS;
height: 385px;
border-radius: 0px 0px $euiBorderRadius $euiBorderRadius;
overflow: hidden;
}

View file

@ -5,21 +5,20 @@
*/
import React, { FC } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiProgress, EuiSpacer, EuiText } from '@elastic/eui';
import { EuiIcon, EuiSpacer, EuiText } 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';
function getPercentLabel(valueCount: number, totalCount: number): string {
if (valueCount === 0) {
function getPercentLabel(value: number): string {
if (value === 0) {
return '0%';
}
const percent = (100 * valueCount) / totalCount;
if (percent >= 0.1) {
return `${roundToDecimalPlace(percent, 1)}%`;
if (value >= 0.1) {
return `${value}%`;
} else {
return '< 0.1%';
}
@ -31,64 +30,74 @@ export const BooleanContent: FC<FieldDataCardProps> = ({ config }) => {
const { count, sampleCount, trueCount, falseCount } = stats;
const docsPercent = roundToDecimalPlace((count / sampleCount) * 100);
// TODO - display counts of true / false in an Elastic Charts bar chart (or Pie chart if available).
return (
<div className="mlFieldDataCard__stats">
<div>
<EuiIcon type="document" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardBoolean.documentsCountDescription"
defaultMessage="{count, plural, zero {# document} one {# document} other {# documents}} ({docsPercent}%)"
values={{
count,
docsPercent,
}}
/>
<EuiText size="xs" color="subdued">
<EuiIcon type="document" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardBoolean.documentsCountDescription"
defaultMessage="{count, plural, zero {# document} one {# document} other {# documents}} ({docsPercent}%)"
values={{
count,
docsPercent,
}}
/>
</EuiText>
</div>
<EuiSpacer size="m" />
<div>
<FormattedMessage
id="xpack.ml.fieldDataCard.cardBoolean.valuesLabel"
defaultMessage="values"
/>
<EuiSpacer size="xs" />
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false} style={{ width: 100 }} className="eui-textTruncate">
<EuiText size="s" textAlign="right">
true
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiProgress value={trueCount} max={count} color="primary" size="l" />
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ width: 70 }} className="eui-textTruncate">
<EuiText size="s" textAlign="left">
{getPercentLabel(trueCount, count)}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="xs" />
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false} style={{ width: 100 }} className="eui-textTruncate">
<EuiText size="s" textAlign="right">
false
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiProgress value={falseCount} max={count} color="subdued" size="l" />
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ width: 70 }} className="eui-textTruncate">
<EuiText size="s" textAlign="left">
{getPercentLabel(falseCount, count)}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiText size="s">
<h6>
<FormattedMessage
id="xpack.ml.fieldDataCard.cardBoolean.valuesLabel"
defaultMessage="Values"
/>
</h6>
</EuiText>
<EuiSpacer size="s" />
<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,
}}
customSeriesColors={['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>
</div>
);

View file

@ -5,7 +5,7 @@
*/
import React, { FC } from 'react';
import { EuiIcon, EuiSpacer } from '@elastic/eui';
import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui';
// @ts-ignore
import { formatDate } from '@elastic/eui/lib/services/format';
@ -25,19 +25,21 @@ export const DateContent: FC<FieldDataCardProps> = ({ config }) => {
return (
<div className="mlFieldDataCard__stats">
<div>
<EuiIcon type="document" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardDate.documentsCountDescription"
defaultMessage="{count, plural, zero {# document} one {# document} other {# documents}} ({docsPercent}%)"
values={{
count,
docsPercent,
}}
/>
<EuiText size="xs" color="subdued">
<EuiIcon type="document" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardDate.documentsCountDescription"
defaultMessage="{count, plural, zero {# document} one {# document} other {# documents}} ({docsPercent}%)"
values={{
count,
docsPercent,
}}
/>
</EuiText>
</div>
<EuiSpacer size="s" />
<EuiSpacer size="m" />
<div>
<FormattedMessage

View file

@ -5,7 +5,7 @@
*/
import React, { FC } from 'react';
import { EuiIcon, EuiSpacer } from '@elastic/eui';
import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
@ -42,33 +42,37 @@ export const GeoPointContent: FC<FieldDataCardProps> = ({ config }) => {
return (
<div className="mlFieldDataCard__stats">
<div>
<EuiIcon type="document" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardGeoPoint.documentsCountDescription"
defaultMessage="{count, plural, zero {# document} one {# document} other {# documents}} ({docsPercent}%)"
values={{
count,
docsPercent,
}}
/>
<EuiText size="xs" color="subdued">
<EuiIcon type="document" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardGeoPoint.documentsCountDescription"
defaultMessage="{count, plural, zero {# document} one {# document} other {# documents}} ({docsPercent}%)"
values={{
count,
docsPercent,
}}
/>
</EuiText>
</div>
<EuiSpacer size="xs" />
<div>
<EuiIcon type="database" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardGeoPoint.distinctCountDescription"
defaultMessage="{cardinality} distinct {cardinality, plural, zero {value} one {value} other {values}}"
values={{
cardinality,
}}
/>
<EuiText size="xs" color="subdued">
<EuiIcon type="database" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardGeoPoint.distinctCountDescription"
defaultMessage="{cardinality} distinct {cardinality, plural, zero {value} one {value} other {values}}"
values={{
cardinality,
}}
/>
</EuiText>
</div>
<EuiSpacer size="s" />
<EuiSpacer size="m" />
<ExamplesList examples={examples} />
</div>

View file

@ -5,7 +5,7 @@
*/
import React, { FC } from 'react';
import { EuiIcon, EuiSpacer } from '@elastic/eui';
import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui';
// @ts-ignore
import { formatDate } from '@elastic/eui/lib/services/format';
@ -24,30 +24,34 @@ export const IpContent: FC<FieldDataCardProps> = ({ config }) => {
return (
<div className="mlFieldDataCard__stats">
<div>
<EuiIcon type="document" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardIp.documentsCountDescription"
defaultMessage="{count, plural, zero {# document} one {# document} other {# documents}} ({docsPercent}%)"
values={{
count,
docsPercent,
}}
/>
<EuiText size="xs" color="subdued">
<EuiIcon type="document" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardIp.documentsCountDescription"
defaultMessage="{count, plural, zero {# document} one {# document} other {# documents}} ({docsPercent}%)"
values={{
count,
docsPercent,
}}
/>
</EuiText>
</div>
<EuiSpacer size="xs" />
<div>
<EuiIcon type="database" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardIp.distinctCountDescription"
defaultMessage="{cardinality} distinct {cardinality, plural, zero {value} one {value} other {values}}"
values={{
cardinality,
}}
/>
<EuiText size="xs" color="subdued">
<EuiIcon type="database" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardIp.distinctCountDescription"
defaultMessage="{cardinality} distinct {cardinality, plural, zero {value} one {value} other {values}}"
values={{
cardinality,
}}
/>
</EuiText>
</div>
<EuiSpacer size="m" />

View file

@ -5,7 +5,7 @@
*/
import React, { FC } from 'react';
import { EuiIcon, EuiSpacer } from '@elastic/eui';
import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui';
// @ts-ignore
import { formatDate } from '@elastic/eui/lib/services/format';
@ -24,40 +24,49 @@ export const KeywordContent: FC<FieldDataCardProps> = ({ config }) => {
return (
<div className="mlFieldDataCard__stats">
<div>
<EuiIcon type="document" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardKeyword.documentsCountDescription"
defaultMessage="{count, plural, zero {# document} one {# document} other {# documents}} ({docsPercent}%)"
values={{
count,
docsPercent,
}}
/>
<EuiText size="xs" color="subdued">
<EuiIcon type="document" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardKeyword.documentsCountDescription"
defaultMessage="{count, plural, zero {# document} one {# document} other {# documents}} ({docsPercent}%)"
values={{
count,
docsPercent,
}}
/>
</EuiText>
</div>
<EuiSpacer size="xs" />
<div>
<EuiIcon type="database" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardKeyword.distinctCountDescription"
defaultMessage="{cardinality} distinct {cardinality, plural, zero {value} one {value} other {values}}"
values={{
cardinality,
}}
/>
<EuiText size="xs" color="subdued">
<EuiIcon type="database" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardKeyword.distinctCountDescription"
defaultMessage="{cardinality} distinct {cardinality, plural, zero {value} one {value} other {values}}"
values={{
cardinality,
}}
/>
</EuiText>
</div>
<EuiSpacer size="m" />
<div>
<FormattedMessage
id="xpack.ml.fieldDataCard.cardKeyword.topValuesLabel"
defaultMessage="top values"
/>
<EuiSpacer size="xs" />
<EuiText size="s">
<h6>
<FormattedMessage
id="xpack.ml.fieldDataCard.cardKeyword.topValuesLabel"
defaultMessage="Top values"
/>
</h6>
</EuiText>
<EuiSpacer size="s" />
<TopValues stats={stats} fieldFormat={fieldFormat} barColor="secondary" />
</div>
</div>

View file

@ -5,29 +5,23 @@
*/
import React, { FC, Fragment } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer } from '@elastic/eui';
import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
export const NotInDocsContent: FC = () => (
<Fragment>
<EuiSpacer size="xxl" />
<EuiFlexGroup justifyContent="spaceAround">
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" justifyContent="spaceAround">
<EuiFlexItem grow={false}>
<EuiIcon type="alert" />
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup style={{ padding: '0px 16px', textAlign: 'center' }}>
<EuiFlexItem grow={false}>
<FormattedMessage
id="xpack.ml.fieldDataCard.fieldNotInDocsLabel"
defaultMessage="This field does not appear in any documents for the selected time range"
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
<EuiText textAlign="center">
<EuiIcon type="alert" />
</EuiText>
<EuiSpacer size="s" />
<EuiText textAlign="center">
<FormattedMessage
id="xpack.ml.fieldDataCard.fieldNotInDocsLabel"
defaultMessage="This field does not appear in any documents for the selected time range"
/>
</EuiText>
</Fragment>
);

View file

@ -5,7 +5,14 @@
*/
import React, { FC, Fragment, useEffect, useState } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSelect, EuiSpacer, EuiText } from '@elastic/eui';
import {
EuiButtonGroup,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiSpacer,
EuiText,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
@ -53,15 +60,15 @@ export const NumberContent: FC<FieldDataCardProps> = ({ config }) => {
const detailsOptions = [
{
value: DETAILS_MODE.DISTRIBUTION,
text: i18n.translate('xpack.ml.fieldDataCard.cardNumber.details.distributionOfValuesLabel', {
defaultMessage: 'distribution of values',
id: DETAILS_MODE.TOP_VALUES,
label: i18n.translate('xpack.ml.fieldDataCard.cardNumber.details.topValuesLabel', {
defaultMessage: 'Top values',
}),
},
{
value: DETAILS_MODE.TOP_VALUES,
text: 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',
}),
},
];
@ -69,49 +76,60 @@ export const NumberContent: FC<FieldDataCardProps> = ({ config }) => {
return (
<div className="mlFieldDataCard__stats">
<div>
<EuiIcon type="document" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardNumber.documentsCountDescription"
defaultMessage="{count, plural, zero {# document} one {# document} other {# documents}} ({docsPercent}%)"
values={{
count,
docsPercent,
}}
/>
<EuiText size="xs" color="subdued">
<EuiIcon type="document" />
&nbsp;
<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>
<EuiIcon type="database" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardNumber.distinctCountDescription"
defaultMessage="{cardinality} distinct {cardinality, plural, zero {value} one {value} other {values}}"
values={{
cardinality,
}}
/>
<EuiText size="xs" color="subdued">
<EuiIcon type="database" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardNumber.distinctCountDescription"
defaultMessage="{cardinality} distinct {cardinality, plural, zero {value} one {value} other {values}}"
values={{
cardinality,
}}
/>
</EuiText>
</div>
<EuiSpacer size="xs" />
<EuiSpacer size="m" />
<EuiFlexGroup gutterSize="xs" justifyContent="center">
<EuiFlexItem grow={1}>
<FormattedMessage id="xpack.ml.fieldDataCard.cardNumber.minLabel" defaultMessage="min" />
<EuiText color="subdued" size="xs">
<FormattedMessage
id="xpack.ml.fieldDataCard.cardNumber.minLabel"
defaultMessage="min"
/>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={1}>
<FormattedMessage
id="xpack.ml.fieldDataCard.cardNumber.medianLabel"
defaultMessage="median"
/>
<EuiText color="subdued" size="xs">
<FormattedMessage
id="xpack.ml.fieldDataCard.cardNumber.medianLabel"
defaultMessage="median"
/>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={1}>
<FormattedMessage id="xpack.ml.fieldDataCard.cardNumber.maxLabel" defaultMessage="max" />
<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">
<DisplayValue value={kibanaFieldFormat(min, fieldFormat)} />
@ -123,30 +141,22 @@ export const NumberContent: FC<FieldDataCardProps> = ({ config }) => {
<DisplayValue value={kibanaFieldFormat(max, fieldFormat)} />
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiFlexGroup gutterSize="xs" justifyContent="center">
<EuiFlexItem grow={false} style={{ width: 200 }} className="eui-textTruncate">
<EuiSelect
options={detailsOptions}
value={detailsMode}
compressed
onChange={e => setDetailsMode(e.target.value as DETAILS_MODE)}
style={{ width: '200px' }}
aria-label={i18n.translate(
'xpack.ml.fieldDataCard.cardNumber.selectMetricDetailsDisplayAriaLabel',
{
defaultMessage: 'Select display option for metric details',
}
)}
data-test-subj="mlFieldDataCardNumberDetailsSelect"
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiButtonGroup
options={detailsOptions}
idSelected={detailsMode}
onChange={optionId => setDetailsMode(optionId as DETAILS_MODE)}
aria-label={i18n.translate(
'xpack.ml.fieldDataCard.cardNumber.selectMetricDetailsDisplayAriaLabel',
{
defaultMessage: 'Select display option for metric details',
}
)}
data-test-subj="mlFieldDataCardNumberDetailsSelect"
isFullWidth={true}
buttonSize="compressed"
/>
<EuiSpacer size="m" />
{detailsMode === DETAILS_MODE.DISTRIBUTION && (
<Fragment>
<EuiFlexGroup justifyContent="spaceAround" gutterSize="xs">
@ -175,7 +185,6 @@ export const NumberContent: FC<FieldDataCardProps> = ({ config }) => {
</EuiFlexGroup>
</Fragment>
)}
{detailsMode === DETAILS_MODE.TOP_VALUES && (
<EuiFlexGroup>
<EuiFlexItem>

View file

@ -36,30 +36,34 @@ export const OtherContent: FC<FieldDataCardProps> = ({ config }) => {
<Fragment>
<EuiSpacer size="s" />
<div>
<EuiIcon type="document" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardOther.documentsCountDescription"
defaultMessage="{count, plural, zero {# document} one {# document} other {# documents}} ({docsPercent}%)"
values={{
count,
docsPercent,
}}
/>
<EuiText size="xs" color="subdued">
<EuiIcon type="document" />
&nbsp;
<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>
<EuiIcon type="database" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardOther.distinctCountDescription"
defaultMessage="{cardinality} distinct {cardinality, plural, zero {value} one {value} other {values}}"
values={{
cardinality,
}}
/>
<EuiText size="xs" color="subdued">
<EuiIcon type="database" />
&nbsp;
<FormattedMessage
id="xpack.ml.fieldDataCard.cardOther.distinctCountDescription"
defaultMessage="{cardinality} distinct {cardinality, plural, zero {value} one {value} other {values}}"
values={{
cardinality,
}}
/>
</EuiText>
</div>
</Fragment>
)}

View file

@ -27,14 +27,16 @@ export const ExamplesList: FC<Props> = ({ examples }) => {
return (
<div>
<EuiText>
<FormattedMessage
id="xpack.ml.fieldDataCard.cardText.examplesTitle"
defaultMessage="{numExamples, plural, one {value} other {examples}}"
values={{
numExamples: examples.length,
}}
/>
<EuiText size="s">
<h6>
<FormattedMessage
id="xpack.ml.fieldDataCard.cardText.examplesTitle"
defaultMessage="{numExamples, plural, one {value} other {examples}}"
values={{
numExamples: examples.length,
}}
/>
</h6>
</EuiText>
<EuiSpacer size="s" />
{examplesContent}

View file

@ -4,6 +4,7 @@
* 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';
@ -30,11 +31,7 @@ export interface FieldDataCardProps {
}
export const FieldDataCard: FC<FieldDataCardProps> = ({ config }) => {
const { fieldName, loading, type, existsInDocs, stats } = config;
if (stats === undefined) {
return null;
}
const { fieldName, loading, type, existsInDocs } = config;
function getCardContent() {
if (existsInDocs === false) {
@ -73,13 +70,15 @@ export const FieldDataCard: FC<FieldDataCardProps> = ({ config }) => {
}
return (
<div data-test-subj={`mlFieldDataCard ${fieldName} ${type}`}>
<div className="mlFieldDataCard">
<FieldTitleBar card={config} />
<div className="mlFieldDataCard__content">
{loading === true ? <LoadingIndicator /> : getCardContent()}
</div>
<EuiPanel
data-test-subj={`mlFieldDataCard ${fieldName} ${type}`}
className="mlFieldDataCard"
hasShadow={false}
>
<FieldTitleBar card={config} />
<div className="mlFieldDataCard__content">
{loading === true ? <LoadingIndicator /> : getCardContent()}
</div>
</div>
</EuiPanel>
);
};

View file

@ -50,16 +50,16 @@ export const TopValues: FC<Props> = ({ stats, fieldFormat, barColor }) => {
<EuiFlexGroup gutterSize="xs" alignItems="center" key={value.key}>
<EuiFlexItem grow={false} style={{ width: 100 }} className="eui-textTruncate">
<EuiToolTip content={kibanaFieldFormat(value.key, fieldFormat)} position="right">
<EuiText size="s" textAlign="right">
<EuiText size="xs" textAlign="right" color="subdued">
{kibanaFieldFormat(value.key, fieldFormat)}
</EuiText>
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem>
<EuiProgress value={value.doc_count} max={progressBarMax} color={barColor} size="l" />
<EuiProgress value={value.doc_count} max={progressBarMax} color={barColor} size="m" />
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ width: 70 }} className="eui-textTruncate">
<EuiText size="s" textAlign="left">
<EuiText size="xs" textAlign="left" color="subdued">
{getPercentLabel(value.doc_count, progressBarMax)}
</EuiText>
</EuiFlexItem>

View file

@ -7,15 +7,17 @@
import React, { FC } from 'react';
import {
EuiCheckbox,
EuiBadge,
EuiFlexGrid,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
// @ts-ignore
EuiSearchBar,
EuiSpacer,
EuiSwitch,
EuiText,
EuiTitle,
EuiToolTip,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
@ -78,51 +80,62 @@ export const FieldsPanel: FC<Props> = ({
}
return (
<EuiPanel data-test-subj={`mlDataVisualizerFieldsPanel ${fieldTypes}`}>
<EuiTitle>
<h2>{title}</h2>
</EuiTitle>
<EuiSpacer size="s" />
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={false}>
<span>
{showAllFields === true ? (
<FormattedMessage
id="xpack.ml.datavisualizer.fieldsPanel.showAllCountDescription"
defaultMessage="{wrappedCardsCount} {cardsCount, plural, one {field} other {fields}} ({wrappedPopulatedFieldCount} {populatedFieldCount, plural, one {exists} other {exist}} in documents)"
values={{
cardsCount: fieldVisConfigs.length,
wrappedCardsCount: <b>{fieldVisConfigs.length}</b>,
populatedFieldCount,
wrappedPopulatedFieldCount: <b>{populatedFieldCount}</b>,
}}
/>
) : (
<FormattedMessage
id="xpack.ml.datavisualizer.fieldsPanel.fieldsCountDescription"
defaultMessage="{wrappedCardsCount} {cardsCount, plural, one {field exists} other {fields exist}} in documents ({wrappedTotalFieldCount} in total)"
values={{
cardsCount: fieldVisConfigs.length,
wrappedCardsCount: <b>{fieldVisConfigs.length}</b>,
wrappedTotalFieldCount: <b>{totalFieldCount}</b>,
}}
/>
<div data-test-subj={`mlDataVisualizerFieldsPanel ${fieldTypes}`}>
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
<EuiFlexItem>
<EuiFlexGroup alignItems="center" gutterSize="m">
<EuiFlexItem grow={false}>
<EuiTitle size="m">
<h2>{title}</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiToolTip
position="top"
content={
<FormattedMessage
id="xpack.ml.datavisualizer.fieldsPanel.countDescription"
defaultMessage="{cardsCount} {cardsCount, plural, one {field exists} other {fields exist}} in documents sampled"
values={{ cardsCount: populatedFieldCount }}
/>
}
>
<EuiBadge title="">
<b>{populatedFieldCount}</b>
</EuiBadge>
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
<FormattedMessage
id="xpack.ml.datavisualizer.fieldsPanel.totalFieldLabel"
defaultMessage="Total fields: {wrappedTotalFields}"
values={{
wrappedTotalFields: <b>{totalFieldCount}</b>,
}}
/>
</EuiText>
</EuiFlexItem>
{populatedFieldCount < totalFieldCount && (
<EuiFlexItem grow={false}>
<EuiSwitch
id={`${title}_show_empty_fields`}
label={i18n.translate(
'xpack.ml.datavisualizer.fieldsPanel.showEmptyFieldsLabel',
{
defaultMessage: 'Show empty fields',
}
)}
checked={showAllFields}
onChange={onShowAllFieldsChange}
data-test-subj="mlDataVisualizerShowEmptyFieldsCheckbox"
compressed
/>
</EuiFlexItem>
)}
</span>
</EuiFlexGroup>
</EuiFlexItem>
{populatedFieldCount < totalFieldCount && (
<EuiFlexItem>
<EuiCheckbox
id={`${title}_show_empty_fields`}
label={i18n.translate('xpack.ml.datavisualizer.fieldsPanel.showEmptyFieldsLabel', {
defaultMessage: 'show empty fields',
})}
checked={showAllFields}
onChange={onShowAllFieldsChange}
data-test-subj="mlDataVisualizerShowEmptyFieldsCheckbox"
/>
</EuiFlexItem>
)}
<EuiFlexItem grow={true}>
<EuiFlexGroup alignItems="center" gutterSize="m" direction="rowReverse">
<EuiFlexItem
@ -135,9 +148,7 @@ export const FieldsPanel: FC<Props> = ({
box={{
placeholder: i18n.translate(
'xpack.ml.datavisualizer.fieldsPanel.filterFieldsPlaceholder',
{
defaultMessage: 'filter',
}
{ defaultMessage: 'Filter {type}', values: { type: title } }
),
}}
onChange={onSearchBarChange}
@ -156,14 +167,19 @@ export const FieldsPanel: FC<Props> = ({
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiSpacer size="m" />
<EuiFlexGrid gutterSize="m">
{fieldVisConfigs.map((visConfig, i) => (
<EuiFlexItem key={`card_${i}`} style={{ minWidth: '360px' }}>
<FieldDataCard config={visConfig} />
</EuiFlexItem>
))}
{fieldVisConfigs
.filter(({ stats }) => stats !== undefined)
.map((visConfig, i) => (
<EuiFlexItem
key={`${visConfig.fieldName}_${visConfig.stats.count}`}
style={{ minWidth: '360px' }}
>
<FieldDataCard config={visConfig} />
</EuiFlexItem>
))}
</EuiFlexGrid>
</EuiPanel>
</div>
);
};

View file

@ -13,9 +13,8 @@ import {
EuiForm,
EuiFormRow,
EuiIconTip,
EuiPanel,
EuiSelect,
EuiSpacer,
EuiSuperSelect,
EuiText,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
@ -41,6 +40,25 @@ interface Props {
totalCount: number;
}
const searchSizeOptions = [1000, 5000, 10000, 100000, -1].map(v => {
return {
value: String(v),
inputDisplay:
v > 0 ? (
<FormattedMessage
id="xpack.ml.datavisualizer.searchPanel.sampleSizeOptionLabel"
defaultMessage="Sample size (per shard): {wrappedValue}"
values={{ wrappedValue: <b>{v}</b> }}
/>
) : (
<FormattedMessage
id="xpack.ml.datavisualizer.searchPanel.allOptionLabel"
defaultMessage="Search all"
/>
),
};
});
export const SearchPanel: FC<Props> = ({
indexPattern,
searchString,
@ -52,94 +70,81 @@ export const SearchPanel: FC<Props> = ({
setSamplerShardSize,
totalCount,
}) => {
const searchAllOptionText = i18n.translate('xpack.ml.datavisualizer.searchPanel.allOptionLabel', {
defaultMessage: 'all',
});
const searchSizeOptions = [
{ value: '1000', text: '1000' },
{ value: '5000', text: '5000' },
{ value: '10000', text: '10000' },
{ value: '100000', text: '100000' },
{ value: '-1', text: searchAllOptionText },
];
const searchHandler = (d: Record<string, any>) => {
setSearchQuery(d.filterQuery);
};
return (
<EuiPanel grow={false} data-test-subj="mlDataVisualizerSearchPanel">
{searchQueryLanguage === SEARCH_QUERY_LANGUAGE.KUERY ? (
<KqlFilterBar
indexPattern={indexPattern}
onSubmit={searchHandler}
initialValue={searchString}
placeholder={i18n.translate(
'xpack.ml.datavisualizer.searchPanel.queryBarPlaceholderText',
{
defaultMessage: 'Search… (e.g. status:200 AND extension:"PHP")',
}
)}
/>
) : (
<EuiForm>
<EuiFormRow
helpText={i18n.translate('xpack.ml.datavisualizer.searchPanel.kqlEditOnlyLabel', {
defaultMessage: 'Currently only KQL saved searches can be edited',
})}
>
<EuiFieldSearch
value={`${searchString}`}
readOnly
data-test-subj="mlDataVisualizerLuceneSearchBarl"
<EuiFlexGroup gutterSize="m" alignItems="center" data-test-subj="mlDataVisualizerSearchPanel">
<EuiFlexItem>
{searchQueryLanguage === SEARCH_QUERY_LANGUAGE.KUERY ? (
<KqlFilterBar
indexPattern={indexPattern}
onSubmit={searchHandler}
initialValue={searchString}
placeholder={i18n.translate(
'xpack.ml.datavisualizer.searchPanel.queryBarPlaceholderText',
{
defaultMessage: 'Search… (e.g. status:200 AND extension:"PHP")',
}
)}
/>
) : (
<EuiForm>
<EuiFormRow
helpText={i18n.translate('xpack.ml.datavisualizer.searchPanel.kqlEditOnlyLabel', {
defaultMessage: 'Currently only KQL saved searches can be edited',
})}
>
<EuiFieldSearch
value={`${searchString}`}
readOnly
data-test-subj="mlDataVisualizerLuceneSearchBarl"
/>
</EuiFormRow>
</EuiForm>
)}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
<EuiFlexItem grow={false} style={{ width: 270 }}>
<EuiSuperSelect
options={searchSizeOptions}
valueOfSelected={String(samplerShardSize)}
onChange={value => setSamplerShardSize(+value)}
aria-label={i18n.translate(
'xpack.ml.datavisualizer.searchPanel.sampleSizeAriaLabel',
{
defaultMessage: 'Select number of documents to sample',
}
)}
data-test-subj="mlDataVisualizerShardSizeSelect"
/>
</EuiFormRow>
</EuiForm>
)}
<EuiSpacer size="l" />
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIconTip
content={i18n.translate('xpack.ml.datavisualizer.searchPanel.queryBarPlaceholder', {
defaultMessage:
'Selecting a smaller sample size will reduce query run times and the load on the cluster.',
})}
position="right"
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">
<FormattedMessage
id="xpack.ml.datavisualizer.searchPanel.sampleLabel"
defaultMessage="Sample"
id="xpack.ml.datavisualizer.searchPanel.documentsPerShardLabel"
defaultMessage="Total documents: {wrappedTotalCount}"
values={{
wrappedTotalCount: <b data-test-subj="mlDataVisualizerTotalDocCount">{totalCount}</b>,
}}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSelect
options={searchSizeOptions}
value={samplerShardSize}
onChange={e => setSamplerShardSize(+e.target.value)}
aria-label={i18n.translate('xpack.ml.datavisualizer.searchPanel.sampleSizeAriaLabel', {
defaultMessage: 'Select number of documents to sample',
})}
data-test-subj="mlDataVisualizerShardSizeSelect"
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<span>
<FormattedMessage
id="xpack.ml.datavisualizer.searchPanel.documentsPerShardLabel"
defaultMessage="documents per shard from a total of {wrappedTotalCount} {totalCount, plural, one {document} other {documents}}"
values={{
wrappedTotalCount: (
<b data-test-subj="mlDataVisualizerTotalDocCount">{totalCount}</b>
),
totalCount,
}}
/>
</span>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIconTip
content={i18n.translate('xpack.ml.datavisualizer.searchPanel.queryBarPlaceholder', {
defaultMessage:
'Selecting a smaller sample size will reduce query run times and the load on the cluster.',
})}
position="right"
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false} />
</EuiFlexGroup>
);
};

View file

@ -13,11 +13,13 @@ import { timefilter } from 'ui/timefilter';
import {
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiPage,
EuiPageBody,
EuiPageContentBody,
EuiPageContentHeader,
EuiPageContentHeaderSection,
EuiPanel,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
@ -585,84 +587,96 @@ export const Page: FC = () => {
setNonMetricConfigs(configs);
}
const wizardPanelWidth = '280px';
return (
<Fragment>
<NavigationMenu tabId="datavisualizer" />
<EuiPage data-test-subj="mlPageIndexDataVisualizer">
<EuiPageBody>
<EuiPageContentHeader>
<EuiPageContentHeaderSection>
<EuiTitle size="l">
<h1>{currentIndexPattern.title}</h1>
</EuiTitle>
</EuiPageContentHeaderSection>
{currentIndexPattern.timeFieldName !== undefined && (
<EuiPageContentHeaderSection data-test-subj="mlDataVisualizerTimeRangeSelectorSection">
<FullTimeRangeSelector
indexPattern={currentIndexPattern}
query={combinedQuery}
disabled={false}
/>
</EuiPageContentHeaderSection>
)}
</EuiPageContentHeader>
<EuiFlexGroup gutterSize="m">
<EuiFlexItem>
<EuiPageContentHeader>
<EuiPageContentHeaderSection>
<EuiTitle size="l">
<h1>{currentIndexPattern.title}</h1>
</EuiTitle>
</EuiPageContentHeaderSection>
{currentIndexPattern.timeFieldName !== undefined && (
<EuiPageContentHeaderSection data-test-subj="mlDataVisualizerTimeRangeSelectorSection">
<FullTimeRangeSelector
indexPattern={currentIndexPattern}
query={combinedQuery}
disabled={false}
/>
</EuiPageContentHeaderSection>
)}
</EuiPageContentHeader>
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ width: wizardPanelWidth }} />
</EuiFlexGroup>
<EuiSpacer size="m" />
<EuiPageContentBody>
<EuiFlexGroup gutterSize="m">
<EuiFlexItem>
<SearchPanel
indexPattern={currentIndexPattern}
searchString={searchString}
setSearchString={setSearchString}
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
searchQueryLanguage={searchQueryLanguage}
samplerShardSize={samplerShardSize}
setSamplerShardSize={setSamplerShardSize}
totalCount={overallStats.totalCount}
/>
<EuiSpacer size="m" />
<EuiFlexGroup gutterSize="m">
<EuiFlexItem>
{totalMetricFieldCount > 0 && (
<Fragment>
<FieldsPanel
title={i18n.translate('xpack.ml.datavisualizer.page.metricsPanelTitle', {
defaultMessage: 'Metrics',
})}
totalFieldCount={totalMetricFieldCount}
populatedFieldCount={populatedMetricFieldCount}
fieldTypes={[ML_JOB_FIELD_TYPES.NUMBER]}
showFieldType={ML_JOB_FIELD_TYPES.NUMBER}
showAllFields={showAllMetrics}
setShowAllFields={setShowAllMetrics}
fieldSearchBarQuery={metricFieldQuery}
setFieldSearchBarQuery={setMetricFieldQuery}
fieldVisConfigs={metricConfigs}
/>
<EuiSpacer size="m" />
</Fragment>
)}
<FieldsPanel
title={i18n.translate('xpack.ml.datavisualizer.page.fieldsPanelTitle', {
defaultMessage: 'Fields',
})}
totalFieldCount={totalNonMetricFieldCount}
populatedFieldCount={populatedNonMetricFieldCount}
showAllFields={showAllNonMetrics}
setShowAllFields={setShowAllNonMetrics}
fieldTypes={indexedFieldTypes}
showFieldType={nonMetricShowFieldType}
setShowFieldType={setNonMetricShowFieldType}
fieldSearchBarQuery={nonMetricFieldQuery}
setFieldSearchBarQuery={setNonMetricFieldQuery}
fieldVisConfigs={nonMetricConfigs}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiPanel>
<SearchPanel
indexPattern={currentIndexPattern}
searchString={searchString}
setSearchString={setSearchString}
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
searchQueryLanguage={searchQueryLanguage}
samplerShardSize={samplerShardSize}
setSamplerShardSize={setSamplerShardSize}
totalCount={overallStats.totalCount}
/>
<EuiHorizontalRule />
<EuiFlexGroup gutterSize="m">
<EuiFlexItem>
{totalMetricFieldCount > 0 && (
<Fragment>
<FieldsPanel
title={i18n.translate(
'xpack.ml.datavisualizer.page.metricsPanelTitle',
{
defaultMessage: 'Metrics',
}
)}
totalFieldCount={totalMetricFieldCount}
populatedFieldCount={populatedMetricFieldCount}
fieldTypes={[ML_JOB_FIELD_TYPES.NUMBER]}
showFieldType={ML_JOB_FIELD_TYPES.NUMBER}
showAllFields={showAllMetrics}
setShowAllFields={setShowAllMetrics}
fieldSearchBarQuery={metricFieldQuery}
setFieldSearchBarQuery={setMetricFieldQuery}
fieldVisConfigs={metricConfigs}
/>
<EuiSpacer size="xl" />
</Fragment>
)}
<FieldsPanel
title={i18n.translate('xpack.ml.datavisualizer.page.fieldsPanelTitle', {
defaultMessage: 'Fields',
})}
totalFieldCount={totalNonMetricFieldCount}
populatedFieldCount={populatedNonMetricFieldCount}
showAllFields={showAllNonMetrics}
setShowAllFields={setShowAllNonMetrics}
fieldTypes={indexedFieldTypes}
showFieldType={nonMetricShowFieldType}
setShowFieldType={setNonMetricShowFieldType}
fieldSearchBarQuery={nonMetricFieldQuery}
setFieldSearchBarQuery={setNonMetricFieldQuery}
fieldVisConfigs={nonMetricConfigs}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
{showActionsPanel === true && (
<EuiFlexItem grow={false} style={{ width: '280px' }}>
<EuiFlexItem grow={false} style={{ width: wizardPanelWidth }}>
<ActionsPanel indexPattern={currentIndexPattern} />
</EuiFlexItem>
)}

View file

@ -7782,10 +7782,7 @@
"xpack.ml.datavisualizer.actionsPanel.createJobTitle": "ジョブの作成",
"xpack.ml.datavisualizer.actionsPanel.selectKnownConfigurationDescription": "認識されたデータの既知の構成を選択します:",
"xpack.ml.datavisualizer.dataLoader.internalServerErrorMessage": "インデックス {index} のデータの読み込み中にエラーが発生。{message}。リクエストがタイムアウトした可能性があります。小さなサンプルサイズを使うか、時間範囲を狭めてみてください。",
"xpack.ml.datavisualizer.fieldsPanel.fieldsCountDescription": "ドキュメントに {wrappedCardsCount} {cardsCount, plural, one {個のフィールドが存在します} other {個のフィールドが存在します}} (合計 {wrappedTotalFieldCount} 個)",
"xpack.ml.datavisualizer.fieldsPanel.filterFieldsPlaceholder": "フィルター",
"xpack.ml.datavisualizer.fieldsPanel.searchBarError": "検索の実行中にエラーが発生。{message}。",
"xpack.ml.datavisualizer.fieldsPanel.showAllCountDescription": "{wrappedCardsCount} {cardsCount, plural, one {field} other {fields}} ({wrappedPopulatedFieldCount} {populatedFieldCount, plural, one {exists} other {exist}}ドキュメントに)",
"xpack.ml.datavisualizer.fieldsPanel.showEmptyFieldsLabel": "空のフィールドを表示",
"xpack.ml.datavisualizer.fieldTypesSelect.allFieldsTypeOptionLabel": "すべてのフィールドタイプ",
"xpack.ml.datavisualizer.fieldTypesSelect.selectAriaLabel": "表示するフィールドタイプを選択してください",
@ -7795,11 +7792,9 @@
"xpack.ml.datavisualizer.page.fieldsPanelTitle": "フィールド",
"xpack.ml.datavisualizer.page.metricsPanelTitle": "メトリック",
"xpack.ml.datavisualizer.searchPanel.allOptionLabel": "すべて",
"xpack.ml.datavisualizer.searchPanel.documentsPerShardLabel": "合計 {wrappedTotalCount} {totalCount, plural, one {document} other {documents}}からのシャードごとのドキュメント数",
"xpack.ml.datavisualizer.searchPanel.kqlEditOnlyLabel": "現在 KQAL で保存された検索のみ編集できます。",
"xpack.ml.datavisualizer.searchPanel.queryBarPlaceholder": "小さいサンプルサイズを選択することで、クエリの実行時間を短縮しクラスターへの負荷を軽減できます。",
"xpack.ml.datavisualizer.searchPanel.queryBarPlaceholderText": "検索… (例: status:200 AND extension:\"PHP\")",
"xpack.ml.datavisualizer.searchPanel.sampleLabel": "サンプル",
"xpack.ml.datavisualizer.searchPanel.sampleSizeAriaLabel": "サンプリングするドキュメント数を選択してください",
"xpack.ml.datavisualizer.selector.dataVisualizerDescription": "機械学習データビジュアライザーツールは、ログファイルのメトリックとフィールド、または既存の Elasticsearch インデックスを分析し、データの理解を助けます。",
"xpack.ml.datavisualizer.selector.dataVisualizerTitle": "データビジュアライザー",

View file

@ -7781,10 +7781,7 @@
"xpack.ml.datavisualizer.actionsPanel.createJobTitle": "创建作业",
"xpack.ml.datavisualizer.actionsPanel.selectKnownConfigurationDescription": "选择已识别数据的已知配置:",
"xpack.ml.datavisualizer.dataLoader.internalServerErrorMessage": "加载索引 {index} 中的数据时出错。{message}。请求可能已超时。请尝试使用较小的样例大小或缩小时间范围。",
"xpack.ml.datavisualizer.fieldsPanel.fieldsCountDescription": "{wrappedCardsCount} {cardsCount, plural, one { 个字段存在} other { 个字段存在}}于文档中(共 {wrappedTotalFieldCount} 个)",
"xpack.ml.datavisualizer.fieldsPanel.filterFieldsPlaceholder": "筛选",
"xpack.ml.datavisualizer.fieldsPanel.searchBarError": "运行搜索时发生错误。{message}。",
"xpack.ml.datavisualizer.fieldsPanel.showAllCountDescription": "{wrappedCardsCount} {cardsCount, plural, one { 个字段} other { 个字段}}{wrappedPopulatedFieldCount} {populatedFieldCount, plural, one { 存在} other { 存在}}于文档中)",
"xpack.ml.datavisualizer.fieldsPanel.showEmptyFieldsLabel": "显示空字段",
"xpack.ml.datavisualizer.fieldTypesSelect.allFieldsTypeOptionLabel": "所有字段类型",
"xpack.ml.datavisualizer.fieldTypesSelect.selectAriaLabel": "选择要显示的字段类型",
@ -7794,11 +7791,9 @@
"xpack.ml.datavisualizer.page.fieldsPanelTitle": "字段",
"xpack.ml.datavisualizer.page.metricsPanelTitle": "指标",
"xpack.ml.datavisualizer.searchPanel.allOptionLabel": "全部",
"xpack.ml.datavisualizer.searchPanel.documentsPerShardLabel": "每个分片的文档,共 {wrappedTotalCount} {totalCount, plural, one { 个文档 } other { 个文档}}",
"xpack.ml.datavisualizer.searchPanel.kqlEditOnlyLabel": "当前仅可以编辑 KQL 已保存搜索",
"xpack.ml.datavisualizer.searchPanel.queryBarPlaceholder": "选择较小的样例大小将减少查询运行时间和集群上的负载。",
"xpack.ml.datavisualizer.searchPanel.queryBarPlaceholderText": "搜索……例如status:200 AND extension:\"PHP\"",
"xpack.ml.datavisualizer.searchPanel.sampleLabel": "采样",
"xpack.ml.datavisualizer.searchPanel.sampleSizeAriaLabel": "选择要采样的文档数目",
"xpack.ml.datavisualizer.selector.dataVisualizerDescription": "Machine Learning Data Visualizer 工具通过分析日志文件或现有 Elasticsearch 索引中的指标和字段,帮助您理解数据。",
"xpack.ml.datavisualizer.selector.dataVisualizerTitle": "数据可视化工具",