[ML] Add options to exclude or include frozen data tier for Anomaly detection and Index data visualizer (#122306)

* Add exclude frozen data tier

* Rename util name to addExcludeFrozenToQuery for clarity

* Add menu option to let user pick

* Add menu option to let user pick

* Fix match_all which is not valid if we want to exclude

* Update snapshot

* Update texts

* Fix translations

* Add storage pref & radio

* Update dv selector, snapshots

* Fix message

* Update texts

* Update snapshots

* Put in useMemo

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Quynh Nguyen 2022-01-14 12:07:13 -06:00 committed by GitHub
parent 703a451e41
commit 600abd7535
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 705 additions and 79 deletions

View file

@ -5,22 +5,45 @@
* 2.0.
*/
import React, { FC } from 'react';
import React, { FC, useCallback, useMemo, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { Query, IndexPattern, TimefilterContract } from 'src/plugins/data/public';
import { EuiButton } from '@elastic/eui';
import { TimefilterContract } from 'src/plugins/data/public';
import { DataView } from 'src/plugins/data/common';
import {
EuiButton,
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiPopover,
EuiRadioGroup,
EuiRadioGroupOption,
EuiToolTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { setFullTimeRange } from './full_time_range_selector_service';
import { useDataVisualizerKibana } from '../../../kibana_context';
import { DV_FROZEN_TIER_PREFERENCE, useStorage } from '../../hooks/use_storage';
export const ML_FROZEN_TIER_PREFERENCE = 'ml.frozenDataTierPreference';
interface Props {
timefilter: TimefilterContract;
indexPattern: IndexPattern;
indexPattern: DataView;
disabled: boolean;
query?: Query;
query?: QueryDslQueryContainer;
callback?: (a: any) => void;
}
const FROZEN_TIER_PREFERENCE = {
EXCLUDE: 'exclude-frozen',
INCLUDE: 'include-frozen',
} as const;
type FrozenTierPreference = typeof FROZEN_TIER_PREFERENCE[keyof typeof FROZEN_TIER_PREFERENCE];
// Component for rendering a button which automatically sets the range of the time filter
// to the time range of data in the index(es) mapped to the supplied Kibana data view or query.
export const FullTimeRangeSelector: FC<Props> = ({
@ -37,36 +60,144 @@ export const FullTimeRangeSelector: FC<Props> = ({
} = useDataVisualizerKibana();
// wrapper around setFullTimeRange to allow for the calling of the optional callBack prop
async function setRange(i: IndexPattern, q?: Query) {
try {
const fullTimeRange = await setFullTimeRange(timefilter, i, q);
if (typeof callback === 'function') {
callback(fullTimeRange);
const setRange = useCallback(
async (i: DataView, q?: QueryDslQueryContainer, excludeFrozenData?: boolean) => {
try {
const fullTimeRange = await setFullTimeRange(timefilter, i, q, excludeFrozenData);
if (typeof callback === 'function') {
callback(fullTimeRange);
}
} catch (e) {
toasts.addDanger(
i18n.translate(
'xpack.dataVisualizer.index.fullTimeRangeSelector.errorSettingTimeRangeNotification',
{
defaultMessage: 'An error occurred setting the time range.',
}
)
);
}
} catch (e) {
toasts.addDanger(
i18n.translate(
'xpack.dataVisualizer.index.fullTimeRangeSelector.errorSettingTimeRangeNotification',
},
[callback, timefilter, toasts]
);
const [isPopoverOpen, setPopover] = useState(false);
const [frozenDataPreference, setFrozenDataPreference] = useStorage<FrozenTierPreference>(
DV_FROZEN_TIER_PREFERENCE,
// By default we will exclude frozen data tier
FROZEN_TIER_PREFERENCE.EXCLUDE
);
const setPreference = useCallback(
(id: string) => {
setFrozenDataPreference(id as FrozenTierPreference);
setRange(indexPattern, query, id === FROZEN_TIER_PREFERENCE.EXCLUDE);
closePopover();
},
[indexPattern, query, setFrozenDataPreference, setRange]
);
const onButtonClick = () => {
setPopover(!isPopoverOpen);
};
const closePopover = () => {
setPopover(false);
};
const sortOptions: EuiRadioGroupOption[] = useMemo(() => {
return [
{
id: FROZEN_TIER_PREFERENCE.EXCLUDE,
label: i18n.translate(
'xpack.dataVisualizer.index.fullTimeRangeSelector.useFullDataExcludingFrozenMenuLabel',
{
defaultMessage: 'An error occurred setting the time range.',
defaultMessage: 'Exclude frozen data tier',
}
)
);
}
}
),
},
{
id: FROZEN_TIER_PREFERENCE.INCLUDE,
label: i18n.translate(
'xpack.dataVisualizer.index.fullTimeRangeSelector.useFullDataIncludingFrozenMenuLabel',
{
defaultMessage: 'Include frozen data tier',
}
),
},
];
}, []);
const popoverContent = useMemo(
() => (
<EuiPanel>
<EuiRadioGroup
options={sortOptions}
idSelected={frozenDataPreference}
onChange={setPreference}
compressed
/>
</EuiPanel>
),
[sortOptions, frozenDataPreference, setPreference]
);
const buttonTooltip = useMemo(
() =>
frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE ? (
<FormattedMessage
id="xpack.dataVisualizer.fullTimeRangeSelector.useFullDataExcludingFrozenButtonTooltip"
defaultMessage="Use full range of data excluding frozen data tier."
/>
) : (
<FormattedMessage
id="xpack.dataVisualizer.fullTimeRangeSelector.useFullDataIncludingFrozenButtonTooltip"
defaultMessage="Use full range of data including frozen data tier, which might have slower search results."
/>
),
[frozenDataPreference]
);
return (
<EuiButton
isDisabled={disabled}
onClick={() => setRange(indexPattern, query)}
data-test-subj="dataVisualizerButtonUseFullData"
>
<FormattedMessage
id="xpack.dataVisualizer.index.fullTimeRangeSelector.useFullDataButtonLabel"
defaultMessage="Use full {indexPatternTitle} data"
values={{
indexPatternTitle: indexPattern.title,
}}
/>
</EuiButton>
<EuiFlexGroup responsive={false} gutterSize="xs" alignItems="center">
<EuiToolTip content={buttonTooltip}>
<EuiButton
isDisabled={disabled}
onClick={() => setRange(indexPattern, query, true)}
data-test-subj="dataVisualizerButtonUseFullData"
>
<FormattedMessage
id="xpack.dataVisualizer.index.fullTimeRangeSelector.useFullDataButtonLabel"
defaultMessage="Use full data"
/>
</EuiButton>
</EuiToolTip>
<EuiFlexItem grow={false}>
<EuiPopover
id={'mlFullTimeRangeSelectorOption'}
button={
<EuiButtonIcon
display="base"
size="m"
iconType="boxesVertical"
aria-label={i18n.translate(
'xpack.dataVisualizer.index.fullTimeRangeSelector.moreOptionsButtonAriaLabel',
{
defaultMessage: 'More options',
}
)}
onClick={onButtonClick}
/>
}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
anchorPosition="downRight"
>
{popoverContent}
</EuiPopover>
</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -7,12 +7,14 @@
import moment from 'moment';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { Query, TimefilterContract } from 'src/plugins/data/public';
import { TimefilterContract } from 'src/plugins/data/public';
import dateMath from '@elastic/datemath';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { IndexPattern } from '../../../../../../../../src/plugins/data/public';
import { isPopulatedObject } from '../../../../../common/utils/object_utils';
import { getTimeFieldRange } from '../../services/time_field_range';
import { GetTimeFieldRangeResponse } from '../../../../../common/types/time_field_request';
import { addExcludeFrozenToQuery } from '../../utils/query_utils';
export interface TimeRange {
from: number;
@ -22,14 +24,15 @@ export interface TimeRange {
export async function setFullTimeRange(
timefilter: TimefilterContract,
indexPattern: IndexPattern,
query?: Query
query?: QueryDslQueryContainer,
excludeFrozenData?: boolean
): Promise<GetTimeFieldRangeResponse> {
const runtimeMappings = indexPattern.getComputedFields()
.runtimeFields as estypes.MappingRuntimeFields;
const resp = await getTimeFieldRange({
index: indexPattern.title,
timeFieldName: indexPattern.timeFieldName,
query,
query: excludeFrozenData ? addExcludeFrozenToQuery(query) : query,
...(isPopulatedObject(runtimeMappings) ? { runtimeMappings } : {}),
});
timefilter.setTime({

View file

@ -0,0 +1,44 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useCallback, useState } from 'react';
import { useDataVisualizerKibana } from '../../kibana_context';
export const DV_FROZEN_TIER_PREFERENCE = 'dataVisualizer.frozenDataTierPreference';
export type DV = Partial<{
[DV_FROZEN_TIER_PREFERENCE]: 'exclude_frozen' | 'include_frozen';
}> | null;
export type DVKey = keyof Exclude<DV, null>;
/**
* Hook for accessing and changing a value in the storage.
* @param key - Storage key
* @param initValue
*/
export function useStorage<T>(key: DVKey, initValue?: T): [T, (value: T) => void] {
const {
services: { storage },
} = useDataVisualizerKibana();
const [val, setVal] = useState<T>(storage.get(key) ?? initValue);
const setStorage = useCallback(
(value: T): void => {
try {
storage.set(key, value);
setVal(value);
} catch (e) {
throw new Error('Unable to update storage with provided value');
}
},
[key, storage]
);
return [val, setStorage];
}

View file

@ -6,9 +6,9 @@
*/
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { lazyLoadModules } from '../../../lazy_load_bundle';
import { GetTimeFieldRangeResponse } from '../../../../common/types/time_field_request';
import { Query } from '../../../../../../../src/plugins/data/common/query';
export async function getTimeFieldRange({
index,
@ -18,7 +18,7 @@ export async function getTimeFieldRange({
}: {
index: string;
timeFieldName?: string;
query?: Query;
query?: QueryDslQueryContainer;
runtimeMappings?: estypes.MappingRuntimeFields;
}) {
const body = JSON.stringify({ index, timeFieldName, query, runtimeMappings });

View file

@ -0,0 +1,76 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { addExcludeFrozenToQuery } from './query_utils';
describe('Util: addExcludeFrozenToQuery()', () => {
test('Validation checks.', () => {
expect(
addExcludeFrozenToQuery({
match_all: {},
bool: {
must: [
{
match_all: {},
},
],
},
})
).toMatchObject({
bool: {
must: [{ match_all: {} }],
must_not: [{ term: { _tier: { value: 'data_frozen' } } }],
},
});
expect(
addExcludeFrozenToQuery({
bool: {
must: [],
must_not: {
term: {
category: {
value: 'clothing',
},
},
},
},
})
).toMatchObject({
bool: {
must: [],
must_not: [
{ term: { category: { value: 'clothing' } } },
{ term: { _tier: { value: 'data_frozen' } } },
],
},
});
expect(
addExcludeFrozenToQuery({
bool: {
must: [],
must_not: [{ term: { category: { value: 'clothing' } } }],
},
})
).toMatchObject({
bool: {
must: [],
must_not: [
{ term: { category: { value: 'clothing' } } },
{ term: { _tier: { value: 'data_frozen' } } },
],
},
});
expect(addExcludeFrozenToQuery(undefined)).toMatchObject({
bool: {
must_not: [{ term: { _tier: { value: 'data_frozen' } } }],
},
});
});
});

View file

@ -0,0 +1,53 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { cloneDeep } from 'lodash';
import { isPopulatedObject } from '../../../../common/utils/object_utils';
export const addExcludeFrozenToQuery = (originalQuery: QueryDslQueryContainer | undefined) => {
const FROZEN_TIER_TERM = {
term: {
_tier: {
value: 'data_frozen',
},
},
};
if (!originalQuery) {
return {
bool: {
must_not: [FROZEN_TIER_TERM],
},
};
}
const query = cloneDeep(originalQuery);
delete query.match_all;
if (isPopulatedObject(query.bool)) {
// Must_not can be both arrays or singular object
if (Array.isArray(query.bool.must_not)) {
query.bool.must_not.push(FROZEN_TIER_TERM);
} else {
// If there's already a must_not condition
if (isPopulatedObject(query.bool.must_not)) {
query.bool.must_not = [query.bool.must_not, FROZEN_TIER_TERM];
}
if (query.bool.must_not === undefined) {
query.bool.must_not = [FROZEN_TIER_TERM];
}
}
} else {
query.bool = {
must_not: [FROZEN_TIER_TERM],
};
}
return query;
};

View file

@ -8,7 +8,11 @@
import { CoreStart } from 'kibana/public';
import { KibanaReactContextValue, useKibana } from '../../../../../src/plugins/kibana_react/public';
import type { DataVisualizerStartDependencies } from '../plugin';
import type { IStorageWrapper } from '../../../../../src/plugins/kibana_utils/public';
export type StartServices = CoreStart & DataVisualizerStartDependencies;
export type StartServices = CoreStart &
DataVisualizerStartDependencies & {
storage: IStorageWrapper;
};
export type DataVisualizerKibanaReactContextValue = KibanaReactContextValue<StartServices>;
export const useDataVisualizerKibana = () => useKibana<StartServices>();

View file

@ -6,13 +6,14 @@
*/
import { IScopedClusterClient } from 'kibana/server';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { isPopulatedObject } from '../common/utils';
export async function getTimeFieldRange(
client: IScopedClusterClient,
index: string[] | string,
timeFieldName: string,
query: any,
query: QueryDslQueryContainer,
runtimeMappings?: estypes.MappingRuntimeFields
): Promise<{
success: boolean;

View file

@ -13,6 +13,8 @@ export const ML_APPLY_TIME_RANGE_CONFIG = 'ml.jobSelectorFlyout.applyTimeRange';
export const ML_GETTING_STARTED_CALLOUT_DISMISSED = 'ml.gettingStarted.isDismissed';
export const ML_FROZEN_TIER_PREFERENCE = 'ml.frozenDataTierPreference';
export type PartitionFieldConfig =
| {
/**
@ -44,6 +46,7 @@ export type MlStorage = Partial<{
[ML_ENTITY_FIELDS_CONFIG]: PartitionFieldsConfig;
[ML_APPLY_TIME_RANGE_CONFIG]: ApplyTimeRangeConfig;
[ML_GETTING_STARTED_CALLOUT_DISMISSED]: boolean | undefined;
[ML_FROZEN_TIER_PREFERENCE]: 'exclude_frozen' | 'include_frozen';
}> | null;
export type MlStorageKey = keyof Exclude<MlStorage, null>;

View file

@ -0,0 +1,76 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { addExcludeFrozenToQuery } from './query_utils';
describe('Util: addExcludeFrozenToQuery()', () => {
test('Validation checks.', () => {
expect(
addExcludeFrozenToQuery({
match_all: {},
bool: {
must: [
{
match_all: {},
},
],
},
})
).toMatchObject({
bool: {
must: [{ match_all: {} }],
must_not: [{ term: { _tier: { value: 'data_frozen' } } }],
},
});
expect(
addExcludeFrozenToQuery({
bool: {
must: [],
must_not: {
term: {
category: {
value: 'clothing',
},
},
},
},
})
).toMatchObject({
bool: {
must: [],
must_not: [
{ term: { category: { value: 'clothing' } } },
{ term: { _tier: { value: 'data_frozen' } } },
],
},
});
expect(
addExcludeFrozenToQuery({
bool: {
must: [],
must_not: [{ term: { category: { value: 'clothing' } } }],
},
})
).toMatchObject({
bool: {
must: [],
must_not: [
{ term: { category: { value: 'clothing' } } },
{ term: { _tier: { value: 'data_frozen' } } },
],
},
});
expect(addExcludeFrozenToQuery(undefined)).toMatchObject({
bool: {
must_not: [{ term: { _tier: { value: 'data_frozen' } } }],
},
});
});
});

View file

@ -0,0 +1,53 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { cloneDeep } from 'lodash';
import { isPopulatedObject } from './object_utils';
export const addExcludeFrozenToQuery = (originalQuery: QueryDslQueryContainer | undefined) => {
const FROZEN_TIER_TERM = {
term: {
_tier: {
value: 'data_frozen',
},
},
};
if (!originalQuery) {
return {
bool: {
must_not: [FROZEN_TIER_TERM],
},
};
}
const query = cloneDeep(originalQuery);
delete query.match_all;
if (isPopulatedObject(query.bool)) {
// Must_not can be both arrays or singular object
if (Array.isArray(query.bool.must_not)) {
query.bool.must_not.push(FROZEN_TIER_TERM);
} else {
// If there's already a must_not condition
if (isPopulatedObject(query.bool.must_not)) {
query.bool.must_not = [query.bool.must_not, FROZEN_TIER_TERM];
}
if (query.bool.must_not === undefined) {
query.bool.must_not = [FROZEN_TIER_TERM];
}
}
} else {
query.bool = {
must_not: [FROZEN_TIER_TERM],
};
}
return query;
};

View file

@ -1,19 +1,77 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FullTimeRangeSelector renders the selector 1`] = `
<EuiButton
data-test-subj="mlButtonUseFullData"
isDisabled={false}
onClick={[Function]}
<EuiFlexGroup
alignItems="center"
gutterSize="xs"
responsive={false}
>
<FormattedMessage
defaultMessage="Use full {dataViewTitle} data"
id="xpack.ml.fullTimeRangeSelector.useFullDataButtonLabel"
values={
Object {
"dataViewTitle": "test-data-view",
}
<EuiToolTip
content={
<FormattedMessage
defaultMessage="Use full range of data including frozen data tier, which might have slower search results."
id="xpack.ml.fullTimeRangeSelector.useFullDataIncludingFrozenButtonTooltip"
values={Object {}}
/>
}
/>
</EuiButton>
delay="regular"
display="inlineBlock"
position="top"
>
<EuiButton
data-test-subj="mlButtonUseFullData"
isDisabled={false}
onClick={[Function]}
>
<FormattedMessage
defaultMessage="Use full data"
id="xpack.ml.fullTimeRangeSelector.useFullDataButtonLabel"
values={Object {}}
/>
</EuiButton>
</EuiToolTip>
<EuiFlexItem
grow={false}
>
<EuiPopover
anchorPosition="downRight"
button={
<EuiButtonIcon
aria-label="More"
display="base"
iconType="boxesVertical"
onClick={[Function]}
size="m"
/>
}
closePopover={[Function]}
display="inlineBlock"
hasArrow={true}
id="mlFullTimeRangeSelectorOption"
isOpen={false}
ownFocus={true}
panelPaddingSize="none"
>
<EuiPanel>
<EuiRadioGroup
compressed={true}
idSelected="e"
onChange={[Function]}
options={
Array [
Object {
"id": "exclude-frozen",
"label": "Exclude frozen data tier",
},
Object {
"id": "include-frozen",
"label": "Include frozen data tier",
},
]
}
/>
</EuiPanel>
</EuiPopover>
</EuiFlexItem>
</EuiFlexGroup>
`;

View file

@ -20,6 +20,12 @@ jest.mock('./full_time_range_selector_service', () => ({
mockSetFullTimeRange(indexPattern, query),
}));
jest.mock('../../contexts/ml/use_storage', () => {
return {
useStorage: jest.fn(() => 'exclude-frozen'),
};
});
describe('FullTimeRangeSelector', () => {
const dataView = {
id: '0844fc70-5ab5-11e9-935e-836737467b0f',

View file

@ -5,44 +5,160 @@
* 2.0.
*/
import React, { FC } from 'react';
import React, { FC, useCallback, useMemo, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import type { Query } from 'src/plugins/data/public';
import { EuiButton } from '@elastic/eui';
import {
EuiFlexGroup,
EuiButton,
EuiFlexItem,
EuiButtonIcon,
EuiRadioGroup,
EuiPanel,
EuiToolTip,
EuiPopover,
EuiRadioGroupOption,
} from '@elastic/eui';
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { i18n } from '@kbn/i18n';
import type { DataView } from '../../../../../../../src/plugins/data_views/public';
import { setFullTimeRange } from './full_time_range_selector_service';
import { useStorage } from '../../contexts/ml/use_storage';
import { ML_FROZEN_TIER_PREFERENCE } from '../../../../common/types/storage';
interface Props {
dataView: DataView;
query: Query;
query: QueryDslQueryContainer;
disabled: boolean;
callback?: (a: any) => void;
}
const FROZEN_TIER_PREFERENCE = {
EXCLUDE: 'exclude-frozen',
INCLUDE: 'include-frozen',
} as const;
type FrozenTierPreference = typeof FROZEN_TIER_PREFERENCE[keyof typeof FROZEN_TIER_PREFERENCE];
// Component for rendering a button which automatically sets the range of the time filter
// to the time range of data in the index(es) mapped to the supplied Kibana index pattern or query.
export const FullTimeRangeSelector: FC<Props> = ({ dataView, query, disabled, callback }) => {
// wrapper around setFullTimeRange to allow for the calling of the optional callBack prop
async function setRange(i: DataView, q: Query) {
const fullTimeRange = await setFullTimeRange(i, q);
async function setRange(i: DataView, q: QueryDslQueryContainer, excludeFrozenData = true) {
const fullTimeRange = await setFullTimeRange(i, q, excludeFrozenData);
if (typeof callback === 'function') {
callback(fullTimeRange);
}
}
const [isPopoverOpen, setPopover] = useState(false);
const [frozenDataPreference, setFrozenDataPreference] = useStorage<FrozenTierPreference>(
ML_FROZEN_TIER_PREFERENCE,
FROZEN_TIER_PREFERENCE.EXCLUDE
);
const onButtonClick = () => {
setPopover(!isPopoverOpen);
};
const closePopover = () => {
setPopover(false);
};
const sortOptions: EuiRadioGroupOption[] = useMemo(() => {
return [
{
id: FROZEN_TIER_PREFERENCE.EXCLUDE,
label: i18n.translate(
'xpack.ml.fullTimeRangeSelector.useFullDataExcludingFrozenMenuLabel',
{
defaultMessage: 'Exclude frozen data tier',
}
),
},
{
id: FROZEN_TIER_PREFERENCE.INCLUDE,
label: i18n.translate(
'xpack.ml.fullTimeRangeSelector.useFullDataIncludingFrozenMenuLabel',
{
defaultMessage: 'Include frozen data tier',
}
),
},
];
}, []);
const setPreference = useCallback((id: string) => {
setFrozenDataPreference(id as FrozenTierPreference);
setRange(dataView, query, id === FROZEN_TIER_PREFERENCE.EXCLUDE);
closePopover();
}, []);
const popoverContent = useMemo(
() => (
<EuiPanel>
<EuiRadioGroup
options={sortOptions}
idSelected={frozenDataPreference}
onChange={setPreference}
compressed
/>
</EuiPanel>
),
[frozenDataPreference, sortOptions]
);
const buttonTooltip = useMemo(
() =>
frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE ? (
<FormattedMessage
id="xpack.ml.fullTimeRangeSelector.useFullDataExcludingFrozenButtonTooltip"
defaultMessage="Use full range of data excluding frozen data tier."
/>
) : (
<FormattedMessage
id="xpack.ml.fullTimeRangeSelector.useFullDataIncludingFrozenButtonTooltip"
defaultMessage="Use full range of data including frozen data tier, which might have slower search results."
/>
),
[frozenDataPreference]
);
return (
<EuiButton
isDisabled={disabled}
onClick={() => setRange(dataView, query)}
data-test-subj="mlButtonUseFullData"
>
<FormattedMessage
id="xpack.ml.fullTimeRangeSelector.useFullDataButtonLabel"
defaultMessage="Use full {dataViewTitle} data"
values={{
dataViewTitle: dataView.title,
}}
/>
</EuiButton>
<EuiFlexGroup responsive={false} gutterSize="xs" alignItems="center">
<EuiToolTip content={buttonTooltip}>
<EuiButton
isDisabled={disabled}
onClick={() => setRange(dataView, query, true)}
data-test-subj="mlButtonUseFullData"
>
<FormattedMessage
id="xpack.ml.fullTimeRangeSelector.useFullDataButtonLabel"
defaultMessage="Use full data"
/>
</EuiButton>
</EuiToolTip>
<EuiFlexItem grow={false}>
<EuiPopover
id={'mlFullTimeRangeSelectorOption'}
button={
<EuiButtonIcon
display="base"
size="m"
iconType="boxesVertical"
aria-label="More"
onClick={onButtonClick}
/>
}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
anchorPosition="downRight"
>
{popoverContent}
</EuiPopover>
</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -8,13 +8,14 @@
import moment from 'moment';
import { i18n } from '@kbn/i18n';
import type { Query } from 'src/plugins/data/public';
import dateMath from '@elastic/datemath';
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { getTimefilter, getToastNotifications } from '../../util/dependency_cache';
import { ml, GetTimeFieldRangeResponse } from '../../services/ml_api_service';
import type { DataView } from '../../../../../../../src/plugins/data_views/public';
import { isPopulatedObject } from '../../../../common/util/object_utils';
import { RuntimeMappings } from '../../../../common/types/fields';
import type { RuntimeMappings } from '../../../../common/types/fields';
import { addExcludeFrozenToQuery } from '../../../../common/util/query_utils';
export interface TimeRange {
from: number;
@ -23,7 +24,8 @@ export interface TimeRange {
export async function setFullTimeRange(
indexPattern: DataView,
query: Query
query: QueryDslQueryContainer,
excludeFrozenData: boolean
): Promise<GetTimeFieldRangeResponse> {
try {
const timefilter = getTimefilter();
@ -31,7 +33,8 @@ export async function setFullTimeRange(
const resp = await ml.getTimeFieldRange({
index: indexPattern.title,
timeFieldName: indexPattern.timeFieldName,
query,
// By default we want to use full non-frozen time range
query: excludeFrozenData ? addExcludeFrozenToQuery(query) : query,
...(isPopulatedObject(runtimeMappings) ? { runtimeMappings } : {}),
});
timefilter.setTime({

View file

@ -42,6 +42,7 @@ import { TIME_FORMAT } from '../../../../../common/constants/time_format';
import { JobsAwaitingNodeWarning } from '../../../components/jobs_awaiting_node_warning';
import { isPopulatedObject } from '../../../../../common/util/object_utils';
import { RuntimeMappings } from '../../../../../common/types/fields';
import { addExcludeFrozenToQuery } from '../../../../../common/util/query_utils';
import { MlPageHeader } from '../../../components/page_header';
export interface ModuleJobUI extends ModuleJob {
@ -136,7 +137,8 @@ export const Page: FC<PageProps> = ({ moduleId, existingGroupIds }) => {
const { start, end } = await ml.getTimeFieldRange({
index: dataView.title,
timeFieldName: dataView.timeFieldName,
query: combinedQuery,
// By default we want to use full non-frozen time range
query: addExcludeFrozenToQuery(combinedQuery),
...(isPopulatedObject(runtimeMappings) ? { runtimeMappings } : {}),
});
return {

View file

@ -16,7 +16,6 @@ import { getDatafeedAggregations } from '../../../common/util/datafeed_utils';
import { Datafeed, IndicesOptions } from '../../../common/types/anomaly_detection_jobs';
import { RuntimeMappings } from '../../../common/types/fields';
import { isPopulatedObject } from '../../../common/util/object_utils';
/**
* Service for carrying out queries to obtain data
* specific to fields in Elasticsearch indices.

View file

@ -8388,7 +8388,6 @@
"xpack.dataVisualizer.index.fieldNameSelect": "フィールド名",
"xpack.dataVisualizer.index.fieldTypeSelect": "フィールド型",
"xpack.dataVisualizer.index.fullTimeRangeSelector.errorSettingTimeRangeNotification": "時間範囲の設定中にエラーが発生しました。",
"xpack.dataVisualizer.index.fullTimeRangeSelector.useFullDataButtonLabel": "完全な {indexPatternTitle} データを使用",
"xpack.dataVisualizer.index.indexPatternNotBasedOnTimeSeriesNotificationDescription": "異常検知は時間ベースのインデックスでのみ実行されます",
"xpack.dataVisualizer.index.indexPatternNotBasedOnTimeSeriesNotificationTitle": "インデックスパターン {indexPatternTitle} は時系列に基づくものではありません",
"xpack.dataVisualizer.index.lensChart.averageOfLabel": "{fieldName}の平均",

View file

@ -8461,7 +8461,6 @@
"xpack.dataVisualizer.index.fieldNameSelect": "字段名称",
"xpack.dataVisualizer.index.fieldTypeSelect": "字段类型",
"xpack.dataVisualizer.index.fullTimeRangeSelector.errorSettingTimeRangeNotification": "设置时间范围时出错。",
"xpack.dataVisualizer.index.fullTimeRangeSelector.useFullDataButtonLabel": "使用完整的 {indexPatternTitle} 数据",
"xpack.dataVisualizer.index.indexPatternNotBasedOnTimeSeriesNotificationDescription": "仅针对基于时间的索引运行异常检测",
"xpack.dataVisualizer.index.indexPatternNotBasedOnTimeSeriesNotificationTitle": "索引模式 {indexPatternTitle} 不基于时间序列",
"xpack.dataVisualizer.index.lensChart.averageOfLabel": "{fieldName} 的平均值",