mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[ML] Move DatePickerWrapper and related code to package (#148063)
- Moves duplicates of `DatePickerWrapper` and related code to package `@kbn/ml-date-picker`. The duplicated components across the code base have been consolidated and diverging features combined. Each duplicate has been checked with a diff against the package before deletion. - Moves duplicates of `query_utils.ts` to a package `@kbn/ml-query_utils`. - Some jest test were migrated from enzyme to react-testing-lib. - `i18n` strings and data-test-subjects have been updated to be prefixes in line with package names. - Replaces custom code related to the `compact` flag with EUI's breakpoints.
This commit is contained in:
parent
a275262b8a
commit
ae5594849c
121 changed files with 1305 additions and 2304 deletions
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
|
@ -1078,9 +1078,11 @@ src/plugins/chart_expressions/common @elastic/kibana-visualizations
|
||||||
x-pack/packages/ml/agg_utils @elastic/ml-ui
|
x-pack/packages/ml/agg_utils @elastic/ml-ui
|
||||||
x-pack/packages/ml/aiops_components @elastic/ml-ui
|
x-pack/packages/ml/aiops_components @elastic/ml-ui
|
||||||
x-pack/packages/ml/aiops_utils @elastic/ml-ui
|
x-pack/packages/ml/aiops_utils @elastic/ml-ui
|
||||||
|
x-pack/packages/ml/date_picker @elastic/ml-ui
|
||||||
x-pack/packages/ml/is_defined @elastic/ml-ui
|
x-pack/packages/ml/is_defined @elastic/ml-ui
|
||||||
x-pack/packages/ml/is_populated_object @elastic/ml-ui
|
x-pack/packages/ml/is_populated_object @elastic/ml-ui
|
||||||
x-pack/packages/ml/local_storage @elastic/ml-ui
|
x-pack/packages/ml/local_storage @elastic/ml-ui
|
||||||
x-pack/packages/ml/nested_property @elastic/ml-ui
|
x-pack/packages/ml/nested_property @elastic/ml-ui
|
||||||
|
x-pack/packages/ml/query_utils @elastic/ml-ui
|
||||||
x-pack/packages/ml/string_hash @elastic/ml-ui
|
x-pack/packages/ml/string_hash @elastic/ml-ui
|
||||||
x-pack/packages/ml/url_state @elastic/ml-ui
|
x-pack/packages/ml/url_state @elastic/ml-ui
|
||||||
|
|
|
@ -356,10 +356,12 @@
|
||||||
"@kbn/logging-mocks": "link:packages/kbn-logging-mocks",
|
"@kbn/logging-mocks": "link:packages/kbn-logging-mocks",
|
||||||
"@kbn/mapbox-gl": "link:packages/kbn-mapbox-gl",
|
"@kbn/mapbox-gl": "link:packages/kbn-mapbox-gl",
|
||||||
"@kbn/ml-agg-utils": "link:x-pack/packages/ml/agg_utils",
|
"@kbn/ml-agg-utils": "link:x-pack/packages/ml/agg_utils",
|
||||||
|
"@kbn/ml-date-picker": "link:x-pack/packages/ml/date_picker",
|
||||||
"@kbn/ml-is-defined": "link:x-pack/packages/ml/is_defined",
|
"@kbn/ml-is-defined": "link:x-pack/packages/ml/is_defined",
|
||||||
"@kbn/ml-is-populated-object": "link:x-pack/packages/ml/is_populated_object",
|
"@kbn/ml-is-populated-object": "link:x-pack/packages/ml/is_populated_object",
|
||||||
"@kbn/ml-local-storage": "link:x-pack/packages/ml/local_storage",
|
"@kbn/ml-local-storage": "link:x-pack/packages/ml/local_storage",
|
||||||
"@kbn/ml-nested-property": "link:x-pack/packages/ml/nested_property",
|
"@kbn/ml-nested-property": "link:x-pack/packages/ml/nested_property",
|
||||||
|
"@kbn/ml-query-utils": "link:x-pack/packages/ml/query_utils",
|
||||||
"@kbn/ml-string-hash": "link:x-pack/packages/ml/string_hash",
|
"@kbn/ml-string-hash": "link:x-pack/packages/ml/string_hash",
|
||||||
"@kbn/ml-url-state": "link:x-pack/packages/ml/url_state",
|
"@kbn/ml-url-state": "link:x-pack/packages/ml/url_state",
|
||||||
"@kbn/monaco": "link:packages/kbn-monaco",
|
"@kbn/monaco": "link:packages/kbn-monaco",
|
||||||
|
|
|
@ -838,6 +838,8 @@
|
||||||
"@kbn/maps-plugin/*": ["x-pack/plugins/maps/*"],
|
"@kbn/maps-plugin/*": ["x-pack/plugins/maps/*"],
|
||||||
"@kbn/ml-agg-utils": ["x-pack/packages/ml/agg_utils"],
|
"@kbn/ml-agg-utils": ["x-pack/packages/ml/agg_utils"],
|
||||||
"@kbn/ml-agg-utils/*": ["x-pack/packages/ml/agg_utils/*"],
|
"@kbn/ml-agg-utils/*": ["x-pack/packages/ml/agg_utils/*"],
|
||||||
|
"@kbn/ml-date-picker": ["x-pack/packages/ml/date_picker"],
|
||||||
|
"@kbn/ml-date-picker/*": ["x-pack/packages/ml/date_picker/*"],
|
||||||
"@kbn/ml-is-defined": ["x-pack/packages/ml/is_defined"],
|
"@kbn/ml-is-defined": ["x-pack/packages/ml/is_defined"],
|
||||||
"@kbn/ml-is-defined/*": ["x-pack/packages/ml/is_defined/*"],
|
"@kbn/ml-is-defined/*": ["x-pack/packages/ml/is_defined/*"],
|
||||||
"@kbn/ml-is-populated-object": ["x-pack/packages/ml/is_populated_object"],
|
"@kbn/ml-is-populated-object": ["x-pack/packages/ml/is_populated_object"],
|
||||||
|
@ -848,6 +850,8 @@
|
||||||
"@kbn/ml-nested-property/*": ["x-pack/packages/ml/nested_property/*"],
|
"@kbn/ml-nested-property/*": ["x-pack/packages/ml/nested_property/*"],
|
||||||
"@kbn/ml-plugin": ["x-pack/plugins/ml"],
|
"@kbn/ml-plugin": ["x-pack/plugins/ml"],
|
||||||
"@kbn/ml-plugin/*": ["x-pack/plugins/ml/*"],
|
"@kbn/ml-plugin/*": ["x-pack/plugins/ml/*"],
|
||||||
|
"@kbn/ml-query-utils": ["x-pack/packages/ml/query_utils"],
|
||||||
|
"@kbn/ml-query-utils/*": ["x-pack/packages/ml/query_utils/*"],
|
||||||
"@kbn/ml-string-hash": ["x-pack/packages/ml/string_hash"],
|
"@kbn/ml-string-hash": ["x-pack/packages/ml/string_hash"],
|
||||||
"@kbn/ml-string-hash/*": ["x-pack/packages/ml/string_hash/*"],
|
"@kbn/ml-string-hash/*": ["x-pack/packages/ml/string_hash/*"],
|
||||||
"@kbn/ml-url-state": ["x-pack/packages/ml/url_state"],
|
"@kbn/ml-url-state": ["x-pack/packages/ml/url_state"],
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
"xpack.main": "legacy/plugins/xpack_main",
|
"xpack.main": "legacy/plugins/xpack_main",
|
||||||
"xpack.maps": ["plugins/maps"],
|
"xpack.maps": ["plugins/maps"],
|
||||||
"xpack.aiops": ["packages/ml/aiops_components", "plugins/aiops"],
|
"xpack.aiops": ["packages/ml/aiops_components", "plugins/aiops"],
|
||||||
"xpack.ml": ["plugins/ml"],
|
"xpack.ml": ["packages/ml/date_picker", "plugins/ml"],
|
||||||
"xpack.monitoring": ["plugins/monitoring"],
|
"xpack.monitoring": ["plugins/monitoring"],
|
||||||
"xpack.osquery": ["plugins/osquery"],
|
"xpack.osquery": ["plugins/osquery"],
|
||||||
"xpack.painlessLab": "plugins/painless_lab",
|
"xpack.painlessLab": "plugins/painless_lab",
|
||||||
|
|
3
x-pack/packages/ml/date_picker/README.md
Normal file
3
x-pack/packages/ml/date_picker/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# @kbn/ml-date-picker
|
||||||
|
|
||||||
|
Date picker related components to be used in UIs maintained by the @elastic/ml-ui team.
|
28
x-pack/packages/ml/date_picker/index.ts
Normal file
28
x-pack/packages/ml/date_picker/index.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export {
|
||||||
|
DatePickerContextProvider,
|
||||||
|
type DatePickerDependencies,
|
||||||
|
} from './src/hooks/use_date_picker_context';
|
||||||
|
export {
|
||||||
|
useRefreshIntervalUpdates,
|
||||||
|
useTimefilter,
|
||||||
|
useTimeRangeUpdates,
|
||||||
|
} from './src/hooks/use_timefilter';
|
||||||
|
export { DatePickerWrapper } from './src/components/date_picker_wrapper';
|
||||||
|
export {
|
||||||
|
FullTimeRangeSelector,
|
||||||
|
type FullTimeRangeSelectorProps,
|
||||||
|
} from './src/components/full_time_range_selector';
|
||||||
|
export {
|
||||||
|
getTimeFilterRange,
|
||||||
|
type TimeRange,
|
||||||
|
} from './src/services/full_time_range_selector_service';
|
||||||
|
export { type GetTimeFieldRangeResponse } from './src/services/types';
|
||||||
|
export { mlTimefilterRefresh$, type Refresh } from './src/services/timefilter_refresh_service';
|
||||||
|
export { type FrozenTierPreference, FROZEN_TIER_PREFERENCE } from './src/storage';
|
|
@ -5,4 +5,8 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { DatePickerWrapper } from './date_picker_wrapper';
|
module.exports = {
|
||||||
|
preset: '@kbn/test',
|
||||||
|
rootDir: '../../../..',
|
||||||
|
roots: ['<rootDir>/x-pack/packages/ml/date_picker'],
|
||||||
|
};
|
5
x-pack/packages/ml/date_picker/kibana.jsonc
Normal file
5
x-pack/packages/ml/date_picker/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"type": "shared-common",
|
||||||
|
"id": "@kbn/ml-date-picker",
|
||||||
|
"owner": "@elastic/ml-ui"
|
||||||
|
}
|
6
x-pack/packages/ml/date_picker/package.json
Normal file
6
x-pack/packages/ml/date_picker/package.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "@kbn/ml-date-picker",
|
||||||
|
"private": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||||
|
}
|
|
@ -5,20 +5,24 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mount } from 'enzyme';
|
import '@testing-library/jest-dom';
|
||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { EuiSuperDatePicker } from '@elastic/eui';
|
import { EuiSuperDatePicker } from '@elastic/eui';
|
||||||
|
|
||||||
import { useUrlState } from '@kbn/ml-url-state';
|
import { useUrlState } from '@kbn/ml-url-state';
|
||||||
|
import type { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||||
|
|
||||||
import { mlTimefilterRefresh$ } from '../../../services/timefilter_refresh_service';
|
import { useDatePickerContext } from '../hooks/use_date_picker_context';
|
||||||
import { useToastNotificationService } from '../../../services/toast_notification_service';
|
import { mlTimefilterRefresh$ } from '../services/timefilter_refresh_service';
|
||||||
|
|
||||||
import { DatePickerWrapper } from './date_picker_wrapper';
|
import { DatePickerWrapper } from './date_picker_wrapper';
|
||||||
|
|
||||||
jest.mock('@elastic/eui', () => {
|
jest.mock('@elastic/eui', () => {
|
||||||
|
const EuiButtonMock = jest.fn(() => {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
const EuiSuperDatePickerMock = jest.fn(() => {
|
const EuiSuperDatePickerMock = jest.fn(() => {
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
@ -29,6 +33,9 @@ jest.mock('@elastic/eui', () => {
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
useEuiBreakpoint: jest.fn(() => 'mediaQuery @media only screen and (max-width: 1199px)'),
|
||||||
|
useIsWithinMaxBreakpoint: jest.fn(() => false),
|
||||||
|
EuiButton: EuiButtonMock,
|
||||||
EuiSuperDatePicker: EuiSuperDatePickerMock,
|
EuiSuperDatePicker: EuiSuperDatePickerMock,
|
||||||
EuiFlexGroup: EuiFlexGroupMock,
|
EuiFlexGroup: EuiFlexGroupMock,
|
||||||
EuiFlexItem: EuiFlexItemMock,
|
EuiFlexItem: EuiFlexItemMock,
|
||||||
|
@ -43,67 +50,82 @@ jest.mock('@kbn/ml-url-state', () => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock('../../../contexts/kibana/use_timefilter');
|
jest.mock('../hooks/use_timefilter', () => ({
|
||||||
|
useRefreshIntervalUpdates: jest.fn(),
|
||||||
jest.mock('../../../services/toast_notification_service');
|
useTimefilter: () => {
|
||||||
|
|
||||||
jest.mock('../../../contexts/kibana', () => ({
|
|
||||||
useMlKibana: () => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const { of } = require('rxjs');
|
const { of } = require('rxjs');
|
||||||
return {
|
return {
|
||||||
services: {
|
getRefreshIntervalUpdate$: of(),
|
||||||
uiSettings: {
|
|
||||||
get: jest.fn().mockReturnValue([
|
|
||||||
{
|
|
||||||
from: 'now/d',
|
|
||||||
to: 'now/d',
|
|
||||||
display: 'Today',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
from: 'now/w',
|
|
||||||
to: 'now/w',
|
|
||||||
display: 'This week',
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
query: {
|
|
||||||
timefilter: {
|
|
||||||
timefilter: {
|
|
||||||
getRefreshInterval: jest.fn(),
|
|
||||||
setRefreshInterval: jest.fn(),
|
|
||||||
getTime: jest.fn(() => {
|
|
||||||
return { from: '', to: '' };
|
|
||||||
}),
|
|
||||||
isAutoRefreshSelectorEnabled: jest.fn(() => true),
|
|
||||||
isTimeRangeSelectorEnabled: jest.fn(() => true),
|
|
||||||
getRefreshIntervalUpdate$: jest.fn(),
|
|
||||||
getTimeUpdate$: jest.fn(),
|
|
||||||
getEnabledUpdated$: jest.fn(),
|
|
||||||
},
|
|
||||||
history: { get: jest.fn() },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mlServices: {
|
|
||||||
httpService: {
|
|
||||||
getLoadingCount$: of(0),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
theme: {
|
|
||||||
theme$: of(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
useTimeRangeUpdates: jest.fn(() => {
|
||||||
|
return { from: '', to: '' };
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('../hooks/use_date_picker_context', () => ({
|
||||||
|
useDatePickerContext: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockContextFactory = (addWarning: jest.Mock<void, []>) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const { of } = require('rxjs');
|
||||||
|
const mockedUiSettingsKeys = {} as typeof UI_SETTINGS;
|
||||||
|
const mockedToMountPoint = jest.fn();
|
||||||
|
const mockedWrapWithTheme = jest.fn();
|
||||||
|
|
||||||
|
return () => ({
|
||||||
|
notifications: {
|
||||||
|
toasts: { addWarning },
|
||||||
|
},
|
||||||
|
uiSettings: {
|
||||||
|
get: jest.fn().mockReturnValue([
|
||||||
|
{
|
||||||
|
from: 'now/d',
|
||||||
|
to: 'now/d',
|
||||||
|
display: 'Today',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: 'now/w',
|
||||||
|
to: 'now/w',
|
||||||
|
display: 'This week',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
query: {
|
||||||
|
timefilter: {
|
||||||
|
timefilter: {
|
||||||
|
getRefreshInterval: jest.fn(),
|
||||||
|
setRefreshInterval: jest.fn(),
|
||||||
|
getTime: jest.fn(() => {
|
||||||
|
return { from: '', to: '' };
|
||||||
|
}),
|
||||||
|
isAutoRefreshSelectorEnabled: jest.fn(() => true),
|
||||||
|
isTimeRangeSelectorEnabled: jest.fn(() => true),
|
||||||
|
getRefreshIntervalUpdate$: jest.fn(),
|
||||||
|
getTimeUpdate$: jest.fn(),
|
||||||
|
getEnabledUpdated$: jest.fn(),
|
||||||
|
},
|
||||||
|
history: { get: jest.fn() },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
theme$: of(),
|
||||||
|
},
|
||||||
|
uiSettingsKeys: mockedUiSettingsKeys,
|
||||||
|
toMountPoint: mockedToMountPoint,
|
||||||
|
wrapWithTheme: mockedWrapWithTheme,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const MockedEuiSuperDatePicker = EuiSuperDatePicker as jest.MockedFunction<
|
const MockedEuiSuperDatePicker = EuiSuperDatePicker as jest.MockedFunction<
|
||||||
typeof EuiSuperDatePicker
|
typeof EuiSuperDatePicker
|
||||||
>;
|
>;
|
||||||
|
|
||||||
describe('Navigation Menu: <DatePickerWrapper />', () => {
|
describe('<DatePickerWrapper />', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.useFakeTimers({ legacyFakeTimers: true });
|
jest.useFakeTimers({ legacyFakeTimers: true });
|
||||||
MockedEuiSuperDatePicker.mockClear();
|
MockedEuiSuperDatePicker.mockClear();
|
||||||
|
@ -113,12 +135,16 @@ describe('Navigation Menu: <DatePickerWrapper />', () => {
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Minimal initialization.', () => {
|
test('Minimal initialization.', async () => {
|
||||||
const refreshListener = jest.fn();
|
const refreshListener = jest.fn();
|
||||||
const refreshSubscription = mlTimefilterRefresh$.subscribe(refreshListener);
|
const refreshSubscription = mlTimefilterRefresh$.subscribe(refreshListener);
|
||||||
|
|
||||||
const wrapper = mount(<DatePickerWrapper />);
|
const displayWarningSpy = jest.fn(() => {});
|
||||||
expect(wrapper.find(DatePickerWrapper)).toHaveLength(1);
|
|
||||||
|
(useDatePickerContext as jest.Mock).mockImplementation(mockContextFactory(displayWarningSpy));
|
||||||
|
|
||||||
|
render(<DatePickerWrapper />);
|
||||||
|
|
||||||
expect(refreshListener).toBeCalledTimes(0);
|
expect(refreshListener).toBeCalledTimes(0);
|
||||||
|
|
||||||
refreshSubscription.unsubscribe();
|
refreshSubscription.unsubscribe();
|
||||||
|
@ -130,9 +156,7 @@ describe('Navigation Menu: <DatePickerWrapper />', () => {
|
||||||
|
|
||||||
const displayWarningSpy = jest.fn(() => {});
|
const displayWarningSpy = jest.fn(() => {});
|
||||||
|
|
||||||
(useToastNotificationService as jest.Mock).mockReturnValueOnce({
|
(useDatePickerContext as jest.Mock).mockImplementation(mockContextFactory(displayWarningSpy));
|
||||||
displayWarningToast: displayWarningSpy,
|
|
||||||
});
|
|
||||||
|
|
||||||
// act
|
// act
|
||||||
render(<DatePickerWrapper />);
|
render(<DatePickerWrapper />);
|
||||||
|
@ -151,9 +175,7 @@ describe('Navigation Menu: <DatePickerWrapper />', () => {
|
||||||
|
|
||||||
const displayWarningSpy = jest.fn(() => {});
|
const displayWarningSpy = jest.fn(() => {});
|
||||||
|
|
||||||
(useToastNotificationService as jest.Mock).mockReturnValueOnce({
|
(useDatePickerContext as jest.Mock).mockImplementation(mockContextFactory(displayWarningSpy));
|
||||||
displayWarningToast: displayWarningSpy,
|
|
||||||
});
|
|
||||||
|
|
||||||
// act
|
// act
|
||||||
render(<DatePickerWrapper />);
|
render(<DatePickerWrapper />);
|
|
@ -5,13 +5,14 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { css } from '@emotion/react';
|
||||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
useEuiBreakpoint,
|
||||||
|
useIsWithinMaxBreakpoint,
|
||||||
EuiButton,
|
EuiButton,
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
EuiFlexItem,
|
EuiFlexItem,
|
||||||
|
@ -19,19 +20,19 @@ import {
|
||||||
OnRefreshProps,
|
OnRefreshProps,
|
||||||
OnTimeChangeProps,
|
OnTimeChangeProps,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import type { TimeRange } from '@kbn/es-query';
|
|
||||||
import { TimeHistoryContract } from '@kbn/data-plugin/public';
|
|
||||||
import { UI_SETTINGS } from '@kbn/data-plugin/common';
|
|
||||||
|
|
||||||
import { wrapWithTheme, toMountPoint } from '@kbn/kibana-react-plugin/public';
|
import type { TimeRange } from '@kbn/es-query';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
import type { TimeHistoryContract } from '@kbn/data-plugin/public';
|
||||||
import { useUrlState } from '@kbn/ml-url-state';
|
import { useUrlState } from '@kbn/ml-url-state';
|
||||||
import { mlTimefilterRefresh$ } from '../../../services/timefilter_refresh_service';
|
|
||||||
import { useMlKibana } from '../../../contexts/kibana';
|
import { useRefreshIntervalUpdates, useTimeRangeUpdates } from '../hooks/use_timefilter';
|
||||||
import {
|
import { useDatePickerContext } from '../hooks/use_date_picker_context';
|
||||||
useRefreshIntervalUpdates,
|
import { mlTimefilterRefresh$ } from '../services/timefilter_refresh_service';
|
||||||
useTimeRangeUpdates,
|
|
||||||
} from '../../../contexts/kibana/use_timefilter';
|
const DEFAULT_REFRESH_INTERVAL_MS = 5000;
|
||||||
import { useToastNotificationService } from '../../../services/toast_notification_service';
|
const DATE_PICKER_MAX_WIDTH = '540px';
|
||||||
|
|
||||||
interface TimePickerQuickRange {
|
interface TimePickerQuickRange {
|
||||||
from: string;
|
from: string;
|
||||||
|
@ -66,16 +67,46 @@ function updateLastRefresh(timeRange?: OnRefreshProps) {
|
||||||
mlTimefilterRefresh$.next({ lastRefresh: Date.now(), ...(timeRange ? { timeRange } : {}) });
|
mlTimefilterRefresh$.next({ lastRefresh: Date.now(), ...(timeRange ? { timeRange } : {}) });
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_REFRESH_INTERVAL_MS = 5000;
|
/**
|
||||||
|
* DatePickerWrapper React Component props interface
|
||||||
|
*/
|
||||||
|
interface DatePickerWrapperProps {
|
||||||
|
/**
|
||||||
|
* Boolean flag to be passed on to `EuiSuperDatePicker`.
|
||||||
|
*/
|
||||||
|
isAutoRefreshOnly?: boolean;
|
||||||
|
/**
|
||||||
|
* Boolean flag to indicate loading state.
|
||||||
|
*/
|
||||||
|
isLoading?: boolean;
|
||||||
|
/**
|
||||||
|
* Boolean flag to enforce showing/hiding the refresh button.
|
||||||
|
*/
|
||||||
|
showRefresh?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export const DatePickerWrapper: FC = () => {
|
/**
|
||||||
const { services } = useMlKibana();
|
* DatePickerWrapper React Component
|
||||||
const config = services.uiSettings;
|
*
|
||||||
const theme$ = services.theme.theme$;
|
* @type {FC<DatePickerWrapperProps>}
|
||||||
|
* @param props - `DatePickerWrapper` component props
|
||||||
|
* @returns {React.ReactElement} The DatePickerWrapper component.
|
||||||
|
*/
|
||||||
|
export const DatePickerWrapper: FC<DatePickerWrapperProps> = (props) => {
|
||||||
|
const { isAutoRefreshOnly, isLoading = false, showRefresh } = props;
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
notifications: { toasts },
|
||||||
|
theme: { theme$ },
|
||||||
|
uiSettings: config,
|
||||||
|
uiSettingsKeys,
|
||||||
|
wrapWithTheme,
|
||||||
|
toMountPoint,
|
||||||
|
} = useDatePickerContext();
|
||||||
|
|
||||||
const { httpService } = services.mlServices;
|
const isWithinLBreakpoint = useIsWithinMaxBreakpoint('l');
|
||||||
const { timefilter, history } = services.data.query.timefilter;
|
|
||||||
const { displayWarningToast } = useToastNotificationService();
|
const { timefilter, history } = data.query.timefilter;
|
||||||
|
|
||||||
const [globalState, setGlobalState] = useUrlState('_g');
|
const [globalState, setGlobalState] = useUrlState('_g');
|
||||||
const getRecentlyUsedRanges = getRecentlyUsedRangesFactory(history);
|
const getRecentlyUsedRanges = getRecentlyUsedRangesFactory(history);
|
||||||
|
@ -117,7 +148,6 @@ export const DatePickerWrapper: FC = () => {
|
||||||
[setGlobalState]
|
[setGlobalState]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const [recentlyUsedRanges, setRecentlyUsedRanges] = useState(getRecentlyUsedRanges());
|
const [recentlyUsedRanges, setRecentlyUsedRanges] = useState(getRecentlyUsedRanges());
|
||||||
const [isAutoRefreshSelectorEnabled, setIsAutoRefreshSelectorEnabled] = useState(
|
const [isAutoRefreshSelectorEnabled, setIsAutoRefreshSelectorEnabled] = useState(
|
||||||
timefilter.isAutoRefreshSelectorEnabled()
|
timefilter.isAutoRefreshSelectorEnabled()
|
||||||
|
@ -140,16 +170,16 @@ export const DatePickerWrapper: FC = () => {
|
||||||
// Only warn about short interval with enabled auto-refresh.
|
// Only warn about short interval with enabled auto-refresh.
|
||||||
if (!isTooShort || refreshInterval.pause) return;
|
if (!isTooShort || refreshInterval.pause) return;
|
||||||
|
|
||||||
displayWarningToast(
|
toasts.addWarning(
|
||||||
{
|
{
|
||||||
title: isResolvedFromUrlState
|
title: isResolvedFromUrlState
|
||||||
? i18n.translate('xpack.ml.datePicker.shortRefreshIntervalURLWarningMessage', {
|
? i18n.translate('xpack.ml.datePicker.shortRefreshIntervalURLWarningMessage', {
|
||||||
defaultMessage:
|
defaultMessage:
|
||||||
'The refresh interval in the URL is shorter than the minimum supported by Machine Learning.',
|
'The refresh interval in the URL is shorter than the minimum supported interval.',
|
||||||
})
|
})
|
||||||
: i18n.translate('xpack.ml.datePicker.shortRefreshIntervalTimeFilterWarningMessage', {
|
: i18n.translate('xpack.ml.datePicker.shortRefreshIntervalTimeFilterWarningMessage', {
|
||||||
defaultMessage:
|
defaultMessage:
|
||||||
'The refresh interval in Advanced Settings is shorter than the minimum supported by Machine Learning.',
|
'The refresh interval in Advanced Settings is shorter than the minimum supported interval.',
|
||||||
}),
|
}),
|
||||||
text: toMountPoint(
|
text: toMountPoint(
|
||||||
wrapWithTheme(
|
wrapWithTheme(
|
||||||
|
@ -160,7 +190,7 @@ export const DatePickerWrapper: FC = () => {
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.ml.pageRefreshResetButton"
|
id="xpack.ml.datePicker.pageRefreshResetButton"
|
||||||
defaultMessage="Set to {defaultInterval}"
|
defaultMessage="Set to {defaultInterval}"
|
||||||
values={{
|
values={{
|
||||||
defaultInterval: `${DEFAULT_REFRESH_INTERVAL_MS / 1000}s`,
|
defaultInterval: `${DEFAULT_REFRESH_INTERVAL_MS / 1000}s`,
|
||||||
|
@ -186,7 +216,7 @@ export const DatePickerWrapper: FC = () => {
|
||||||
|
|
||||||
const dateFormat = config.get('dateFormat');
|
const dateFormat = config.get('dateFormat');
|
||||||
const timePickerQuickRanges = config.get<TimePickerQuickRange[]>(
|
const timePickerQuickRanges = config.get<TimePickerQuickRange[]>(
|
||||||
UI_SETTINGS.TIMEPICKER_QUICK_RANGES
|
uiSettingsKeys.TIMEPICKER_QUICK_RANGES
|
||||||
);
|
);
|
||||||
|
|
||||||
const commonlyUsedRanges = useMemo(
|
const commonlyUsedRanges = useMemo(
|
||||||
|
@ -202,12 +232,6 @@ export const DatePickerWrapper: FC = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscriptions = new Subscription();
|
const subscriptions = new Subscription();
|
||||||
|
|
||||||
subscriptions.add(
|
|
||||||
httpService.getLoadingCount$.subscribe((v) => {
|
|
||||||
setIsLoading(v !== 0);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const enabledUpdated$ = timefilter.getEnabledUpdated$();
|
const enabledUpdated$ = timefilter.getEnabledUpdated$();
|
||||||
if (enabledUpdated$ !== undefined) {
|
if (enabledUpdated$ !== undefined) {
|
||||||
subscriptions.add(
|
subscriptions.add(
|
||||||
|
@ -250,19 +274,21 @@ export const DatePickerWrapper: FC = () => {
|
||||||
setRefreshInterval({ pause, value });
|
setRefreshInterval({ pause, value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const datePickerWidth = css({
|
||||||
|
[useEuiBreakpoint(['xs', 's', 'm', 'l'])]: {
|
||||||
|
maxWidth: DATE_PICKER_MAX_WIDTH,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return isAutoRefreshSelectorEnabled || isTimeRangeSelectorEnabled ? (
|
return isAutoRefreshSelectorEnabled || isTimeRangeSelectorEnabled ? (
|
||||||
<EuiFlexGroup
|
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||||
gutterSize="s"
|
<EuiFlexItem grow={false} css={datePickerWidth}>
|
||||||
alignItems="center"
|
|
||||||
className="mlNavigationMenu__datePickerWrapper"
|
|
||||||
>
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<EuiSuperDatePicker
|
<EuiSuperDatePicker
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
start={time.from}
|
start={time.from}
|
||||||
end={time.to}
|
end={time.to}
|
||||||
isPaused={refreshInterval.pause}
|
isPaused={refreshInterval.pause}
|
||||||
isAutoRefreshOnly={!isTimeRangeSelectorEnabled}
|
isAutoRefreshOnly={!isTimeRangeSelectorEnabled || isAutoRefreshOnly}
|
||||||
refreshInterval={refreshInterval.value || DEFAULT_REFRESH_INTERVAL_MS}
|
refreshInterval={refreshInterval.value || DEFAULT_REFRESH_INTERVAL_MS}
|
||||||
onTimeChange={updateTimeFilter}
|
onTimeChange={updateTimeFilter}
|
||||||
onRefresh={updateLastRefresh}
|
onRefresh={updateLastRefresh}
|
||||||
|
@ -270,23 +296,24 @@ export const DatePickerWrapper: FC = () => {
|
||||||
recentlyUsedRanges={recentlyUsedRanges}
|
recentlyUsedRanges={recentlyUsedRanges}
|
||||||
dateFormat={dateFormat}
|
dateFormat={dateFormat}
|
||||||
commonlyUsedRanges={commonlyUsedRanges}
|
commonlyUsedRanges={commonlyUsedRanges}
|
||||||
|
updateButtonProps={{ iconOnly: isWithinLBreakpoint }}
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
|
|
||||||
{isTimeRangeSelectorEnabled ? null : (
|
{showRefresh === true || !isTimeRangeSelectorEnabled ? (
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiButton
|
<EuiButton
|
||||||
fill
|
fill
|
||||||
color="primary"
|
color="primary"
|
||||||
iconType={'refresh'}
|
iconType={'refresh'}
|
||||||
onClick={() => updateLastRefresh()}
|
onClick={() => updateLastRefresh()}
|
||||||
data-test-subj={`mlRefreshPageButton${isLoading ? ' loading' : ' loaded'}`}
|
data-test-subj={`mlDatePickerRefreshPageButton${isLoading ? ' loading' : ' loaded'}`}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
>
|
>
|
||||||
<FormattedMessage id="xpack.ml.pageRefreshButton" defaultMessage="Refresh" />
|
<FormattedMessage id="xpack.ml.datePicker.pageRefreshButton" defaultMessage="Refresh" />
|
||||||
</EuiButton>
|
</EuiButton>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
)}
|
) : null}
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
) : null;
|
) : null;
|
||||||
};
|
};
|
|
@ -6,16 +6,30 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallowWithIntl } from '@kbn/test-jest-helpers';
|
import { render, fireEvent } from '@testing-library/react';
|
||||||
import { FullTimeRangeSelector } from '.';
|
|
||||||
import type { Query } from '@kbn/es-query';
|
import type { Query } from '@kbn/es-query';
|
||||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||||
|
import type { TimefilterContract } from '@kbn/data-plugin/public';
|
||||||
|
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DatePickerContextProvider,
|
||||||
|
type DatePickerDependencies,
|
||||||
|
} from '../hooks/use_date_picker_context';
|
||||||
|
import { FROZEN_TIER_PREFERENCE } from '../storage';
|
||||||
|
|
||||||
|
import { FullTimeRangeSelector } from './full_time_range_selector';
|
||||||
|
|
||||||
|
const mockDependencies = {
|
||||||
|
notifications: {},
|
||||||
|
} as DatePickerDependencies;
|
||||||
|
|
||||||
// Create a mock for the setFullTimeRange function in the service.
|
// Create a mock for the setFullTimeRange function in the service.
|
||||||
// The mock is hoisted to the top, so need to prefix the mock function
|
// The mock is hoisted to the top, so need to prefix the mock function
|
||||||
// with 'mock' so it can be used lazily.
|
// with 'mock' so it can be used lazily.
|
||||||
const mockSetFullTimeRange = jest.fn((indexPattern: DataView, query: Query) => true);
|
const mockSetFullTimeRange = jest.fn((indexPattern: DataView, query: Query) => true);
|
||||||
jest.mock('./full_time_range_selector_service', () => ({
|
jest.mock('../services/full_time_range_selector_service', () => ({
|
||||||
setFullTimeRange: (indexPattern: DataView, query: Query) =>
|
setFullTimeRange: (indexPattern: DataView, query: Query) =>
|
||||||
mockSetFullTimeRange(indexPattern, query),
|
mockSetFullTimeRange(indexPattern, query),
|
||||||
}));
|
}));
|
||||||
|
@ -44,24 +58,25 @@ describe('FullTimeRangeSelector', () => {
|
||||||
query,
|
query,
|
||||||
};
|
};
|
||||||
|
|
||||||
test('renders the selector', () => {
|
|
||||||
const props = {
|
|
||||||
...requiredProps,
|
|
||||||
disabled: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = shallowWithIntl(<FullTimeRangeSelector {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('calls setFullTimeRange on clicking button', () => {
|
test('calls setFullTimeRange on clicking button', () => {
|
||||||
const props = {
|
const props = {
|
||||||
...requiredProps,
|
...requiredProps,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
frozenDataPreference: FROZEN_TIER_PREFERENCE.EXCLUDE,
|
||||||
|
setFrozenDataPreference: jest.fn(),
|
||||||
|
timefilter: {} as TimefilterContract,
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallowWithIntl(<FullTimeRangeSelector {...props} />);
|
const { getByText } = render(
|
||||||
wrapper.find('EuiButton').simulate('click');
|
<IntlProvider locale="en">
|
||||||
|
<DatePickerContextProvider {...mockDependencies}>
|
||||||
|
<FullTimeRangeSelector {...props} />
|
||||||
|
</DatePickerContextProvider>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(getByText(/use full data/i));
|
||||||
|
|
||||||
expect(mockSetFullTimeRange).toHaveBeenCalled();
|
expect(mockSetFullTimeRange).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -5,14 +5,10 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO Consolidate with near duplicate component `FullTimeRangeSelector` in
|
|
||||||
// `x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector.tsx`
|
|
||||||
|
|
||||||
import React, { FC, useCallback, useMemo, useState } from 'react';
|
import React, { FC, useCallback, useMemo, useState } from 'react';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
|
||||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||||
import { TimefilterContract } from '@kbn/data-plugin/public';
|
|
||||||
import type { DataView } from '@kbn/data-plugin/common';
|
|
||||||
import {
|
import {
|
||||||
EuiButton,
|
EuiButton,
|
||||||
EuiButtonIcon,
|
EuiButtonIcon,
|
||||||
|
@ -24,40 +20,74 @@ import {
|
||||||
EuiRadioGroupOption,
|
EuiRadioGroupOption,
|
||||||
EuiToolTip,
|
EuiToolTip,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { useStorage } from '@kbn/ml-local-storage';
|
|
||||||
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
|
||||||
import {
|
|
||||||
type GetTimeFieldRangeResponse,
|
|
||||||
setFullTimeRange,
|
|
||||||
} from './full_time_range_selector_service';
|
|
||||||
import {
|
|
||||||
AIOPS_FROZEN_TIER_PREFERENCE,
|
|
||||||
FROZEN_TIER_PREFERENCE,
|
|
||||||
type AiOpsKey,
|
|
||||||
type AiOpsStorageMapped,
|
|
||||||
type FrozenTierPreference,
|
|
||||||
} from '../../types/storage';
|
|
||||||
|
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import type { DataView } from '@kbn/data-plugin/common';
|
||||||
|
import type { TimefilterContract } from '@kbn/data-plugin/public';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
import { useDatePickerContext } from '../hooks/use_date_picker_context';
|
||||||
|
import { setFullTimeRange } from '../services/full_time_range_selector_service';
|
||||||
|
import type { GetTimeFieldRangeResponse } from '../services/types';
|
||||||
|
import { FROZEN_TIER_PREFERENCE, type FrozenTierPreference } from '../storage';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FullTimeRangeSelectorProps React Component props interface
|
||||||
|
*/
|
||||||
export interface FullTimeRangeSelectorProps {
|
export interface FullTimeRangeSelectorProps {
|
||||||
|
/**
|
||||||
|
* Frozen data preference ('exclude-frozen' | 'include-frozen')
|
||||||
|
*/
|
||||||
|
frozenDataPreference: FrozenTierPreference;
|
||||||
|
/**
|
||||||
|
* Callback to set frozen data preference.
|
||||||
|
* @param value - The updated frozen data preference.
|
||||||
|
*/
|
||||||
|
setFrozenDataPreference: (value: FrozenTierPreference | undefined) => void;
|
||||||
|
/**
|
||||||
|
* timefilter service.
|
||||||
|
*/
|
||||||
timefilter: TimefilterContract;
|
timefilter: TimefilterContract;
|
||||||
|
/**
|
||||||
|
* Current data view.
|
||||||
|
*/
|
||||||
dataView: DataView;
|
dataView: DataView;
|
||||||
|
/**
|
||||||
|
* Boolean flag to enable/disable the full time range button.
|
||||||
|
*/
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
/**
|
||||||
|
* Optional DSL query.
|
||||||
|
*/
|
||||||
query?: QueryDslQueryContainer;
|
query?: QueryDslQueryContainer;
|
||||||
callback?: (a: GetTimeFieldRangeResponse) => void;
|
/**
|
||||||
|
* Optional callback.
|
||||||
|
* @param value - The time field range response.
|
||||||
|
*/
|
||||||
|
callback?: (value: GetTimeFieldRangeResponse) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = ({
|
/**
|
||||||
timefilter,
|
* Component for rendering a button which automatically sets the range of the time filter
|
||||||
dataView,
|
* to the time range of data in the index(es) mapped to the supplied Kibana data view or query.
|
||||||
query,
|
*
|
||||||
disabled,
|
* @type {FC<FullTimeRangeSelectorProps>}
|
||||||
callback,
|
* @param props - `FullTimeRangeSelectorProps` component props
|
||||||
}) => {
|
* @returns {React.ReactElement} The FullTimeRangeSelector component.
|
||||||
|
*/
|
||||||
|
export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = (props) => {
|
||||||
|
const {
|
||||||
|
frozenDataPreference,
|
||||||
|
setFrozenDataPreference,
|
||||||
|
timefilter,
|
||||||
|
dataView,
|
||||||
|
query,
|
||||||
|
disabled,
|
||||||
|
callback,
|
||||||
|
} = props;
|
||||||
const {
|
const {
|
||||||
http,
|
http,
|
||||||
notifications: { toasts },
|
notifications: { toasts },
|
||||||
} = useAiopsAppContext();
|
} = useDatePickerContext();
|
||||||
|
|
||||||
// wrapper around setFullTimeRange to allow for the calling of the optional callBack prop
|
// wrapper around setFullTimeRange to allow for the calling of the optional callBack prop
|
||||||
const setRange = useCallback(
|
const setRange = useCallback(
|
||||||
|
@ -77,7 +107,7 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = ({
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toasts.addDanger(
|
toasts.addDanger(
|
||||||
i18n.translate(
|
i18n.translate(
|
||||||
'xpack.aiops.index.fullTimeRangeSelector.errorSettingTimeRangeNotification',
|
'xpack.ml.datePicker.fullTimeRangeSelector.errorSettingTimeRangeNotification',
|
||||||
{
|
{
|
||||||
defaultMessage: 'An error occurred setting the time range.',
|
defaultMessage: 'An error occurred setting the time range.',
|
||||||
}
|
}
|
||||||
|
@ -90,15 +120,6 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = ({
|
||||||
|
|
||||||
const [isPopoverOpen, setPopover] = useState(false);
|
const [isPopoverOpen, setPopover] = useState(false);
|
||||||
|
|
||||||
const [frozenDataPreference, setFrozenDataPreference] = useStorage<
|
|
||||||
AiOpsKey,
|
|
||||||
AiOpsStorageMapped<typeof AIOPS_FROZEN_TIER_PREFERENCE>
|
|
||||||
>(
|
|
||||||
AIOPS_FROZEN_TIER_PREFERENCE,
|
|
||||||
// By default we will exclude frozen data tier
|
|
||||||
FROZEN_TIER_PREFERENCE.EXCLUDE
|
|
||||||
);
|
|
||||||
|
|
||||||
const setPreference = useCallback(
|
const setPreference = useCallback(
|
||||||
(id: string) => {
|
(id: string) => {
|
||||||
setFrozenDataPreference(id as FrozenTierPreference);
|
setFrozenDataPreference(id as FrozenTierPreference);
|
||||||
|
@ -121,7 +142,7 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = ({
|
||||||
{
|
{
|
||||||
id: FROZEN_TIER_PREFERENCE.EXCLUDE,
|
id: FROZEN_TIER_PREFERENCE.EXCLUDE,
|
||||||
label: i18n.translate(
|
label: i18n.translate(
|
||||||
'xpack.aiops.index.fullTimeRangeSelector.useFullDataExcludingFrozenMenuLabel',
|
'xpack.ml.datePicker.fullTimeRangeSelector.useFullDataExcludingFrozenMenuLabel',
|
||||||
{
|
{
|
||||||
defaultMessage: 'Exclude frozen data tier',
|
defaultMessage: 'Exclude frozen data tier',
|
||||||
}
|
}
|
||||||
|
@ -130,7 +151,7 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = ({
|
||||||
{
|
{
|
||||||
id: FROZEN_TIER_PREFERENCE.INCLUDE,
|
id: FROZEN_TIER_PREFERENCE.INCLUDE,
|
||||||
label: i18n.translate(
|
label: i18n.translate(
|
||||||
'xpack.aiops.index.fullTimeRangeSelector.useFullDataIncludingFrozenMenuLabel',
|
'xpack.ml.datePicker.fullTimeRangeSelector.useFullDataIncludingFrozenMenuLabel',
|
||||||
{
|
{
|
||||||
defaultMessage: 'Include frozen data tier',
|
defaultMessage: 'Include frozen data tier',
|
||||||
}
|
}
|
||||||
|
@ -157,12 +178,12 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = ({
|
||||||
() =>
|
() =>
|
||||||
frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE ? (
|
frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE ? (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.aiops.fullTimeRangeSelector.useFullDataExcludingFrozenButtonTooltip"
|
id="xpack.ml.datePicker.fullTimeRangeSelector.useFullDataExcludingFrozenButtonTooltip"
|
||||||
defaultMessage="Use full range of data excluding frozen data tier."
|
defaultMessage="Use full range of data excluding frozen data tier."
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.aiops.fullTimeRangeSelector.useFullDataIncludingFrozenButtonTooltip"
|
id="xpack.ml.datePicker.fullTimeRangeSelector.useFullDataIncludingFrozenButtonTooltip"
|
||||||
defaultMessage="Use full range of data including frozen data tier, which might have slower search results."
|
defaultMessage="Use full range of data including frozen data tier, which might have slower search results."
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
@ -175,10 +196,10 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = ({
|
||||||
<EuiButton
|
<EuiButton
|
||||||
isDisabled={disabled}
|
isDisabled={disabled}
|
||||||
onClick={() => setRange(dataView, query, true)}
|
onClick={() => setRange(dataView, query, true)}
|
||||||
data-test-subj="aiopsExplainLogRatesSpikeButtonUseFullData"
|
data-test-subj="mlDatePickerButtonUseFullData"
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.aiops.index.fullTimeRangeSelector.useFullDataButtonLabel"
|
id="xpack.ml.datePicker.fullTimeRangeSelector.useFullDataButtonLabel"
|
||||||
defaultMessage="Use full data"
|
defaultMessage="Use full data"
|
||||||
/>
|
/>
|
||||||
</EuiButton>
|
</EuiButton>
|
||||||
|
@ -192,7 +213,7 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = ({
|
||||||
size="m"
|
size="m"
|
||||||
iconType="boxesVertical"
|
iconType="boxesVertical"
|
||||||
aria-label={i18n.translate(
|
aria-label={i18n.translate(
|
||||||
'xpack.aiops.index.fullTimeRangeSelector.moreOptionsButtonAriaLabel',
|
'xpack.ml.datePicker.fullTimeRangeSelector.moreOptionsButtonAriaLabel',
|
||||||
{
|
{
|
||||||
defaultMessage: 'More options',
|
defaultMessage: 'More options',
|
||||||
}
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* 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 React, { createContext, useContext, type FC } from 'react';
|
||||||
|
|
||||||
|
import type { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||||
|
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||||
|
import type { CoreSetup, IUiSettingsClient, ThemeServiceStart } from '@kbn/core/public';
|
||||||
|
import type { HttpStart } from '@kbn/core/public';
|
||||||
|
import type { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date Picker Dependencies to be passed on via `DatePickerContextProvider`.
|
||||||
|
*/
|
||||||
|
export interface DatePickerDependencies {
|
||||||
|
/**
|
||||||
|
* data plugin
|
||||||
|
*/
|
||||||
|
data: DataPublicPluginStart;
|
||||||
|
/**
|
||||||
|
* http service
|
||||||
|
*/
|
||||||
|
http: HttpStart;
|
||||||
|
/**
|
||||||
|
* notifications service
|
||||||
|
*/
|
||||||
|
notifications: CoreSetup['notifications'];
|
||||||
|
/**
|
||||||
|
* EUI theme
|
||||||
|
*/
|
||||||
|
theme: ThemeServiceStart;
|
||||||
|
/**
|
||||||
|
* Kibana UI advanced settings
|
||||||
|
*/
|
||||||
|
uiSettings: IUiSettingsClient;
|
||||||
|
/**
|
||||||
|
* Kibana UI advanced settings keys.
|
||||||
|
*/
|
||||||
|
uiSettingsKeys: typeof UI_SETTINGS;
|
||||||
|
/**
|
||||||
|
* helper to be used with notifications.
|
||||||
|
*/
|
||||||
|
wrapWithTheme: typeof wrapWithTheme;
|
||||||
|
/**
|
||||||
|
* helper to be used with notifications.
|
||||||
|
*/
|
||||||
|
toMountPoint: typeof toMountPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The context holding the date picker dependencies.
|
||||||
|
*/
|
||||||
|
export const DatePickerContext = createContext<DatePickerDependencies | undefined>(undefined);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook to return date picker dependencies.
|
||||||
|
* @returns `DatePickerDependencies`
|
||||||
|
*/
|
||||||
|
export const useDatePickerContext = (): DatePickerDependencies => {
|
||||||
|
const datePickerContext = useContext(DatePickerContext);
|
||||||
|
|
||||||
|
// if `undefined`, throw an error
|
||||||
|
if (datePickerContext === undefined) {
|
||||||
|
throw new Error('datePickerContext was used outside of its Provider');
|
||||||
|
}
|
||||||
|
|
||||||
|
return datePickerContext;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React Component that acts as a wrapper for DatePickerContext.
|
||||||
|
*
|
||||||
|
* @type {FC<DatePickerDependencies>}
|
||||||
|
* @param props - The component props
|
||||||
|
* @returns {React.ReactElement} The DatePickerContextProvider component.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const DatePickerContextProvider: FC<DatePickerDependencies> = (props) => {
|
||||||
|
const { children, ...deps } = props;
|
||||||
|
return <DatePickerContext.Provider value={deps}>{children}</DatePickerContext.Provider>;
|
||||||
|
};
|
|
@ -6,31 +6,35 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
|
import { useDatePickerContext } from './use_date_picker_context';
|
||||||
import { useTimefilter } from './use_timefilter';
|
import { useTimefilter } from './use_timefilter';
|
||||||
|
|
||||||
jest.mock('./kibana_context', () => ({
|
jest.mock('./use_date_picker_context');
|
||||||
useMlKibana: () => {
|
|
||||||
return {
|
const mockContextFactory = (
|
||||||
services: {
|
isAutoRefreshSelectorEnabled: boolean = true,
|
||||||
data: {
|
isTimeRangeSelectorEnabled: boolean = true
|
||||||
query: {
|
) => ({
|
||||||
timefilter: {
|
data: {
|
||||||
timefilter: {
|
query: {
|
||||||
disableTimeRangeSelector: jest.fn(),
|
timefilter: {
|
||||||
disableAutoRefreshSelector: jest.fn(),
|
timefilter: {
|
||||||
enableTimeRangeSelector: jest.fn(),
|
disableTimeRangeSelector: jest.fn(),
|
||||||
enableAutoRefreshSelector: jest.fn(),
|
disableAutoRefreshSelector: jest.fn(),
|
||||||
},
|
enableTimeRangeSelector: jest.fn(),
|
||||||
},
|
enableAutoRefreshSelector: jest.fn(),
|
||||||
},
|
isAutoRefreshSelectorEnabled: jest.fn(() => isAutoRefreshSelectorEnabled),
|
||||||
|
isTimeRangeSelectorEnabled: jest.fn(() => isTimeRangeSelectorEnabled),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
},
|
},
|
||||||
}));
|
});
|
||||||
|
|
||||||
describe('useTimefilter', () => {
|
describe('useTimefilter', () => {
|
||||||
test('will not trigger any date picker settings by default', () => {
|
test('will not trigger any date picker settings by default', () => {
|
||||||
|
(useDatePickerContext as jest.Mock).mockReturnValueOnce(mockContextFactory());
|
||||||
|
|
||||||
const { result } = renderHook(() => useTimefilter());
|
const { result } = renderHook(() => useTimefilter());
|
||||||
const timefilter = result.current;
|
const timefilter = result.current;
|
||||||
|
|
||||||
|
@ -41,6 +45,8 @@ describe('useTimefilter', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('custom disabled overrides', () => {
|
test('custom disabled overrides', () => {
|
||||||
|
(useDatePickerContext as jest.Mock).mockReturnValueOnce(mockContextFactory());
|
||||||
|
|
||||||
const { result } = renderHook(() =>
|
const { result } = renderHook(() =>
|
||||||
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false })
|
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false })
|
||||||
);
|
);
|
||||||
|
@ -53,6 +59,8 @@ describe('useTimefilter', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('custom enabled overrides', () => {
|
test('custom enabled overrides', () => {
|
||||||
|
(useDatePickerContext as jest.Mock).mockReturnValueOnce(mockContextFactory(false, false));
|
||||||
|
|
||||||
const { result } = renderHook(() =>
|
const { result } = renderHook(() =>
|
||||||
useTimefilter({ timeRangeSelector: true, autoRefreshSelector: true })
|
useTimefilter({ timeRangeSelector: true, autoRefreshSelector: true })
|
||||||
);
|
);
|
|
@ -9,24 +9,41 @@ import { useEffect, useMemo } from 'react';
|
||||||
import useObservable from 'react-use/lib/useObservable';
|
import useObservable from 'react-use/lib/useObservable';
|
||||||
import { distinctUntilChanged, map } from 'rxjs/operators';
|
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { useAiopsAppContext } from './use_aiops_app_context';
|
|
||||||
|
|
||||||
|
import type { TimeRange } from '@kbn/es-query';
|
||||||
|
import type { TimefilterContract } from '@kbn/data-plugin/public';
|
||||||
|
|
||||||
|
import { useDatePickerContext } from './use_date_picker_context';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options interface for the `useTimefilter` custom hook.
|
||||||
|
*/
|
||||||
interface UseTimefilterOptions {
|
interface UseTimefilterOptions {
|
||||||
|
/**
|
||||||
|
* Boolean flag to enable/disable the time range selector
|
||||||
|
*/
|
||||||
timeRangeSelector?: boolean;
|
timeRangeSelector?: boolean;
|
||||||
|
/**
|
||||||
|
* Boolean flag to enable/disable the auto refresh selector
|
||||||
|
*/
|
||||||
autoRefreshSelector?: boolean;
|
autoRefreshSelector?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTimefilter = ({
|
/**
|
||||||
timeRangeSelector,
|
* Custom hook to get the timefilter from runtime dependencies.
|
||||||
autoRefreshSelector,
|
*
|
||||||
}: UseTimefilterOptions = {}) => {
|
* @param {UseTimefilterOptions} options - time filter options
|
||||||
|
* @returns {TimefilterContract} timefilter
|
||||||
|
*/
|
||||||
|
export const useTimefilter = (options: UseTimefilterOptions = {}): TimefilterContract => {
|
||||||
|
const { timeRangeSelector, autoRefreshSelector } = options;
|
||||||
const {
|
const {
|
||||||
data: {
|
data: {
|
||||||
query: {
|
query: {
|
||||||
timefilter: { timefilter },
|
timefilter: { timefilter },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} = useAiopsAppContext();
|
} = useDatePickerContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (timeRangeSelector === true && !timefilter.isTimeRangeSelectorEnabled()) {
|
if (timeRangeSelector === true && !timefilter.isTimeRangeSelectorEnabled()) {
|
||||||
|
@ -45,6 +62,11 @@ export const useTimefilter = ({
|
||||||
return timefilter;
|
return timefilter;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook to return refresh interval updates from the `refreshIntervalObservable$` observable.
|
||||||
|
*
|
||||||
|
* @returns refresh interval update
|
||||||
|
*/
|
||||||
export const useRefreshIntervalUpdates = () => {
|
export const useRefreshIntervalUpdates = () => {
|
||||||
const timefilter = useTimefilter();
|
const timefilter = useTimefilter();
|
||||||
|
|
||||||
|
@ -56,7 +78,13 @@ export const useRefreshIntervalUpdates = () => {
|
||||||
return useObservable(refreshIntervalObservable$, timefilter.getRefreshInterval());
|
return useObservable(refreshIntervalObservable$, timefilter.getRefreshInterval());
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useTimeRangeUpdates = (absolute = false) => {
|
/**
|
||||||
|
* Custom hook to return time range updates from the `timeChangeObservable$` observable.
|
||||||
|
*
|
||||||
|
* @param absolute - flag to enforce absolute times
|
||||||
|
* @returns time range update
|
||||||
|
*/
|
||||||
|
export const useTimeRangeUpdates = (absolute = false): TimeRange => {
|
||||||
const timefilter = useTimefilter();
|
const timefilter = useTimefilter();
|
||||||
|
|
||||||
const getTimeCallback = useMemo(() => {
|
const getTimeCallback = useMemo(() => {
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* 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 moment from 'moment';
|
||||||
|
import type { TimefilterContract } from '@kbn/data-plugin/public';
|
||||||
|
import dateMath from '@kbn/datemath';
|
||||||
|
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import type { ToastsStart, HttpStart } from '@kbn/core/public';
|
||||||
|
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||||
|
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
||||||
|
import { addExcludeFrozenToQuery } from '@kbn/ml-query-utils';
|
||||||
|
import { getTimeFieldRange } from './time_field_range';
|
||||||
|
import type { GetTimeFieldRangeResponse } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the full available time range of the given Data View and updates
|
||||||
|
* the timefilter accordingly.
|
||||||
|
*
|
||||||
|
* @param timefilter - TimefilterContract
|
||||||
|
* @param dataView - DataView
|
||||||
|
* @param toasts - ToastsStart
|
||||||
|
* @param http - HttpStart
|
||||||
|
* @param query - optional query
|
||||||
|
* @param excludeFrozenData - optional boolean flag
|
||||||
|
* @returns {GetTimeFieldRangeResponse}
|
||||||
|
*/
|
||||||
|
export async function setFullTimeRange(
|
||||||
|
timefilter: TimefilterContract,
|
||||||
|
dataView: DataView,
|
||||||
|
toasts: ToastsStart,
|
||||||
|
http: HttpStart,
|
||||||
|
query?: QueryDslQueryContainer,
|
||||||
|
excludeFrozenData?: boolean
|
||||||
|
): Promise<GetTimeFieldRangeResponse> {
|
||||||
|
try {
|
||||||
|
const runtimeMappings = dataView.getRuntimeMappings();
|
||||||
|
const resp = await getTimeFieldRange({
|
||||||
|
index: dataView.getIndexPattern(),
|
||||||
|
timeFieldName: dataView.timeFieldName,
|
||||||
|
query: excludeFrozenData ? addExcludeFrozenToQuery(query) : query,
|
||||||
|
...(isPopulatedObject(runtimeMappings) ? { runtimeMappings } : {}),
|
||||||
|
http,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resp.start.epoch && resp.end.epoch) {
|
||||||
|
timefilter.setTime({
|
||||||
|
from: moment(resp.start.epoch).toISOString(),
|
||||||
|
to: moment(resp.end.epoch).toISOString(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toasts.addWarning({
|
||||||
|
title: i18n.translate('xpack.ml.datePicker.fullTimeRangeSelector.noResults', {
|
||||||
|
defaultMessage: 'No results match your search criteria',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
} catch (resp) {
|
||||||
|
toasts.addDanger(
|
||||||
|
i18n.translate(
|
||||||
|
'xpack.ml.datePicker.fullTimeRangeSelector.errorSettingTimeRangeNotification',
|
||||||
|
{
|
||||||
|
defaultMessage: 'An error occurred setting the time range.',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return type for the `getTimeFilterRange` function.
|
||||||
|
*/
|
||||||
|
export interface TimeRange {
|
||||||
|
/**
|
||||||
|
* From timestamp.
|
||||||
|
*/
|
||||||
|
from: number;
|
||||||
|
/**
|
||||||
|
* To timestamp.
|
||||||
|
*/
|
||||||
|
to: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to get the time filter range as timestamps.
|
||||||
|
*
|
||||||
|
* @param timefilter - The timefilter
|
||||||
|
* @returns TimeRange
|
||||||
|
*/
|
||||||
|
export function getTimeFilterRange(timefilter: TimefilterContract): TimeRange {
|
||||||
|
const fromMoment = dateMath.parse(timefilter.getTime().from);
|
||||||
|
const toMoment = dateMath.parse(timefilter.getTime().to);
|
||||||
|
const from = fromMoment !== undefined ? fromMoment.valueOf() : 0;
|
||||||
|
const to = toMoment !== undefined ? toMoment.valueOf() : 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
to,
|
||||||
|
from,
|
||||||
|
};
|
||||||
|
}
|
|
@ -5,38 +5,50 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO Consolidate with near duplicate service in
|
|
||||||
// `x-pack/plugins/data_visualizer/public/application/index_data_visualizer/services/time_field_range.ts`
|
|
||||||
|
|
||||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||||
|
|
||||||
import type { HttpStart } from '@kbn/core/public';
|
import type { HttpStart } from '@kbn/core/public';
|
||||||
|
|
||||||
export interface GetTimeFieldRangeResponse {
|
import type { GetTimeFieldRangeResponse } from './types';
|
||||||
success: boolean;
|
|
||||||
start: { epoch: number; string: string };
|
/**
|
||||||
end: { epoch: number; string: string };
|
* Options definition for the `getTimeFieldRange` function.
|
||||||
|
*/
|
||||||
|
interface GetTimeFieldRangeOptions {
|
||||||
|
/**
|
||||||
|
* The index to be queried.
|
||||||
|
*/
|
||||||
|
index: string;
|
||||||
|
/**
|
||||||
|
* Optional time field name.
|
||||||
|
*/
|
||||||
|
timeFieldName?: string;
|
||||||
|
/**
|
||||||
|
* Optional DSL query.
|
||||||
|
*/
|
||||||
|
query?: QueryDslQueryContainer;
|
||||||
|
/**
|
||||||
|
* Optional runtime mappings.
|
||||||
|
*/
|
||||||
|
runtimeMappings?: estypes.MappingRuntimeFields;
|
||||||
|
/**
|
||||||
|
* HTTP client
|
||||||
|
*/
|
||||||
|
http: HttpStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTimeFieldRange({
|
/**
|
||||||
index,
|
*
|
||||||
timeFieldName,
|
* @param options - GetTimeFieldRangeOptions
|
||||||
query,
|
* @returns GetTimeFieldRangeResponse
|
||||||
runtimeMappings,
|
*/
|
||||||
http,
|
export async function getTimeFieldRange(options: GetTimeFieldRangeOptions) {
|
||||||
}: {
|
const { http, ...body } = options;
|
||||||
index: string;
|
|
||||||
timeFieldName?: string;
|
|
||||||
query?: QueryDslQueryContainer;
|
|
||||||
runtimeMappings?: estypes.MappingRuntimeFields;
|
|
||||||
http: HttpStart;
|
|
||||||
}) {
|
|
||||||
const body = JSON.stringify({ index, timeFieldName, query, runtimeMappings });
|
|
||||||
|
|
||||||
return await http.fetch<GetTimeFieldRangeResponse>({
|
return await http.fetch<GetTimeFieldRangeResponse>({
|
||||||
path: `/internal/file_upload/time_field_range`,
|
path: `/internal/file_upload/time_field_range`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body,
|
body: JSON.stringify(body),
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -5,14 +5,23 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO Consolidate with near duplicate service in
|
|
||||||
// `x-pack/plugins/data_visualizer/public/application/index_data_visualizer/services/timefilter_refresh_service.ts`
|
|
||||||
|
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State definition of `mlTimefilterRefresh$` observable.
|
||||||
|
*/
|
||||||
export interface Refresh {
|
export interface Refresh {
|
||||||
|
/**
|
||||||
|
* Timestamp of the last time a refresh got triggered.
|
||||||
|
*/
|
||||||
lastRefresh: number;
|
lastRefresh: number;
|
||||||
|
/**
|
||||||
|
* The time range triggered by the refresh.
|
||||||
|
*/
|
||||||
timeRange?: { start: string; end: string };
|
timeRange?: { start: string; end: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const aiopsRefresh$ = new Subject<Refresh>();
|
/**
|
||||||
|
* Observable to hold `Refresh` state.
|
||||||
|
*/
|
||||||
|
export const mlTimefilterRefresh$ = new Subject<Refresh>();
|
38
x-pack/packages/ml/date_picker/src/services/types.ts
Normal file
38
x-pack/packages/ml/date_picker/src/services/types.ts
Normal file
|
@ -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
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time definition for `GetTimeFieldRangeResponse` start/end attributes.
|
||||||
|
*/
|
||||||
|
interface GetTimeFieldRangeResponseTime {
|
||||||
|
/**
|
||||||
|
* Timestamp
|
||||||
|
*/
|
||||||
|
epoch: number;
|
||||||
|
/**
|
||||||
|
* String representation
|
||||||
|
*/
|
||||||
|
string: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response interface for the `setFullTimeRange` function.
|
||||||
|
*/
|
||||||
|
export interface GetTimeFieldRangeResponse {
|
||||||
|
/**
|
||||||
|
* Success boolean flag.
|
||||||
|
*/
|
||||||
|
success: boolean;
|
||||||
|
/**
|
||||||
|
* Start time of the time range.
|
||||||
|
*/
|
||||||
|
start: GetTimeFieldRangeResponseTime;
|
||||||
|
/**
|
||||||
|
* End time of the time range.
|
||||||
|
*/
|
||||||
|
end: GetTimeFieldRangeResponseTime;
|
||||||
|
}
|
20
x-pack/packages/ml/date_picker/src/storage.ts
Normal file
20
x-pack/packages/ml/date_picker/src/storage.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local storage options to include/exclude frozen tier.
|
||||||
|
*/
|
||||||
|
export const FROZEN_TIER_PREFERENCE = {
|
||||||
|
EXCLUDE: 'exclude-frozen',
|
||||||
|
INCLUDE: 'include-frozen',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Union type of `FROZEN_TIER_PREFERENCE` options.
|
||||||
|
*/
|
||||||
|
export type FrozenTierPreference =
|
||||||
|
typeof FROZEN_TIER_PREFERENCE[keyof typeof FROZEN_TIER_PREFERENCE];
|
31
x-pack/packages/ml/date_picker/tsconfig.json
Normal file
31
x-pack/packages/ml/date_picker/tsconfig.json
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"extends": "../../../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "target/types",
|
||||||
|
"types": [
|
||||||
|
"jest",
|
||||||
|
"node",
|
||||||
|
"react"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"target/**/*",
|
||||||
|
],
|
||||||
|
"kbn_references": [
|
||||||
|
"@kbn/es-query",
|
||||||
|
"@kbn/i18n",
|
||||||
|
"@kbn/i18n-react",
|
||||||
|
"@kbn/ml-url-state",
|
||||||
|
"@kbn/data-plugin",
|
||||||
|
"@kbn/datemath",
|
||||||
|
"@kbn/core",
|
||||||
|
"@kbn/data-views-plugin",
|
||||||
|
"@kbn/ml-is-populated-object",
|
||||||
|
"@kbn/kibana-react-plugin",
|
||||||
|
"@kbn/ml-query-utils",
|
||||||
|
]
|
||||||
|
}
|
|
@ -8,7 +8,7 @@
|
||||||
/**
|
/**
|
||||||
* Checks whether the supplied argument is not `undefined` and not `null`.
|
* Checks whether the supplied argument is not `undefined` and not `null`.
|
||||||
*
|
*
|
||||||
* @param argument
|
* @param argument - argument to check whether it is defined.
|
||||||
* @returns boolean
|
* @returns boolean
|
||||||
*/
|
*/
|
||||||
export function isDefined<T>(argument: T | undefined | null): argument is T {
|
export function isDefined<T>(argument: T | undefined | null): argument is T {
|
||||||
|
|
3
x-pack/packages/ml/query_utils/README.md
Normal file
3
x-pack/packages/ml/query_utils/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# @kbn/ml-query-utils
|
||||||
|
|
||||||
|
Query utilities.
|
|
@ -5,4 +5,4 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { FullTimeRangeSelector } from './full_time_range_selector';
|
export { addExcludeFrozenToQuery } from './src/add_exclude_frozen_to_query';
|
|
@ -5,4 +5,8 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { DatePickerWrapper } from './date_picker_wrapper';
|
module.exports = {
|
||||||
|
preset: '@kbn/test',
|
||||||
|
rootDir: '../../../..',
|
||||||
|
roots: ['<rootDir>/x-pack/packages/ml/query_utils'],
|
||||||
|
};
|
5
x-pack/packages/ml/query_utils/kibana.jsonc
Normal file
5
x-pack/packages/ml/query_utils/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"type": "shared-common",
|
||||||
|
"id": "@kbn/ml-query-utils",
|
||||||
|
"owner": "@elastic/ml-ui"
|
||||||
|
}
|
6
x-pack/packages/ml/query_utils/package.json
Normal file
6
x-pack/packages/ml/query_utils/package.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "@kbn/ml-query-utils",
|
||||||
|
"private": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||||
|
}
|
|
@ -5,7 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addExcludeFrozenToQuery } from './query_utils';
|
import { addExcludeFrozenToQuery } from './add_exclude_frozen_to_query';
|
||||||
|
|
||||||
describe('Util: addExcludeFrozenToQuery()', () => {
|
describe('Util: addExcludeFrozenToQuery()', () => {
|
||||||
test('Validation checks.', () => {
|
test('Validation checks.', () => {
|
|
@ -9,6 +9,12 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends an existing query with a clause to exclude the frozen tier.
|
||||||
|
*
|
||||||
|
* @param originalQuery - the original query
|
||||||
|
* @returns the original query exluding the frozen tier
|
||||||
|
*/
|
||||||
export const addExcludeFrozenToQuery = (originalQuery: QueryDslQueryContainer | undefined) => {
|
export const addExcludeFrozenToQuery = (originalQuery: QueryDslQueryContainer | undefined) => {
|
||||||
const FROZEN_TIER_TERM = {
|
const FROZEN_TIER_TERM = {
|
||||||
term: {
|
term: {
|
||||||
|
@ -31,11 +37,11 @@ export const addExcludeFrozenToQuery = (originalQuery: QueryDslQueryContainer |
|
||||||
delete query.match_all;
|
delete query.match_all;
|
||||||
|
|
||||||
if (isPopulatedObject(query.bool)) {
|
if (isPopulatedObject(query.bool)) {
|
||||||
// Must_not can be both arrays or singular object
|
// `must_not` can be both arrays or singular object
|
||||||
if (Array.isArray(query.bool.must_not)) {
|
if (Array.isArray(query.bool.must_not)) {
|
||||||
query.bool.must_not.push(FROZEN_TIER_TERM);
|
query.bool.must_not.push(FROZEN_TIER_TERM);
|
||||||
} else {
|
} else {
|
||||||
// If there's already a must_not condition
|
// If there's already a `must_not` condition
|
||||||
if (isPopulatedObject(query.bool.must_not)) {
|
if (isPopulatedObject(query.bool.must_not)) {
|
||||||
query.bool.must_not = [query.bool.must_not, FROZEN_TIER_TERM];
|
query.bool.must_not = [query.bool.must_not, FROZEN_TIER_TERM];
|
||||||
}
|
}
|
21
x-pack/packages/ml/query_utils/tsconfig.json
Normal file
21
x-pack/packages/ml/query_utils/tsconfig.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"extends": "../../../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "target/types",
|
||||||
|
"types": [
|
||||||
|
"jest",
|
||||||
|
"node",
|
||||||
|
"react"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"target/**/*"
|
||||||
|
],
|
||||||
|
"kbn_references": [
|
||||||
|
"@kbn/ml-is-populated-object",
|
||||||
|
]
|
||||||
|
}
|
|
@ -8,13 +8,9 @@
|
||||||
// TODO Consolidate with duplicate query utils in
|
// TODO Consolidate with duplicate query utils in
|
||||||
// `x-pack/plugins/data_visualizer/common/utils/query_utils.ts`
|
// `x-pack/plugins/data_visualizer/common/utils/query_utils.ts`
|
||||||
|
|
||||||
import { cloneDeep } from 'lodash';
|
|
||||||
|
|
||||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
|
||||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||||
|
|
||||||
import { Query } from '@kbn/es-query';
|
import type { Query } from '@kbn/es-query';
|
||||||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
|
||||||
import type { ChangePoint, FieldValuePair } from '@kbn/ml-agg-utils';
|
import type { ChangePoint, FieldValuePair } from '@kbn/ml-agg-utils';
|
||||||
import type { GroupTableItem } from '../../components/spike_analysis_table/types';
|
import type { GroupTableItem } from '../../components/spike_analysis_table/types';
|
||||||
|
|
||||||
|
@ -93,46 +89,3 @@ export function buildBaseFilterCriteria(
|
||||||
|
|
||||||
return filterCriteria;
|
return filterCriteria;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
|
@ -18,12 +18,12 @@ import { type DataViewField } from '@kbn/data-views-plugin/public';
|
||||||
import { startWith } from 'rxjs';
|
import { startWith } from 'rxjs';
|
||||||
import type { Query, Filter } from '@kbn/es-query';
|
import type { Query, Filter } from '@kbn/es-query';
|
||||||
import { usePageUrlState } from '@kbn/ml-url-state';
|
import { usePageUrlState } from '@kbn/ml-url-state';
|
||||||
|
import { useTimefilter, useTimeRangeUpdates } from '@kbn/ml-date-picker';
|
||||||
import {
|
import {
|
||||||
createMergedEsQuery,
|
createMergedEsQuery,
|
||||||
getEsQueryFromSavedSearch,
|
getEsQueryFromSavedSearch,
|
||||||
} from '../../application/utils/search_utils';
|
} from '../../application/utils/search_utils';
|
||||||
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
||||||
import { useTimefilter, useTimeRangeUpdates } from '../../hooks/use_time_filter';
|
|
||||||
import { useChangePointResults } from './use_change_point_agg_request';
|
import { useChangePointResults } from './use_change_point_agg_request';
|
||||||
import { type TimeBuckets, TimeBucketsInterval } from '../../../common/time_buckets';
|
import { type TimeBuckets, TimeBucketsInterval } from '../../../common/time_buckets';
|
||||||
import { useDataSource } from '../../hooks/use_data_source';
|
import { useDataSource } from '../../hooks/use_data_source';
|
||||||
|
|
|
@ -6,12 +6,18 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
|
import { EuiSpacer } from '@elastic/eui';
|
||||||
|
|
||||||
import { DataView } from '@kbn/data-views-plugin/common';
|
import { DataView } from '@kbn/data-views-plugin/common';
|
||||||
import { SavedSearch } from '@kbn/saved-search-plugin/public';
|
import { SavedSearch } from '@kbn/saved-search-plugin/public';
|
||||||
import { StorageContextProvider } from '@kbn/ml-local-storage';
|
import { StorageContextProvider } from '@kbn/ml-local-storage';
|
||||||
import { UrlStateProvider } from '@kbn/ml-url-state';
|
import { UrlStateProvider } from '@kbn/ml-url-state';
|
||||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||||
|
import { DatePickerContextProvider } from '@kbn/ml-date-picker';
|
||||||
|
import { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||||
|
import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public';
|
||||||
|
|
||||||
import { DataSourceContext } from '../../hooks/use_data_source';
|
import { DataSourceContext } from '../../hooks/use_data_source';
|
||||||
import { SavedSearchSavedObject } from '../../application/utils/search_utils';
|
import { SavedSearchSavedObject } from '../../application/utils/search_utils';
|
||||||
|
@ -36,15 +42,25 @@ export const ChangePointDetectionAppState: FC<ChangePointDetectionAppStateProps>
|
||||||
savedSearch,
|
savedSearch,
|
||||||
appDependencies,
|
appDependencies,
|
||||||
}) => {
|
}) => {
|
||||||
|
const datePickerDeps = {
|
||||||
|
...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings']),
|
||||||
|
toMountPoint,
|
||||||
|
wrapWithTheme,
|
||||||
|
uiSettingsKeys: UI_SETTINGS,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AiopsAppContext.Provider value={appDependencies}>
|
<AiopsAppContext.Provider value={appDependencies}>
|
||||||
<UrlStateProvider>
|
<UrlStateProvider>
|
||||||
<DataSourceContext.Provider value={{ dataView, savedSearch }}>
|
<DataSourceContext.Provider value={{ dataView, savedSearch }}>
|
||||||
<StorageContextProvider storage={localStorage} storageKeys={AIOPS_STORAGE_KEYS}>
|
<StorageContextProvider storage={localStorage} storageKeys={AIOPS_STORAGE_KEYS}>
|
||||||
<PageHeader />
|
<DatePickerContextProvider {...datePickerDeps}>
|
||||||
<ChangePointDetectionContextProvider>
|
<PageHeader />
|
||||||
<ChangePointDetectionPage />
|
<EuiSpacer />
|
||||||
</ChangePointDetectionContextProvider>
|
<ChangePointDetectionContextProvider>
|
||||||
|
<ChangePointDetectionPage />
|
||||||
|
</ChangePointDetectionContextProvider>
|
||||||
|
</DatePickerContextProvider>
|
||||||
</StorageContextProvider>
|
</StorageContextProvider>
|
||||||
</DataSourceContext.Provider>
|
</DataSourceContext.Provider>
|
||||||
</UrlStateProvider>
|
</UrlStateProvider>
|
||||||
|
|
|
@ -6,12 +6,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { FC, useMemo } from 'react';
|
import React, { FC, useMemo } from 'react';
|
||||||
|
|
||||||
import { type TypedLensByValueInput } from '@kbn/lens-plugin/public';
|
import { type TypedLensByValueInput } from '@kbn/lens-plugin/public';
|
||||||
import { FilterStateStore } from '@kbn/es-query';
|
import { FilterStateStore } from '@kbn/es-query';
|
||||||
import { useChangePointDetectionContext } from './change_point_detection_context';
|
import { useTimeRangeUpdates } from '@kbn/ml-date-picker';
|
||||||
|
|
||||||
import { useDataSource } from '../../hooks/use_data_source';
|
import { useDataSource } from '../../hooks/use_data_source';
|
||||||
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
||||||
import { useTimeRangeUpdates } from '../../hooks/use_time_filter';
|
|
||||||
|
import { useChangePointDetectionContext } from './change_point_detection_context';
|
||||||
import { fnOperationTypeMapping } from './constants';
|
import { fnOperationTypeMapping } from './constants';
|
||||||
|
|
||||||
export interface ChartComponentProps {
|
export interface ChartComponentProps {
|
||||||
|
|
|
@ -1,278 +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
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// TODO Consolidate with duplicate component `DatePickerWrapper` in
|
|
||||||
// `x-pack/plugins/data_visualizer/public/application/common/components/date_picker_wrapper/date_picker_wrapper.tsx`
|
|
||||||
// `x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.tsx`
|
|
||||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
import { debounce } from 'lodash';
|
|
||||||
|
|
||||||
import {
|
|
||||||
EuiButton,
|
|
||||||
EuiFlexGroup,
|
|
||||||
EuiFlexItem,
|
|
||||||
EuiSuperDatePicker,
|
|
||||||
OnRefreshProps,
|
|
||||||
OnTimeChangeProps,
|
|
||||||
} from '@elastic/eui';
|
|
||||||
|
|
||||||
import type { TimeRange } from '@kbn/es-query';
|
|
||||||
import { TimeHistoryContract, UI_SETTINGS } from '@kbn/data-plugin/public';
|
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
|
||||||
import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public';
|
|
||||||
import { useUrlState } from '@kbn/ml-url-state';
|
|
||||||
import { useRefreshIntervalUpdates, useTimeRangeUpdates } from '../../hooks/use_time_filter';
|
|
||||||
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
|
||||||
import { aiopsRefresh$ } from '../../application/services/timefilter_refresh_service';
|
|
||||||
|
|
||||||
const DEFAULT_REFRESH_INTERVAL_MS = 5000;
|
|
||||||
|
|
||||||
interface TimePickerQuickRange {
|
|
||||||
from: string;
|
|
||||||
to: string;
|
|
||||||
display: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Duration {
|
|
||||||
start: string;
|
|
||||||
end: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RefreshInterval {
|
|
||||||
pause: boolean;
|
|
||||||
value: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRecentlyUsedRangesFactory(timeHistory: TimeHistoryContract) {
|
|
||||||
return function (): Duration[] {
|
|
||||||
return (
|
|
||||||
timeHistory.get()?.map(({ from, to }: TimeRange) => {
|
|
||||||
return {
|
|
||||||
start: from,
|
|
||||||
end: to,
|
|
||||||
};
|
|
||||||
}) ?? []
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateLastRefresh(timeRange?: OnRefreshProps) {
|
|
||||||
aiopsRefresh$.next({ lastRefresh: Date.now(), timeRange });
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DatePickerWrapper: FC = () => {
|
|
||||||
const services = useAiopsAppContext();
|
|
||||||
const { toasts } = services.notifications;
|
|
||||||
const config = services.uiSettings;
|
|
||||||
|
|
||||||
const { timefilter, history } = services.data.query.timefilter;
|
|
||||||
const theme$ = services.theme.theme$;
|
|
||||||
|
|
||||||
const [globalState, setGlobalState] = useUrlState('_g');
|
|
||||||
const getRecentlyUsedRanges = getRecentlyUsedRangesFactory(history);
|
|
||||||
|
|
||||||
const timeFilterRefreshInterval = useRefreshIntervalUpdates();
|
|
||||||
const time = useTimeRangeUpdates();
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
function syncTimRangeFromUrlState() {
|
|
||||||
if (globalState?.time !== undefined) {
|
|
||||||
timefilter.setTime({
|
|
||||||
from: globalState.time.from,
|
|
||||||
to: globalState.time.to,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[globalState?.time?.from, globalState?.time?.to, globalState?.time?.ts]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
function syncRefreshIntervalFromUrlState() {
|
|
||||||
if (globalState?.refreshInterval !== undefined) {
|
|
||||||
timefilter.setRefreshInterval({
|
|
||||||
pause: !!globalState?.refreshInterval?.pause,
|
|
||||||
value: globalState?.refreshInterval?.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[globalState?.refreshInterval]
|
|
||||||
);
|
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
const setRefreshInterval = useCallback(
|
|
||||||
debounce((refreshIntervalUpdate: RefreshInterval) => {
|
|
||||||
setGlobalState('refreshInterval', refreshIntervalUpdate, true);
|
|
||||||
}, 200),
|
|
||||||
[setGlobalState]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [recentlyUsedRanges, setRecentlyUsedRanges] = useState(getRecentlyUsedRanges());
|
|
||||||
const [isAutoRefreshSelectorEnabled, setIsAutoRefreshSelectorEnabled] = useState(
|
|
||||||
timefilter.isAutoRefreshSelectorEnabled()
|
|
||||||
);
|
|
||||||
const [isTimeRangeSelectorEnabled, setIsTimeRangeSelectorEnabled] = useState(
|
|
||||||
timefilter.isTimeRangeSelectorEnabled()
|
|
||||||
);
|
|
||||||
|
|
||||||
const refreshInterval = useMemo(
|
|
||||||
(): RefreshInterval => globalState?.refreshInterval ?? timeFilterRefreshInterval,
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[JSON.stringify(globalState?.refreshInterval), timeFilterRefreshInterval]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
function warnAboutShortRefreshInterval() {
|
|
||||||
const isResolvedFromUrlState = !!globalState?.refreshInterval;
|
|
||||||
const isTooShort = refreshInterval.value < DEFAULT_REFRESH_INTERVAL_MS;
|
|
||||||
|
|
||||||
// Only warn about short interval with enabled auto-refresh.
|
|
||||||
if (!isTooShort || refreshInterval.pause) return;
|
|
||||||
|
|
||||||
toasts.addWarning(
|
|
||||||
{
|
|
||||||
title: isResolvedFromUrlState
|
|
||||||
? i18n.translate('xpack.aiops.datePicker.shortRefreshIntervalURLWarningMessage', {
|
|
||||||
defaultMessage:
|
|
||||||
'The refresh interval in the URL is shorter than the minimum supported by Machine Learning.',
|
|
||||||
})
|
|
||||||
: i18n.translate(
|
|
||||||
'xpack.aiops.datePicker.shortRefreshIntervalTimeFilterWarningMessage',
|
|
||||||
{
|
|
||||||
defaultMessage:
|
|
||||||
'The refresh interval in Advanced Settings is shorter than the minimum supported by Machine Learning.',
|
|
||||||
}
|
|
||||||
),
|
|
||||||
text: toMountPoint(
|
|
||||||
wrapWithTheme(
|
|
||||||
<EuiButton
|
|
||||||
onClick={setRefreshInterval.bind(null, {
|
|
||||||
pause: refreshInterval.pause,
|
|
||||||
value: DEFAULT_REFRESH_INTERVAL_MS,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="xpack.aiops.index.pageRefreshResetButton"
|
|
||||||
defaultMessage="Set to {defaultInterval}"
|
|
||||||
values={{
|
|
||||||
defaultInterval: `${DEFAULT_REFRESH_INTERVAL_MS / 1000}s`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</EuiButton>,
|
|
||||||
theme$
|
|
||||||
)
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{ toastLifeTimeMs: 30000 }
|
|
||||||
);
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
JSON.stringify(refreshInterval),
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
JSON.stringify(globalState?.refreshInterval),
|
|
||||||
setRefreshInterval,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
const dateFormat = config.get('dateFormat');
|
|
||||||
const timePickerQuickRanges = config.get<TimePickerQuickRange[]>(
|
|
||||||
UI_SETTINGS.TIMEPICKER_QUICK_RANGES
|
|
||||||
);
|
|
||||||
|
|
||||||
const commonlyUsedRanges = useMemo(
|
|
||||||
() =>
|
|
||||||
timePickerQuickRanges.map(({ from, to, display }) => ({
|
|
||||||
start: from,
|
|
||||||
end: to,
|
|
||||||
label: display,
|
|
||||||
})),
|
|
||||||
[timePickerQuickRanges]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const subscriptions = new Subscription();
|
|
||||||
|
|
||||||
const enabledUpdated$ = timefilter.getEnabledUpdated$();
|
|
||||||
if (enabledUpdated$ !== undefined) {
|
|
||||||
subscriptions.add(
|
|
||||||
enabledUpdated$.subscribe((w) => {
|
|
||||||
setIsAutoRefreshSelectorEnabled(timefilter.isAutoRefreshSelectorEnabled());
|
|
||||||
setIsTimeRangeSelectorEnabled(timefilter.isTimeRangeSelectorEnabled());
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return function cleanup() {
|
|
||||||
subscriptions.unsubscribe();
|
|
||||||
};
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const updateTimeFilter = useCallback(
|
|
||||||
({ start, end }: OnTimeChangeProps) => {
|
|
||||||
setRecentlyUsedRanges(getRecentlyUsedRanges());
|
|
||||||
setGlobalState('time', {
|
|
||||||
from: start,
|
|
||||||
to: end,
|
|
||||||
...(start === 'now' || end === 'now' ? { ts: Date.now() } : {}),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[setGlobalState]
|
|
||||||
);
|
|
||||||
|
|
||||||
function updateInterval({
|
|
||||||
isPaused: pause,
|
|
||||||
refreshInterval: value,
|
|
||||||
}: {
|
|
||||||
isPaused: boolean;
|
|
||||||
refreshInterval: number;
|
|
||||||
}) {
|
|
||||||
setRefreshInterval({ pause, value });
|
|
||||||
}
|
|
||||||
|
|
||||||
return isAutoRefreshSelectorEnabled || isTimeRangeSelectorEnabled ? (
|
|
||||||
<EuiFlexGroup gutterSize="s" alignItems="center" css={{ width: '100%', minWidth: 0 }}>
|
|
||||||
<EuiFlexItem grow={true} css={{ width: '100%' }}>
|
|
||||||
<EuiSuperDatePicker
|
|
||||||
css={{ width: '100%', minWidth: 0 }}
|
|
||||||
start={time.from}
|
|
||||||
end={time.to}
|
|
||||||
isPaused={refreshInterval.pause}
|
|
||||||
isAutoRefreshOnly={!isTimeRangeSelectorEnabled}
|
|
||||||
refreshInterval={refreshInterval.value || DEFAULT_REFRESH_INTERVAL_MS}
|
|
||||||
onTimeChange={updateTimeFilter}
|
|
||||||
onRefresh={updateLastRefresh}
|
|
||||||
onRefreshChange={updateInterval}
|
|
||||||
recentlyUsedRanges={recentlyUsedRanges}
|
|
||||||
dateFormat={dateFormat}
|
|
||||||
commonlyUsedRanges={commonlyUsedRanges}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
|
|
||||||
{isTimeRangeSelectorEnabled ? null : (
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<EuiButton
|
|
||||||
fill
|
|
||||||
color="primary"
|
|
||||||
iconType={'refresh'}
|
|
||||||
onClick={() => updateLastRefresh()}
|
|
||||||
data-test-subj="aiOpsRefreshPageButton"
|
|
||||||
>
|
|
||||||
<FormattedMessage id="xpack.aiops.pageRefreshButton" defaultMessage="Refresh" />
|
|
||||||
</EuiButton>
|
|
||||||
</EuiFlexItem>
|
|
||||||
)}
|
|
||||||
</EuiFlexGroup>
|
|
||||||
) : null;
|
|
||||||
};
|
|
|
@ -1,8 +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
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { DatePickerWrapper } from './date_picker_wrapper';
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
import { EuiCallOut } from '@elastic/eui';
|
import { EuiCallOut } from '@elastic/eui';
|
||||||
|
|
||||||
|
@ -16,6 +17,9 @@ import type { DataView } from '@kbn/data-views-plugin/public';
|
||||||
import { StorageContextProvider } from '@kbn/ml-local-storage';
|
import { StorageContextProvider } from '@kbn/ml-local-storage';
|
||||||
import { UrlStateProvider } from '@kbn/ml-url-state';
|
import { UrlStateProvider } from '@kbn/ml-url-state';
|
||||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||||
|
import { DatePickerContextProvider } from '@kbn/ml-date-picker';
|
||||||
|
import { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||||
|
import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SEARCH_QUERY_LANGUAGE,
|
SEARCH_QUERY_LANGUAGE,
|
||||||
|
@ -24,6 +28,7 @@ import {
|
||||||
} from '../../application/utils/search_utils';
|
} from '../../application/utils/search_utils';
|
||||||
import type { AiopsAppDependencies } from '../../hooks/use_aiops_app_context';
|
import type { AiopsAppDependencies } from '../../hooks/use_aiops_app_context';
|
||||||
import { AiopsAppContext } from '../../hooks/use_aiops_app_context';
|
import { AiopsAppContext } from '../../hooks/use_aiops_app_context';
|
||||||
|
import { DataSourceContext } from '../../hooks/use_data_source';
|
||||||
import { AIOPS_STORAGE_KEYS } from '../../types/storage';
|
import { AIOPS_STORAGE_KEYS } from '../../types/storage';
|
||||||
|
|
||||||
import { SpikeAnalysisTableRowStateProvider } from '../spike_analysis_table/spike_analysis_table_row_provider';
|
import { SpikeAnalysisTableRowStateProvider } from '../spike_analysis_table/spike_analysis_table_row_provider';
|
||||||
|
@ -95,14 +100,25 @@ export const ExplainLogRateSpikesAppState: FC<ExplainLogRateSpikesAppStateProps>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const datePickerDeps = {
|
||||||
|
...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings']),
|
||||||
|
toMountPoint,
|
||||||
|
wrapWithTheme,
|
||||||
|
uiSettingsKeys: UI_SETTINGS,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AiopsAppContext.Provider value={appDependencies}>
|
<AiopsAppContext.Provider value={appDependencies}>
|
||||||
<UrlStateProvider>
|
<UrlStateProvider>
|
||||||
<SpikeAnalysisTableRowStateProvider>
|
<DataSourceContext.Provider value={{ dataView, savedSearch }}>
|
||||||
<StorageContextProvider storage={localStorage} storageKeys={AIOPS_STORAGE_KEYS}>
|
<SpikeAnalysisTableRowStateProvider>
|
||||||
<ExplainLogRateSpikesPage dataView={dataView} savedSearch={savedSearch} />
|
<StorageContextProvider storage={localStorage} storageKeys={AIOPS_STORAGE_KEYS}>
|
||||||
</StorageContextProvider>
|
<DatePickerContextProvider {...datePickerDeps}>
|
||||||
</SpikeAnalysisTableRowStateProvider>
|
<ExplainLogRateSpikesPage />
|
||||||
|
</DatePickerContextProvider>
|
||||||
|
</StorageContextProvider>
|
||||||
|
</SpikeAnalysisTableRowStateProvider>
|
||||||
|
</DataSourceContext.Provider>
|
||||||
</UrlStateProvider>
|
</UrlStateProvider>
|
||||||
</AiopsAppContext.Provider>
|
</AiopsAppContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useState, FC } from 'react';
|
import React, { useCallback, useEffect, useState, FC } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EuiEmptyPrompt,
|
EuiEmptyPrompt,
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
|
@ -13,34 +14,29 @@ import {
|
||||||
EuiHorizontalRule,
|
EuiHorizontalRule,
|
||||||
EuiPageBody,
|
EuiPageBody,
|
||||||
EuiPageContentBody_Deprecated as EuiPageContentBody,
|
EuiPageContentBody_Deprecated as EuiPageContentBody,
|
||||||
EuiPageContentHeader_Deprecated as EuiPageContentHeader,
|
|
||||||
EuiPageContentHeaderSection_Deprecated as EuiPageContentHeaderSection,
|
|
||||||
EuiPanel,
|
EuiPanel,
|
||||||
EuiTitle,
|
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
|
||||||
import type { WindowParameters } from '@kbn/aiops-utils';
|
import type { WindowParameters } from '@kbn/aiops-utils';
|
||||||
import type { ChangePoint } from '@kbn/ml-agg-utils';
|
import type { ChangePoint } from '@kbn/ml-agg-utils';
|
||||||
import { Filter, FilterStateStore, Query } from '@kbn/es-query';
|
import { Filter, FilterStateStore, Query } from '@kbn/es-query';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import { SavedSearch } from '@kbn/discover-plugin/public';
|
|
||||||
|
|
||||||
import { useUrlState, usePageUrlState } from '@kbn/ml-url-state';
|
import { useUrlState, usePageUrlState } from '@kbn/ml-url-state';
|
||||||
import { useCss } from '../../hooks/use_css';
|
|
||||||
|
import { useDataSource } from '../../hooks/use_data_source';
|
||||||
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
||||||
import { SearchQueryLanguage, SavedSearchSavedObject } from '../../application/utils/search_utils';
|
import { SearchQueryLanguage } from '../../application/utils/search_utils';
|
||||||
import { useData } from '../../hooks/use_data';
|
import { useData } from '../../hooks/use_data';
|
||||||
import { FullTimeRangeSelector } from '../full_time_range_selector';
|
|
||||||
import { DocumentCountContent } from '../document_count_content/document_count_content';
|
import { DocumentCountContent } from '../document_count_content/document_count_content';
|
||||||
import { DatePickerWrapper } from '../date_picker_wrapper';
|
|
||||||
import { SearchPanel } from '../search_panel';
|
import { SearchPanel } from '../search_panel';
|
||||||
|
import type { GroupTableItem } from '../spike_analysis_table/types';
|
||||||
|
import { useSpikeAnalysisTableRowContext } from '../spike_analysis_table/spike_analysis_table_row_provider';
|
||||||
|
import { PageHeader } from '../page_header';
|
||||||
|
|
||||||
import { restorableDefaults, type AiOpsPageUrlState } from './explain_log_rate_spikes_app_state';
|
import { restorableDefaults, type AiOpsPageUrlState } from './explain_log_rate_spikes_app_state';
|
||||||
import { ExplainLogRateSpikesAnalysis } from './explain_log_rate_spikes_analysis';
|
import { ExplainLogRateSpikesAnalysis } from './explain_log_rate_spikes_analysis';
|
||||||
import type { GroupTableItem } from '../spike_analysis_table/types';
|
|
||||||
import { useSpikeAnalysisTableRowContext } from '../spike_analysis_table/spike_analysis_table_row_provider';
|
|
||||||
|
|
||||||
function getDocumentCountStatsSplitLabel(changePoint?: ChangePoint, group?: GroupTableItem) {
|
function getDocumentCountStatsSplitLabel(changePoint?: ChangePoint, group?: GroupTableItem) {
|
||||||
if (changePoint) {
|
if (changePoint) {
|
||||||
|
@ -52,22 +48,9 @@ function getDocumentCountStatsSplitLabel(changePoint?: ChangePoint, group?: Grou
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export const ExplainLogRateSpikesPage: FC = () => {
|
||||||
* ExplainLogRateSpikes props require a data view.
|
|
||||||
*/
|
|
||||||
interface ExplainLogRateSpikesPageProps {
|
|
||||||
/** The data view to analyze. */
|
|
||||||
dataView: DataView;
|
|
||||||
/** The saved search to analyze. */
|
|
||||||
savedSearch: SavedSearch | SavedSearchSavedObject | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ExplainLogRateSpikesPage: FC<ExplainLogRateSpikesPageProps> = ({
|
|
||||||
dataView,
|
|
||||||
savedSearch,
|
|
||||||
}) => {
|
|
||||||
const { aiopsPageHeader, dataViewTitleHeader } = useCss();
|
|
||||||
const { data: dataService } = useAiopsAppContext();
|
const { data: dataService } = useAiopsAppContext();
|
||||||
|
const { dataView, savedSearch } = useDataSource();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
currentSelectedChangePoint,
|
currentSelectedChangePoint,
|
||||||
|
@ -186,40 +169,7 @@ export const ExplainLogRateSpikesPage: FC<ExplainLogRateSpikesPageProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiPageBody data-test-subj="aiopsExplainLogRateSpikesPage" paddingSize="none" panelled={false}>
|
<EuiPageBody data-test-subj="aiopsExplainLogRateSpikesPage" paddingSize="none" panelled={false}>
|
||||||
<EuiFlexGroup gutterSize="none">
|
<PageHeader />
|
||||||
<EuiFlexItem>
|
|
||||||
<EuiPageContentHeader css={aiopsPageHeader}>
|
|
||||||
<EuiPageContentHeaderSection>
|
|
||||||
<div css={dataViewTitleHeader}>
|
|
||||||
<EuiTitle size={'s'}>
|
|
||||||
<h2>{dataView.getName()}</h2>
|
|
||||||
</EuiTitle>
|
|
||||||
</div>
|
|
||||||
</EuiPageContentHeaderSection>
|
|
||||||
|
|
||||||
<EuiFlexGroup
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="flexEnd"
|
|
||||||
gutterSize="s"
|
|
||||||
data-test-subj="aiopsTimeRangeSelectorSection"
|
|
||||||
>
|
|
||||||
{dataView.timeFieldName !== undefined && (
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<FullTimeRangeSelector
|
|
||||||
dataView={dataView}
|
|
||||||
query={undefined}
|
|
||||||
disabled={false}
|
|
||||||
timefilter={timefilter}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
)}
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<DatePickerWrapper />
|
|
||||||
</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
</EuiPageContentHeader>
|
|
||||||
</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
<EuiHorizontalRule />
|
<EuiHorizontalRule />
|
||||||
<EuiPageContentBody>
|
<EuiPageContentBody>
|
||||||
<EuiFlexGroup gutterSize="m" direction="column">
|
<EuiFlexGroup gutterSize="m" direction="column">
|
||||||
|
|
|
@ -1,75 +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
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// TODO Consolidate with duplicate service in
|
|
||||||
// `x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts`
|
|
||||||
|
|
||||||
import moment from 'moment';
|
|
||||||
import { TimefilterContract } from '@kbn/data-plugin/public';
|
|
||||||
import dateMath from '@kbn/datemath';
|
|
||||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import type { ToastsStart, HttpStart } from '@kbn/core/public';
|
|
||||||
import { DataView } from '@kbn/data-views-plugin/public';
|
|
||||||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
|
||||||
import { getTimeFieldRange } from '../../application/services/time_field_range';
|
|
||||||
import { addExcludeFrozenToQuery } from '../../application/utils/query_utils';
|
|
||||||
|
|
||||||
export interface GetTimeFieldRangeResponse {
|
|
||||||
success: boolean;
|
|
||||||
start: { epoch: number; string: string };
|
|
||||||
end: { epoch: number; string: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TimeRange {
|
|
||||||
from: number;
|
|
||||||
to: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function setFullTimeRange(
|
|
||||||
timefilter: TimefilterContract,
|
|
||||||
dataView: DataView,
|
|
||||||
toasts: ToastsStart,
|
|
||||||
http: HttpStart,
|
|
||||||
query?: QueryDslQueryContainer,
|
|
||||||
excludeFrozenData?: boolean
|
|
||||||
): Promise<GetTimeFieldRangeResponse> {
|
|
||||||
const runtimeMappings = dataView.getRuntimeMappings();
|
|
||||||
const resp = await getTimeFieldRange({
|
|
||||||
index: dataView.getIndexPattern(),
|
|
||||||
timeFieldName: dataView.timeFieldName,
|
|
||||||
query: excludeFrozenData ? addExcludeFrozenToQuery(query) : query,
|
|
||||||
...(isPopulatedObject(runtimeMappings) ? { runtimeMappings } : {}),
|
|
||||||
http,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (resp.start.epoch && resp.end.epoch) {
|
|
||||||
timefilter.setTime({
|
|
||||||
from: moment(resp.start.epoch).toISOString(),
|
|
||||||
to: moment(resp.end.epoch).toISOString(),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
toasts.addWarning({
|
|
||||||
title: i18n.translate('xpack.aiops.index.fullTimeRangeSelector.noResults', {
|
|
||||||
defaultMessage: 'No results match your search criteria',
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTimeFilterRange(timefilter: TimefilterContract): TimeRange {
|
|
||||||
const fromMoment = dateMath.parse(timefilter.getTime().from);
|
|
||||||
const toMoment = dateMath.parse(timefilter.getTime().to);
|
|
||||||
const from = fromMoment !== undefined ? fromMoment.valueOf() : 0;
|
|
||||||
const to = toMoment !== undefined ? toMoment.valueOf() : 0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
to,
|
|
||||||
from,
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -5,12 +5,18 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
import type { SavedSearch } from '@kbn/discover-plugin/public';
|
import type { SavedSearch } from '@kbn/discover-plugin/public';
|
||||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||||
import { StorageContextProvider } from '@kbn/ml-local-storage';
|
import { StorageContextProvider } from '@kbn/ml-local-storage';
|
||||||
import { UrlStateProvider } from '@kbn/ml-url-state';
|
import { UrlStateProvider } from '@kbn/ml-url-state';
|
||||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||||
|
import { DatePickerContextProvider } from '@kbn/ml-date-picker';
|
||||||
|
import { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||||
|
import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public';
|
||||||
|
|
||||||
|
import { DataSourceContext } from '../../hooks/use_data_source';
|
||||||
import { SavedSearchSavedObject } from '../../application/utils/search_utils';
|
import { SavedSearchSavedObject } from '../../application/utils/search_utils';
|
||||||
import type { AiopsAppDependencies } from '../../hooks/use_aiops_app_context';
|
import type { AiopsAppDependencies } from '../../hooks/use_aiops_app_context';
|
||||||
import { AIOPS_STORAGE_KEYS } from '../../types/storage';
|
import { AIOPS_STORAGE_KEYS } from '../../types/storage';
|
||||||
|
@ -31,12 +37,23 @@ export const LogCategorizationAppState: FC<LogCategorizationAppStateProps> = ({
|
||||||
savedSearch,
|
savedSearch,
|
||||||
appDependencies,
|
appDependencies,
|
||||||
}) => {
|
}) => {
|
||||||
|
const datePickerDeps = {
|
||||||
|
...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings']),
|
||||||
|
toMountPoint,
|
||||||
|
wrapWithTheme,
|
||||||
|
uiSettingsKeys: UI_SETTINGS,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AiopsAppContext.Provider value={appDependencies}>
|
<AiopsAppContext.Provider value={appDependencies}>
|
||||||
<UrlStateProvider>
|
<UrlStateProvider>
|
||||||
<StorageContextProvider storage={localStorage} storageKeys={AIOPS_STORAGE_KEYS}>
|
<DataSourceContext.Provider value={{ dataView, savedSearch }}>
|
||||||
<LogCategorizationPage dataView={dataView} savedSearch={savedSearch} />
|
<StorageContextProvider storage={localStorage} storageKeys={AIOPS_STORAGE_KEYS}>
|
||||||
</StorageContextProvider>
|
<DatePickerContextProvider {...datePickerDeps}>
|
||||||
|
<LogCategorizationPage />
|
||||||
|
</DatePickerContextProvider>
|
||||||
|
</StorageContextProvider>
|
||||||
|
</DataSourceContext.Provider>
|
||||||
</UrlStateProvider>
|
</UrlStateProvider>
|
||||||
</AiopsAppContext.Provider>
|
</AiopsAppContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,59 +5,46 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
import React, { FC, useState, useEffect, useCallback, useMemo } from 'react';
|
import React, { FC, useState, useEffect, useCallback, useMemo } from 'react';
|
||||||
import type { SavedSearch } from '@kbn/discover-plugin/public';
|
|
||||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
|
||||||
import { Filter, Query } from '@kbn/es-query';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
|
||||||
import {
|
import {
|
||||||
EuiButton,
|
EuiButton,
|
||||||
EuiSpacer,
|
EuiSpacer,
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
EuiFlexItem,
|
EuiFlexItem,
|
||||||
EuiPageBody,
|
EuiPageBody,
|
||||||
EuiPageContentHeader_Deprecated as EuiPageContentHeader,
|
|
||||||
EuiPageContentHeaderSection_Deprecated as EuiPageContentHeaderSection,
|
|
||||||
EuiTitle,
|
|
||||||
EuiComboBox,
|
EuiComboBox,
|
||||||
EuiComboBoxOptionOption,
|
EuiComboBoxOptionOption,
|
||||||
EuiFormRow,
|
EuiFormRow,
|
||||||
EuiLoadingContent,
|
EuiLoadingContent,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
|
|
||||||
|
import { Filter, Query } from '@kbn/es-query';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import { useUrlState } from '@kbn/ml-url-state';
|
import { useUrlState } from '@kbn/ml-url-state';
|
||||||
import { FullTimeRangeSelector } from '../full_time_range_selector';
|
|
||||||
import { DatePickerWrapper } from '../date_picker_wrapper';
|
import { useDataSource } from '../../hooks/use_data_source';
|
||||||
import { useCss } from '../../hooks/use_css';
|
|
||||||
import { useData } from '../../hooks/use_data';
|
import { useData } from '../../hooks/use_data';
|
||||||
import { SearchPanel } from '../search_panel';
|
import type { SearchQueryLanguage } from '../../application/utils/search_utils';
|
||||||
import type {
|
|
||||||
SearchQueryLanguage,
|
|
||||||
SavedSearchSavedObject,
|
|
||||||
} from '../../application/utils/search_utils';
|
|
||||||
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
||||||
|
|
||||||
import { restorableDefaults } from '../explain_log_rate_spikes/explain_log_rate_spikes_app_state';
|
import { restorableDefaults } from '../explain_log_rate_spikes/explain_log_rate_spikes_app_state';
|
||||||
import { useCategorizeRequest } from './use_categorize_request';
|
import { SearchPanel } from '../search_panel';
|
||||||
|
import { PageHeader } from '../page_header';
|
||||||
|
|
||||||
import type { EventRate, Category, SparkLinesPerCategory } from './use_categorize_request';
|
import type { EventRate, Category, SparkLinesPerCategory } from './use_categorize_request';
|
||||||
|
import { useCategorizeRequest } from './use_categorize_request';
|
||||||
import { CategoryTable } from './category_table';
|
import { CategoryTable } from './category_table';
|
||||||
import { DocumentCountChart } from './document_count_chart';
|
import { DocumentCountChart } from './document_count_chart';
|
||||||
import { InformationText } from './information_text';
|
import { InformationText } from './information_text';
|
||||||
|
|
||||||
export interface LogCategorizationPageProps {
|
|
||||||
dataView: DataView;
|
|
||||||
savedSearch: SavedSearch | SavedSearchSavedObject | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BAR_TARGET = 20;
|
const BAR_TARGET = 20;
|
||||||
|
|
||||||
export const LogCategorizationPage: FC<LogCategorizationPageProps> = ({
|
export const LogCategorizationPage: FC = () => {
|
||||||
dataView,
|
|
||||||
savedSearch,
|
|
||||||
}) => {
|
|
||||||
const { aiopsPageHeader, dataViewTitleHeader } = useCss();
|
|
||||||
const {
|
const {
|
||||||
notifications: { toasts },
|
notifications: { toasts },
|
||||||
} = useAiopsAppContext();
|
} = useAiopsAppContext();
|
||||||
|
const { dataView, savedSearch } = useDataSource();
|
||||||
|
|
||||||
const { runCategorizeRequest, cancelRequest } = useCategorizeRequest();
|
const { runCategorizeRequest, cancelRequest } = useCategorizeRequest();
|
||||||
const [aiopsListState, setAiopsListState] = useState(restorableDefaults);
|
const [aiopsListState, setAiopsListState] = useState(restorableDefaults);
|
||||||
|
@ -219,41 +206,8 @@ export const LogCategorizationPage: FC<LogCategorizationPageProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiPageBody data-test-subj="aiopsExplainLogRateSpikesPage" paddingSize="none" panelled={false}>
|
<EuiPageBody data-test-subj="aiopsLogCategorizationPage" paddingSize="none" panelled={false}>
|
||||||
<EuiFlexGroup gutterSize="none">
|
<PageHeader />
|
||||||
<EuiFlexItem>
|
|
||||||
<EuiPageContentHeader css={aiopsPageHeader}>
|
|
||||||
<EuiPageContentHeaderSection>
|
|
||||||
<div css={dataViewTitleHeader}>
|
|
||||||
<EuiTitle size="s">
|
|
||||||
<h2>{dataView.getName()}</h2>
|
|
||||||
</EuiTitle>
|
|
||||||
</div>
|
|
||||||
</EuiPageContentHeaderSection>
|
|
||||||
|
|
||||||
<EuiFlexGroup
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="flexEnd"
|
|
||||||
gutterSize="s"
|
|
||||||
data-test-subj="aiopsTimeRangeSelectorSection"
|
|
||||||
>
|
|
||||||
{dataView.timeFieldName !== undefined && (
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<FullTimeRangeSelector
|
|
||||||
dataView={dataView}
|
|
||||||
query={undefined}
|
|
||||||
disabled={false}
|
|
||||||
timefilter={timefilter}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
)}
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<DatePickerWrapper />
|
|
||||||
</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
</EuiPageContentHeader>
|
|
||||||
</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
<EuiSpacer />
|
<EuiSpacer />
|
||||||
<EuiFlexGroup gutterSize="none">
|
<EuiFlexGroup gutterSize="none">
|
||||||
<EuiFlexItem>
|
<EuiFlexItem>
|
||||||
|
|
|
@ -5,8 +5,10 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { FC, useCallback } from 'react';
|
import React, { FC, useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
useIsWithinMaxBreakpoint,
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
EuiFlexItem,
|
EuiFlexItem,
|
||||||
EuiSpacer,
|
EuiSpacer,
|
||||||
|
@ -14,19 +16,40 @@ import {
|
||||||
EuiPageContentHeader_Deprecated as EuiPageContentHeader,
|
EuiPageContentHeader_Deprecated as EuiPageContentHeader,
|
||||||
EuiPageContentHeaderSection_Deprecated as EuiPageContentHeaderSection,
|
EuiPageContentHeaderSection_Deprecated as EuiPageContentHeaderSection,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
|
|
||||||
import { useUrlState } from '@kbn/ml-url-state';
|
import { useUrlState } from '@kbn/ml-url-state';
|
||||||
import { FullTimeRangeSelectorProps } from '../full_time_range_selector/full_time_range_selector';
|
import { useStorage } from '@kbn/ml-local-storage';
|
||||||
|
import {
|
||||||
|
useTimefilter,
|
||||||
|
DatePickerWrapper,
|
||||||
|
FullTimeRangeSelector,
|
||||||
|
type FullTimeRangeSelectorProps,
|
||||||
|
FROZEN_TIER_PREFERENCE,
|
||||||
|
} from '@kbn/ml-date-picker';
|
||||||
|
|
||||||
import { useCss } from '../../hooks/use_css';
|
import { useCss } from '../../hooks/use_css';
|
||||||
import { useDataSource } from '../../hooks/use_data_source';
|
import { useDataSource } from '../../hooks/use_data_source';
|
||||||
import { useTimefilter } from '../../hooks/use_time_filter';
|
import {
|
||||||
import { FullTimeRangeSelector } from '../full_time_range_selector';
|
AIOPS_FROZEN_TIER_PREFERENCE,
|
||||||
import { DatePickerWrapper } from '../date_picker_wrapper';
|
type AiOpsKey,
|
||||||
|
type AiOpsStorageMapped,
|
||||||
|
} from '../../types/storage';
|
||||||
|
|
||||||
export const PageHeader: FC = () => {
|
export const PageHeader: FC = () => {
|
||||||
const { aiopsPageHeader, dataViewTitleHeader } = useCss();
|
const { aiopsPageHeader, dataViewTitleHeader } = useCss();
|
||||||
|
|
||||||
const [, setGlobalState] = useUrlState('_g');
|
const [, setGlobalState] = useUrlState('_g');
|
||||||
const { dataView } = useDataSource();
|
const { dataView } = useDataSource();
|
||||||
|
|
||||||
|
const [frozenDataPreference, setFrozenDataPreference] = useStorage<
|
||||||
|
AiOpsKey,
|
||||||
|
AiOpsStorageMapped<typeof AIOPS_FROZEN_TIER_PREFERENCE>
|
||||||
|
>(
|
||||||
|
AIOPS_FROZEN_TIER_PREFERENCE,
|
||||||
|
// By default we will exclude frozen data tier
|
||||||
|
FROZEN_TIER_PREFERENCE.EXCLUDE
|
||||||
|
);
|
||||||
|
|
||||||
const timefilter = useTimefilter({
|
const timefilter = useTimefilter({
|
||||||
timeRangeSelector: dataView.timeFieldName !== undefined,
|
timeRangeSelector: dataView.timeFieldName !== undefined,
|
||||||
autoRefreshSelector: true,
|
autoRefreshSelector: true,
|
||||||
|
@ -39,44 +62,54 @@ export const PageHeader: FC = () => {
|
||||||
[setGlobalState]
|
[setGlobalState]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
const hasValidTimeField = useMemo(
|
||||||
<>
|
() => dataView.timeFieldName !== undefined && dataView.timeFieldName !== '',
|
||||||
<EuiFlexGroup gutterSize="none">
|
[dataView.timeFieldName]
|
||||||
<EuiFlexItem>
|
);
|
||||||
<EuiPageContentHeader css={aiopsPageHeader}>
|
|
||||||
<EuiPageContentHeaderSection>
|
|
||||||
<div css={dataViewTitleHeader}>
|
|
||||||
<EuiTitle size="s">
|
|
||||||
<h2>{dataView.getName()}</h2>
|
|
||||||
</EuiTitle>
|
|
||||||
</div>
|
|
||||||
</EuiPageContentHeaderSection>
|
|
||||||
|
|
||||||
<EuiFlexGroup
|
const isWithinLBreakpoint = useIsWithinMaxBreakpoint('l');
|
||||||
alignItems="center"
|
|
||||||
justifyContent="flexEnd"
|
return (
|
||||||
gutterSize="s"
|
<EuiFlexGroup gutterSize="none">
|
||||||
data-test-subj="aiopsTimeRangeSelectorSection"
|
<EuiFlexItem>
|
||||||
>
|
<EuiPageContentHeader css={aiopsPageHeader}>
|
||||||
{dataView.timeFieldName !== undefined && (
|
<EuiPageContentHeaderSection>
|
||||||
<EuiFlexItem grow={false}>
|
<div css={dataViewTitleHeader}>
|
||||||
<FullTimeRangeSelector
|
<EuiTitle size="s">
|
||||||
dataView={dataView}
|
<h2>{dataView.getName()}</h2>
|
||||||
query={undefined}
|
</EuiTitle>
|
||||||
disabled={false}
|
</div>
|
||||||
timefilter={timefilter}
|
</EuiPageContentHeaderSection>
|
||||||
callback={updateTimeState}
|
|
||||||
/>
|
{isWithinLBreakpoint ? <EuiSpacer size="m" /> : null}
|
||||||
</EuiFlexItem>
|
<EuiFlexGroup
|
||||||
)}
|
alignItems="center"
|
||||||
|
justifyContent="flexEnd"
|
||||||
|
gutterSize="s"
|
||||||
|
data-test-subj="aiopsTimeRangeSelectorSection"
|
||||||
|
>
|
||||||
|
{hasValidTimeField ? (
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<DatePickerWrapper />
|
<FullTimeRangeSelector
|
||||||
|
frozenDataPreference={frozenDataPreference}
|
||||||
|
setFrozenDataPreference={setFrozenDataPreference}
|
||||||
|
dataView={dataView}
|
||||||
|
query={undefined}
|
||||||
|
disabled={false}
|
||||||
|
timefilter={timefilter}
|
||||||
|
callback={updateTimeState}
|
||||||
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
) : null}
|
||||||
</EuiPageContentHeader>
|
<EuiFlexItem grow={false}>
|
||||||
</EuiFlexItem>
|
<DatePickerWrapper
|
||||||
</EuiFlexGroup>
|
isAutoRefreshOnly={!hasValidTimeField}
|
||||||
<EuiSpacer />
|
showRefresh={!hasValidTimeField}
|
||||||
</>
|
/>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</EuiPageContentHeader>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,8 +14,13 @@ import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/
|
||||||
import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
|
import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
|
||||||
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
||||||
import type { SharePluginStart } from '@kbn/share-plugin/public';
|
import type { SharePluginStart } from '@kbn/share-plugin/public';
|
||||||
import type { CoreStart, CoreSetup, HttpStart, IUiSettingsClient } from '@kbn/core/public';
|
import type {
|
||||||
import type { ThemeServiceStart } from '@kbn/core/public';
|
CoreStart,
|
||||||
|
CoreSetup,
|
||||||
|
HttpStart,
|
||||||
|
IUiSettingsClient,
|
||||||
|
ThemeServiceStart,
|
||||||
|
} from '@kbn/core/public';
|
||||||
import type { LensPublicStart } from '@kbn/lens-plugin/public';
|
import type { LensPublicStart } from '@kbn/lens-plugin/public';
|
||||||
|
|
||||||
export interface AiopsAppDependencies {
|
export interface AiopsAppDependencies {
|
||||||
|
|
|
@ -10,25 +10,23 @@ import { merge } from 'rxjs';
|
||||||
|
|
||||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||||
import type { ChangePoint } from '@kbn/ml-agg-utils';
|
import type { ChangePoint } from '@kbn/ml-agg-utils';
|
||||||
|
|
||||||
import type { SavedSearch } from '@kbn/discover-plugin/public';
|
import type { SavedSearch } from '@kbn/discover-plugin/public';
|
||||||
|
|
||||||
import type { Dictionary } from '@kbn/ml-url-state';
|
import type { Dictionary } from '@kbn/ml-url-state';
|
||||||
import { useTimeBuckets } from './use_time_buckets';
|
import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker';
|
||||||
|
|
||||||
import { useAiopsAppContext } from './use_aiops_app_context';
|
|
||||||
import { aiopsRefresh$ } from '../application/services/timefilter_refresh_service';
|
|
||||||
import type { DocumentStatsSearchStrategyParams } from '../get_document_stats';
|
import type { DocumentStatsSearchStrategyParams } from '../get_document_stats';
|
||||||
import type { AiOpsIndexBasedAppState } from '../components/explain_log_rate_spikes/explain_log_rate_spikes_app_state';
|
import type { AiOpsIndexBasedAppState } from '../components/explain_log_rate_spikes/explain_log_rate_spikes_app_state';
|
||||||
import {
|
import {
|
||||||
getEsQueryFromSavedSearch,
|
getEsQueryFromSavedSearch,
|
||||||
SavedSearchSavedObject,
|
SavedSearchSavedObject,
|
||||||
} from '../application/utils/search_utils';
|
} from '../application/utils/search_utils';
|
||||||
|
|
||||||
import { useTimefilter } from './use_time_filter';
|
|
||||||
import { useDocumentCountStats } from './use_document_count_stats';
|
|
||||||
import type { GroupTableItem } from '../components/spike_analysis_table/types';
|
import type { GroupTableItem } from '../components/spike_analysis_table/types';
|
||||||
|
|
||||||
|
import { useTimeBuckets } from './use_time_buckets';
|
||||||
|
import { useAiopsAppContext } from './use_aiops_app_context';
|
||||||
|
|
||||||
|
import { useDocumentCountStats } from './use_document_count_stats';
|
||||||
|
|
||||||
const DEFAULT_BAR_TARGET = 75;
|
const DEFAULT_BAR_TARGET = 75;
|
||||||
|
|
||||||
export const useData = (
|
export const useData = (
|
||||||
|
@ -153,7 +151,7 @@ export const useData = (
|
||||||
const timeUpdateSubscription = merge(
|
const timeUpdateSubscription = merge(
|
||||||
timefilter.getAutoRefreshFetch$(),
|
timefilter.getAutoRefreshFetch$(),
|
||||||
timefilter.getTimeUpdate$(),
|
timefilter.getTimeUpdate$(),
|
||||||
aiopsRefresh$
|
mlTimefilterRefresh$
|
||||||
).subscribe(() => {
|
).subscribe(() => {
|
||||||
if (onUpdate) {
|
if (onUpdate) {
|
||||||
onUpdate({
|
onUpdate({
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const DataSourceContext = createContext<{
|
||||||
savedSearch: SavedSearch | SavedSearchSavedObject | null;
|
savedSearch: SavedSearch | SavedSearchSavedObject | null;
|
||||||
}>({
|
}>({
|
||||||
get dataView(): never {
|
get dataView(): never {
|
||||||
throw new Error('Context is not implemented');
|
throw new Error('DataSourceContext is not implemented');
|
||||||
},
|
},
|
||||||
savedSearch: null,
|
savedSearch: null,
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,16 +5,10 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { type FrozenTierPreference } from '@kbn/ml-date-picker';
|
||||||
|
|
||||||
export const AIOPS_FROZEN_TIER_PREFERENCE = 'aiops.frozenDataTierPreference';
|
export const AIOPS_FROZEN_TIER_PREFERENCE = 'aiops.frozenDataTierPreference';
|
||||||
|
|
||||||
export const FROZEN_TIER_PREFERENCE = {
|
|
||||||
EXCLUDE: 'exclude-frozen',
|
|
||||||
INCLUDE: 'include-frozen',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type FrozenTierPreference =
|
|
||||||
typeof FROZEN_TIER_PREFERENCE[keyof typeof FROZEN_TIER_PREFERENCE];
|
|
||||||
|
|
||||||
export type AiOps = Partial<{
|
export type AiOps = Partial<{
|
||||||
[AIOPS_FROZEN_TIER_PREFERENCE]: FrozenTierPreference;
|
[AIOPS_FROZEN_TIER_PREFERENCE]: FrozenTierPreference;
|
||||||
}> | null;
|
}> | null;
|
||||||
|
|
|
@ -44,6 +44,8 @@
|
||||||
"@kbn/es-types",
|
"@kbn/es-types",
|
||||||
"@kbn/ml-url-state",
|
"@kbn/ml-url-state",
|
||||||
"@kbn/ml-local-storage",
|
"@kbn/ml-local-storage",
|
||||||
|
"@kbn/ml-date-picker",
|
||||||
|
"@kbn/ml-local-storage",
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"target/**/*",
|
"target/**/*",
|
||||||
|
|
|
@ -1,295 +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
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// TODO Consolidate with duplicate component `DatePickerWrapper` in
|
|
||||||
// `x-pack/plugins/aiops/public/components/date_picker_wrapper/date_picker_wrapper.tsx`
|
|
||||||
|
|
||||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
import { debounce } from 'lodash';
|
|
||||||
|
|
||||||
import {
|
|
||||||
EuiButton,
|
|
||||||
EuiFlexGroup,
|
|
||||||
EuiFlexItem,
|
|
||||||
EuiSuperDatePicker,
|
|
||||||
OnRefreshProps,
|
|
||||||
OnTimeChangeProps,
|
|
||||||
} from '@elastic/eui';
|
|
||||||
|
|
||||||
import type { TimeRange } from '@kbn/es-query';
|
|
||||||
import { TimeHistoryContract, UI_SETTINGS } from '@kbn/data-plugin/public';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { useUrlState } from '@kbn/ml-url-state';
|
|
||||||
import { wrapWithTheme } from '@kbn/kibana-react-plugin/public';
|
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
|
||||||
|
|
||||||
import {
|
|
||||||
useRefreshIntervalUpdates,
|
|
||||||
useTimeRangeUpdates,
|
|
||||||
} from '../../../index_data_visualizer/hooks/use_time_filter';
|
|
||||||
import { useDataVisualizerKibana } from '../../../kibana_context';
|
|
||||||
import { dataVisualizerRefresh$ } from '../../../index_data_visualizer/services/timefilter_refresh_service';
|
|
||||||
|
|
||||||
const DEFAULT_REFRESH_INTERVAL_MS = 5000;
|
|
||||||
const DATE_PICKER_MAX_WIDTH = 540;
|
|
||||||
|
|
||||||
interface TimePickerQuickRange {
|
|
||||||
from: string;
|
|
||||||
to: string;
|
|
||||||
display: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Duration {
|
|
||||||
start: string;
|
|
||||||
end: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RefreshInterval {
|
|
||||||
pause: boolean;
|
|
||||||
value: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRecentlyUsedRangesFactory(timeHistory: TimeHistoryContract) {
|
|
||||||
return function (): Duration[] {
|
|
||||||
return (
|
|
||||||
timeHistory.get()?.map(({ from, to }: TimeRange) => {
|
|
||||||
return {
|
|
||||||
start: from,
|
|
||||||
end: to,
|
|
||||||
};
|
|
||||||
}) ?? []
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateLastRefresh(timeRange?: OnRefreshProps) {
|
|
||||||
dataVisualizerRefresh$.next({ lastRefresh: Date.now(), timeRange });
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Consolidate this component with ML and AIOps's component
|
|
||||||
export const DatePickerWrapper: FC<{
|
|
||||||
isAutoRefreshOnly?: boolean;
|
|
||||||
showRefresh?: boolean;
|
|
||||||
compact?: boolean;
|
|
||||||
}> = ({ isAutoRefreshOnly, showRefresh, compact = false }) => {
|
|
||||||
const {
|
|
||||||
services,
|
|
||||||
notifications: { toasts },
|
|
||||||
} = useDataVisualizerKibana();
|
|
||||||
const config = services.uiSettings;
|
|
||||||
const theme$ = services.theme.theme$;
|
|
||||||
|
|
||||||
const { timefilter, history } = services.data.query.timefilter;
|
|
||||||
|
|
||||||
const [globalState, setGlobalState] = useUrlState('_g');
|
|
||||||
const getRecentlyUsedRanges = getRecentlyUsedRangesFactory(history);
|
|
||||||
|
|
||||||
const timeFilterRefreshInterval = useRefreshIntervalUpdates();
|
|
||||||
const time = useTimeRangeUpdates();
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
function syncTimRangeFromUrlState() {
|
|
||||||
if (globalState?.time !== undefined) {
|
|
||||||
timefilter.setTime({
|
|
||||||
from: globalState.time.from,
|
|
||||||
to: globalState.time.to,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[globalState?.time?.from, globalState?.time?.to, globalState?.time?.ts]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
function syncRefreshIntervalFromUrlState() {
|
|
||||||
if (globalState?.refreshInterval !== undefined) {
|
|
||||||
timefilter.setRefreshInterval({
|
|
||||||
pause: !!globalState?.refreshInterval?.pause,
|
|
||||||
value: globalState?.refreshInterval?.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[globalState?.refreshInterval]
|
|
||||||
);
|
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
const setRefreshInterval = useCallback(
|
|
||||||
debounce((refreshIntervalUpdate: RefreshInterval) => {
|
|
||||||
setGlobalState('refreshInterval', refreshIntervalUpdate, true);
|
|
||||||
}, 200),
|
|
||||||
[setGlobalState]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [recentlyUsedRanges, setRecentlyUsedRanges] = useState(getRecentlyUsedRanges());
|
|
||||||
const [isAutoRefreshSelectorEnabled, setIsAutoRefreshSelectorEnabled] = useState(
|
|
||||||
timefilter.isAutoRefreshSelectorEnabled()
|
|
||||||
);
|
|
||||||
const [isTimeRangeSelectorEnabled, setIsTimeRangeSelectorEnabled] = useState(
|
|
||||||
timefilter.isTimeRangeSelectorEnabled()
|
|
||||||
);
|
|
||||||
|
|
||||||
const refreshInterval = useMemo(
|
|
||||||
(): RefreshInterval => globalState?.refreshInterval ?? timeFilterRefreshInterval,
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[JSON.stringify(globalState?.refreshInterval), timeFilterRefreshInterval]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
function warnAboutShortRefreshInterval() {
|
|
||||||
const isTooShort = refreshInterval.value < DEFAULT_REFRESH_INTERVAL_MS;
|
|
||||||
|
|
||||||
// Only warn about short interval with enabled auto-refresh.
|
|
||||||
if (!isTooShort || refreshInterval.pause) return;
|
|
||||||
|
|
||||||
toasts.warning({
|
|
||||||
title: i18n.translate(
|
|
||||||
'xpack.dataVisualizer.index.datePicker.shortRefreshIntervalURLWarningMessage',
|
|
||||||
{
|
|
||||||
defaultMessage:
|
|
||||||
'The refresh interval in the URL is shorter than the minimum supported by Machine Learning.',
|
|
||||||
}
|
|
||||||
),
|
|
||||||
body: wrapWithTheme(
|
|
||||||
<EuiButton
|
|
||||||
onClick={setRefreshInterval.bind(null, {
|
|
||||||
pause: refreshInterval.pause,
|
|
||||||
value: DEFAULT_REFRESH_INTERVAL_MS,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="xpack.dataVisualizer.index.pageRefreshResetButton"
|
|
||||||
defaultMessage="Set to {defaultInterval}"
|
|
||||||
values={{
|
|
||||||
defaultInterval: `${DEFAULT_REFRESH_INTERVAL_MS / 1000}s`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</EuiButton>,
|
|
||||||
theme$
|
|
||||||
),
|
|
||||||
toastLifeTimeMs: 30000,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
JSON.stringify(refreshInterval),
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
JSON.stringify(globalState?.refreshInterval),
|
|
||||||
setRefreshInterval,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
const dateFormat = config.get('dateFormat');
|
|
||||||
const timePickerQuickRanges = config.get<TimePickerQuickRange[]>(
|
|
||||||
UI_SETTINGS.TIMEPICKER_QUICK_RANGES
|
|
||||||
);
|
|
||||||
|
|
||||||
const commonlyUsedRanges = useMemo(
|
|
||||||
() =>
|
|
||||||
timePickerQuickRanges.map(({ from, to, display }) => ({
|
|
||||||
start: from,
|
|
||||||
end: to,
|
|
||||||
label: display,
|
|
||||||
})),
|
|
||||||
[timePickerQuickRanges]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const subscriptions = new Subscription();
|
|
||||||
|
|
||||||
const enabledUpdated$ = timefilter.getEnabledUpdated$();
|
|
||||||
if (enabledUpdated$ !== undefined) {
|
|
||||||
subscriptions.add(
|
|
||||||
enabledUpdated$.subscribe((w) => {
|
|
||||||
setIsAutoRefreshSelectorEnabled(timefilter.isAutoRefreshSelectorEnabled());
|
|
||||||
setIsTimeRangeSelectorEnabled(timefilter.isTimeRangeSelectorEnabled());
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return function cleanup() {
|
|
||||||
subscriptions.unsubscribe();
|
|
||||||
};
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const updateTimeFilter = useCallback(
|
|
||||||
({ start, end }: OnTimeChangeProps) => {
|
|
||||||
setRecentlyUsedRanges(getRecentlyUsedRanges());
|
|
||||||
setGlobalState('time', {
|
|
||||||
from: start,
|
|
||||||
to: end,
|
|
||||||
...(start === 'now' || end === 'now' ? { ts: Date.now() } : {}),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[setGlobalState]
|
|
||||||
);
|
|
||||||
|
|
||||||
function updateInterval({
|
|
||||||
isPaused: pause,
|
|
||||||
refreshInterval: value,
|
|
||||||
}: {
|
|
||||||
isPaused: boolean;
|
|
||||||
refreshInterval: number;
|
|
||||||
}) {
|
|
||||||
setRefreshInterval({ pause, value });
|
|
||||||
}
|
|
||||||
|
|
||||||
return isAutoRefreshSelectorEnabled || isTimeRangeSelectorEnabled ? (
|
|
||||||
<EuiFlexGroup
|
|
||||||
gutterSize="s"
|
|
||||||
alignItems="center"
|
|
||||||
data-test-subj="mlNavigationMenuDatePickerWrapper"
|
|
||||||
>
|
|
||||||
<EuiFlexItem
|
|
||||||
grow={false}
|
|
||||||
css={
|
|
||||||
compact
|
|
||||||
? {
|
|
||||||
maxWidth: DATE_PICKER_MAX_WIDTH,
|
|
||||||
}
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<EuiSuperDatePicker
|
|
||||||
start={time.from}
|
|
||||||
end={time.to}
|
|
||||||
isPaused={refreshInterval.pause}
|
|
||||||
isAutoRefreshOnly={!isTimeRangeSelectorEnabled || isAutoRefreshOnly}
|
|
||||||
refreshInterval={refreshInterval.value || DEFAULT_REFRESH_INTERVAL_MS}
|
|
||||||
onTimeChange={updateTimeFilter}
|
|
||||||
onRefresh={updateLastRefresh}
|
|
||||||
onRefreshChange={updateInterval}
|
|
||||||
recentlyUsedRanges={recentlyUsedRanges}
|
|
||||||
dateFormat={dateFormat}
|
|
||||||
commonlyUsedRanges={commonlyUsedRanges}
|
|
||||||
updateButtonProps={{ iconOnly: compact }}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
|
|
||||||
{showRefresh === true || !isTimeRangeSelectorEnabled ? (
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<EuiButton
|
|
||||||
fill
|
|
||||||
color="primary"
|
|
||||||
iconType={'refresh'}
|
|
||||||
onClick={() => updateLastRefresh()}
|
|
||||||
data-test-subj="dataVisualizerRefreshPageButton"
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="xpack.dataVisualizer.index.pageRefreshButton"
|
|
||||||
defaultMessage="Refresh"
|
|
||||||
/>
|
|
||||||
</EuiButton>
|
|
||||||
</EuiFlexItem>
|
|
||||||
) : null}
|
|
||||||
</EuiFlexGroup>
|
|
||||||
) : null;
|
|
||||||
};
|
|
|
@ -10,14 +10,11 @@ import { Action } from '@elastic/eui/src/components/basic_table/action_types';
|
||||||
import { MutableRefObject } from 'react';
|
import { MutableRefObject } from 'react';
|
||||||
import { DataView } from '@kbn/data-views-plugin/public';
|
import { DataView } from '@kbn/data-views-plugin/public';
|
||||||
import { VISUALIZE_GEO_FIELD_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
import { VISUALIZE_GEO_FIELD_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
||||||
|
import { mlTimefilterRefresh$, Refresh } from '@kbn/ml-date-picker';
|
||||||
import { getCompatibleLensDataType, getLensAttributes } from './lens_utils';
|
import { getCompatibleLensDataType, getLensAttributes } from './lens_utils';
|
||||||
import { CombinedQuery } from '../../../../index_data_visualizer/types/combined_query';
|
import { CombinedQuery } from '../../../../index_data_visualizer/types/combined_query';
|
||||||
import { FieldVisConfig } from '../../stats_table/types';
|
import { FieldVisConfig } from '../../stats_table/types';
|
||||||
import { DataVisualizerKibanaReactContextValue } from '../../../../kibana_context';
|
import { DataVisualizerKibanaReactContextValue } from '../../../../kibana_context';
|
||||||
import {
|
|
||||||
dataVisualizerRefresh$,
|
|
||||||
Refresh,
|
|
||||||
} from '../../../../index_data_visualizer/services/timefilter_refresh_service';
|
|
||||||
import { SUPPORTED_FIELD_TYPES } from '../../../../../../common/constants';
|
import { SUPPORTED_FIELD_TYPES } from '../../../../../../common/constants';
|
||||||
import { APP_ID } from '../../../../../../common/constants';
|
import { APP_ID } from '../../../../../../common/constants';
|
||||||
|
|
||||||
|
@ -36,7 +33,7 @@ export function getActions(
|
||||||
const refresh: Refresh = {
|
const refresh: Refresh = {
|
||||||
lastRefresh: Date.now(),
|
lastRefresh: Date.now(),
|
||||||
};
|
};
|
||||||
dataVisualizerRefresh$.next(refresh);
|
mlTimefilterRefresh$.next(refresh);
|
||||||
};
|
};
|
||||||
// Navigate to Lens with prefilled chart for data field
|
// Navigate to Lens with prefilled chart for data field
|
||||||
if (services.application?.capabilities?.visualize?.show === true && lensPlugin !== undefined) {
|
if (services.application?.capabilities?.visualize?.show === true && lensPlugin !== undefined) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import React, { FC, useState, useEffect } from 'react';
|
||||||
|
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { EuiSpacer, EuiTitle } from '@elastic/eui';
|
import { useEuiBreakpoint, EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||||
import { DataView } from '@kbn/data-views-plugin/public';
|
import { DataView } from '@kbn/data-views-plugin/public';
|
||||||
import { useUrlState } from '@kbn/ml-url-state';
|
import { useUrlState } from '@kbn/ml-url-state';
|
||||||
import { isDefined } from '@kbn/ml-is-defined';
|
import { isDefined } from '@kbn/ml-is-defined';
|
||||||
|
@ -26,7 +26,6 @@ interface Props {
|
||||||
searchString?: string | { [key: string]: any };
|
searchString?: string | { [key: string]: any };
|
||||||
searchQueryLanguage?: string;
|
searchQueryLanguage?: string;
|
||||||
getAdditionalLinks?: GetAdditionalLinks;
|
getAdditionalLinks?: GetAdditionalLinks;
|
||||||
compact?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ACTIONS_PANEL_WIDTH = '240px';
|
const ACTIONS_PANEL_WIDTH = '240px';
|
||||||
|
@ -36,7 +35,6 @@ export const ActionsPanel: FC<Props> = ({
|
||||||
searchString,
|
searchString,
|
||||||
searchQueryLanguage,
|
searchQueryLanguage,
|
||||||
getAdditionalLinks,
|
getAdditionalLinks,
|
||||||
compact,
|
|
||||||
}) => {
|
}) => {
|
||||||
const [globalState] = useUrlState('_g');
|
const [globalState] = useUrlState('_g');
|
||||||
|
|
||||||
|
@ -121,19 +119,17 @@ export const ActionsPanel: FC<Props> = ({
|
||||||
const showActionsPanel =
|
const showActionsPanel =
|
||||||
discoverLink || (Array.isArray(asyncHrefCards) && asyncHrefCards.length > 0);
|
discoverLink || (Array.isArray(asyncHrefCards) && asyncHrefCards.length > 0);
|
||||||
|
|
||||||
|
const dvActionsPanel = css({
|
||||||
|
[useEuiBreakpoint(['xs', 's', 'm', 'l', 'xl'])]: {
|
||||||
|
width: ACTIONS_PANEL_WIDTH,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Note we use display:none for the DataRecognizer section as it needs to be
|
// Note we use display:none for the DataRecognizer section as it needs to be
|
||||||
// passed the recognizerResults object, and then run the recognizer check which
|
// passed the recognizerResults object, and then run the recognizer check which
|
||||||
// controls whether the recognizer section is ultimately displayed.
|
// controls whether the recognizer section is ultimately displayed.
|
||||||
return showActionsPanel ? (
|
return showActionsPanel ? (
|
||||||
<div
|
<div data-test-subj="dataVisualizerActionsPanel" css={dvActionsPanel}>
|
||||||
data-test-subj="dataVisualizerActionsPanel"
|
|
||||||
css={
|
|
||||||
!compact &&
|
|
||||||
css`
|
|
||||||
width: ${ACTIONS_PANEL_WIDTH};
|
|
||||||
`
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<EuiTitle size="s">
|
<EuiTitle size="s">
|
||||||
<h2>
|
<h2>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
|
|
@ -9,8 +9,8 @@ import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { EuiButtonIcon, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui';
|
import { EuiButtonIcon, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { DataView } from '@kbn/data-views-plugin/public';
|
import { DataView } from '@kbn/data-views-plugin/public';
|
||||||
|
import { mlTimefilterRefresh$, Refresh } from '@kbn/ml-date-picker';
|
||||||
import { useDataVisualizerKibana } from '../../../kibana_context';
|
import { useDataVisualizerKibana } from '../../../kibana_context';
|
||||||
import { dataVisualizerRefresh$, Refresh } from '../../services/timefilter_refresh_service';
|
|
||||||
|
|
||||||
export interface DataVisualizerDataViewManagementProps {
|
export interface DataVisualizerDataViewManagementProps {
|
||||||
/**
|
/**
|
||||||
|
@ -56,7 +56,7 @@ export function DataVisualizerDataViewManagement(props: DataVisualizerDataViewMa
|
||||||
const refresh: Refresh = {
|
const refresh: Refresh = {
|
||||||
lastRefresh: Date.now(),
|
lastRefresh: Date.now(),
|
||||||
};
|
};
|
||||||
dataVisualizerRefresh$.next(refresh);
|
mlTimefilterRefresh$.next(refresh);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,206 +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
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { FC, useCallback, useMemo, useState } from 'react';
|
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
|
||||||
import { TimefilterContract } from '@kbn/data-plugin/public';
|
|
||||||
import type { DataView } from '@kbn/data-plugin/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 { useStorage } from '@kbn/ml-local-storage';
|
|
||||||
import { setFullTimeRange } from './full_time_range_selector_service';
|
|
||||||
import { useDataVisualizerKibana } from '../../../kibana_context';
|
|
||||||
import {
|
|
||||||
DV_FROZEN_TIER_PREFERENCE,
|
|
||||||
FROZEN_TIER_PREFERENCE,
|
|
||||||
type DVKey,
|
|
||||||
type DVStorageMapped,
|
|
||||||
type FrozenTierPreference,
|
|
||||||
} from '../../types/storage';
|
|
||||||
|
|
||||||
export const ML_FROZEN_TIER_PREFERENCE = 'ml.frozenDataTierPreference';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
timefilter: TimefilterContract;
|
|
||||||
dataView: DataView;
|
|
||||||
disabled: boolean;
|
|
||||||
query?: QueryDslQueryContainer;
|
|
||||||
callback?: (a: any) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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> = ({
|
|
||||||
timefilter,
|
|
||||||
dataView,
|
|
||||||
query,
|
|
||||||
disabled,
|
|
||||||
callback,
|
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
services: {
|
|
||||||
notifications: { toasts },
|
|
||||||
},
|
|
||||||
} = useDataVisualizerKibana();
|
|
||||||
|
|
||||||
// wrapper around setFullTimeRange to allow for the calling of the optional callBack prop
|
|
||||||
const setRange = useCallback(
|
|
||||||
async (i: DataView, q?: QueryDslQueryContainer, excludeFrozenData?: boolean) => {
|
|
||||||
try {
|
|
||||||
const fullTimeRange = await setFullTimeRange(timefilter, i, q, excludeFrozenData, toasts);
|
|
||||||
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.',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[callback, timefilter, toasts]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [isPopoverOpen, setPopover] = useState(false);
|
|
||||||
|
|
||||||
const [frozenDataPreference, setFrozenDataPreference] = useStorage<
|
|
||||||
DVKey,
|
|
||||||
DVStorageMapped<typeof DV_FROZEN_TIER_PREFERENCE>
|
|
||||||
>(
|
|
||||||
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(dataView, query, id === FROZEN_TIER_PREFERENCE.EXCLUDE);
|
|
||||||
closePopover();
|
|
||||||
},
|
|
||||||
[dataView, 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: '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 (
|
|
||||||
<EuiFlexGroup responsive={false} gutterSize="xs" alignItems="center">
|
|
||||||
<EuiToolTip content={buttonTooltip}>
|
|
||||||
<EuiButton
|
|
||||||
isDisabled={disabled}
|
|
||||||
onClick={() => setRange(dataView, 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>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,65 +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
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import moment from 'moment';
|
|
||||||
import { TimefilterContract } from '@kbn/data-plugin/public';
|
|
||||||
import dateMath from '@kbn/datemath';
|
|
||||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import type { ToastsStart } from '@kbn/core/public';
|
|
||||||
import { DataView } from '@kbn/data-views-plugin/public';
|
|
||||||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
|
||||||
import { getTimeFieldRange } from '../../services/time_field_range';
|
|
||||||
import type { GetTimeFieldRangeResponse } from '../../../../../common/types/time_field_request';
|
|
||||||
import { addExcludeFrozenToQuery } from '../../utils/query_utils';
|
|
||||||
|
|
||||||
export interface TimeRange {
|
|
||||||
from: number;
|
|
||||||
to: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function setFullTimeRange(
|
|
||||||
timefilter: TimefilterContract,
|
|
||||||
dataView: DataView,
|
|
||||||
query?: QueryDslQueryContainer,
|
|
||||||
excludeFrozenData?: boolean,
|
|
||||||
toasts?: ToastsStart
|
|
||||||
): Promise<GetTimeFieldRangeResponse> {
|
|
||||||
const runtimeMappings = dataView.getRuntimeMappings();
|
|
||||||
const resp = await getTimeFieldRange({
|
|
||||||
index: dataView.getIndexPattern(),
|
|
||||||
timeFieldName: dataView.timeFieldName,
|
|
||||||
query: excludeFrozenData ? addExcludeFrozenToQuery(query) : query,
|
|
||||||
...(isPopulatedObject(runtimeMappings) ? { runtimeMappings } : {}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (resp.start.epoch && resp.end.epoch) {
|
|
||||||
timefilter.setTime({
|
|
||||||
from: moment(resp.start.epoch).toISOString(),
|
|
||||||
to: moment(resp.end.epoch).toISOString(),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
toasts?.addWarning({
|
|
||||||
title: i18n.translate('xpack.dataVisualizer.index.fullTimeRangeSelector.noResults', {
|
|
||||||
defaultMessage: 'No results match your search criteria',
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTimeFilterRange(timefilter: TimefilterContract): TimeRange {
|
|
||||||
const fromMoment = dateMath.parse(timefilter.getTime().from);
|
|
||||||
const toMoment = dateMath.parse(timefilter.getTime().to);
|
|
||||||
const from = fromMoment !== undefined ? fromMoment.valueOf() : 0;
|
|
||||||
const to = toMoment !== undefined ? toMoment.valueOf() : 0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
to,
|
|
||||||
from,
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,10 +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
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { FullTimeRangeSelector } from './full_time_range_selector';
|
|
||||||
export type { TimeRange } from './full_time_range_selector_service';
|
|
||||||
export { getTimeFilterRange } from './full_time_range_selector_service';
|
|
|
@ -5,10 +5,13 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { css } from '@emotion/react';
|
||||||
import React, { FC, useEffect, useMemo, useState, useCallback, useRef } from 'react';
|
import React, { FC, useEffect, useMemo, useState, useCallback, useRef } from 'react';
|
||||||
import type { Required } from 'utility-types';
|
import type { Required } from 'utility-types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
useEuiBreakpoint,
|
||||||
|
useIsWithinMaxBreakpoint,
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
EuiFlexItem,
|
EuiFlexItem,
|
||||||
EuiPageBody,
|
EuiPageBody,
|
||||||
|
@ -26,15 +29,20 @@ import { Filter, FilterStateStore, Query } from '@kbn/es-query';
|
||||||
import { generateFilters } from '@kbn/data-plugin/public';
|
import { generateFilters } from '@kbn/data-plugin/public';
|
||||||
import { DataView, DataViewField } from '@kbn/data-views-plugin/public';
|
import { DataView, DataViewField } from '@kbn/data-views-plugin/public';
|
||||||
import { usePageUrlState, useUrlState } from '@kbn/ml-url-state';
|
import { usePageUrlState, useUrlState } from '@kbn/ml-url-state';
|
||||||
|
import {
|
||||||
|
DatePickerWrapper,
|
||||||
|
FullTimeRangeSelector,
|
||||||
|
FROZEN_TIER_PREFERENCE,
|
||||||
|
} from '@kbn/ml-date-picker';
|
||||||
import { useStorage } from '@kbn/ml-local-storage';
|
import { useStorage } from '@kbn/ml-local-storage';
|
||||||
|
|
||||||
import { useCurrentEuiTheme } from '../../../common/hooks/use_current_eui_theme';
|
import { useCurrentEuiTheme } from '../../../common/hooks/use_current_eui_theme';
|
||||||
import {
|
import {
|
||||||
|
DV_FROZEN_TIER_PREFERENCE,
|
||||||
DV_RANDOM_SAMPLER_PREFERENCE,
|
DV_RANDOM_SAMPLER_PREFERENCE,
|
||||||
type DVKey,
|
type DVKey,
|
||||||
type DVStorageMapped,
|
type DVStorageMapped,
|
||||||
} from '../../types/storage';
|
} from '../../types/storage';
|
||||||
import { FullTimeRangeSelector } from '../full_time_range_selector';
|
|
||||||
import {
|
import {
|
||||||
DataVisualizerTable,
|
DataVisualizerTable,
|
||||||
ItemIdToExpandedRowMap,
|
ItemIdToExpandedRowMap,
|
||||||
|
@ -57,7 +65,6 @@ import { OMIT_FIELDS } from '../../../../../common/constants';
|
||||||
import { kbnTypeToJobType } from '../../../common/util/field_types_utils';
|
import { kbnTypeToJobType } from '../../../common/util/field_types_utils';
|
||||||
import { SearchPanel } from '../search_panel';
|
import { SearchPanel } from '../search_panel';
|
||||||
import { ActionsPanel } from '../actions_panel';
|
import { ActionsPanel } from '../actions_panel';
|
||||||
import { DatePickerWrapper } from '../../../common/components/date_picker_wrapper';
|
|
||||||
import { createMergedEsQuery } from '../../utils/saved_search_utils';
|
import { createMergedEsQuery } from '../../utils/saved_search_utils';
|
||||||
import { DataVisualizerDataViewManagement } from '../data_view_management';
|
import { DataVisualizerDataViewManagement } from '../data_view_management';
|
||||||
import { GetAdditionalLinks } from '../../../common/components/results_links';
|
import { GetAdditionalLinks } from '../../../common/components/results_links';
|
||||||
|
@ -125,7 +132,6 @@ export interface IndexDataVisualizerViewProps {
|
||||||
currentSavedSearch: SavedSearchSavedObject | null;
|
currentSavedSearch: SavedSearchSavedObject | null;
|
||||||
currentSessionId?: string;
|
currentSessionId?: string;
|
||||||
getAdditionalLinks?: GetAdditionalLinks;
|
getAdditionalLinks?: GetAdditionalLinks;
|
||||||
compact?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVisualizerProps) => {
|
export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVisualizerProps) => {
|
||||||
|
@ -136,6 +142,15 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
||||||
DVStorageMapped<typeof DV_RANDOM_SAMPLER_PREFERENCE>
|
DVStorageMapped<typeof DV_RANDOM_SAMPLER_PREFERENCE>
|
||||||
>(DV_RANDOM_SAMPLER_PREFERENCE, RANDOM_SAMPLER_OPTION.ON_AUTOMATIC);
|
>(DV_RANDOM_SAMPLER_PREFERENCE, RANDOM_SAMPLER_OPTION.ON_AUTOMATIC);
|
||||||
|
|
||||||
|
const [frozenDataPreference, setFrozenDataPreference] = useStorage<
|
||||||
|
DVKey,
|
||||||
|
DVStorageMapped<typeof DV_FROZEN_TIER_PREFERENCE>
|
||||||
|
>(
|
||||||
|
DV_FROZEN_TIER_PREFERENCE,
|
||||||
|
// By default we will exclude frozen data tier
|
||||||
|
FROZEN_TIER_PREFERENCE.EXCLUDE
|
||||||
|
);
|
||||||
|
|
||||||
const restorableDefaults = useMemo(
|
const restorableDefaults = useMemo(
|
||||||
() =>
|
() =>
|
||||||
getDefaultDataVisualizerListState({
|
getDefaultDataVisualizerListState({
|
||||||
|
@ -161,7 +176,7 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
||||||
dataVisualizerProps.currentSavedSearch
|
dataVisualizerProps.currentSavedSearch
|
||||||
);
|
);
|
||||||
|
|
||||||
const { currentDataView, currentSessionId, getAdditionalLinks, compact } = dataVisualizerProps;
|
const { currentDataView, currentSessionId, getAdditionalLinks } = dataVisualizerProps;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dataVisualizerProps?.currentSavedSearch !== undefined) {
|
if (dataVisualizerProps?.currentSavedSearch !== undefined) {
|
||||||
|
@ -458,14 +473,21 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
||||||
() => currentDataView.timeFieldName !== undefined && currentDataView.timeFieldName !== '',
|
() => currentDataView.timeFieldName !== undefined && currentDataView.timeFieldName !== '',
|
||||||
[currentDataView.timeFieldName]
|
[currentDataView.timeFieldName]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const dvPageHeader = css({
|
||||||
|
[useEuiBreakpoint(['xs', 's', 'm', 'l', 'xl'])]: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isWithinXl = useIsWithinMaxBreakpoint('xl');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiPageBody data-test-subj="dataVisualizerIndexPage" paddingSize="none" panelled={false}>
|
<EuiPageBody data-test-subj="dataVisualizerIndexPage" paddingSize="none" panelled={false}>
|
||||||
<EuiFlexGroup gutterSize="m">
|
<EuiFlexGroup gutterSize="m">
|
||||||
<EuiFlexItem>
|
<EuiFlexItem>
|
||||||
<EuiPageContentHeader
|
<EuiPageContentHeader data-test-subj="dataVisualizerPageHeader" css={dvPageHeader}>
|
||||||
data-test-subj="dataVisualizerPageHeader"
|
|
||||||
css={compact ? { flexDirection: 'column', alignItems: 'flex-start' } : null}
|
|
||||||
>
|
|
||||||
<EuiPageContentHeaderSection>
|
<EuiPageContentHeaderSection>
|
||||||
<EuiFlexGroup
|
<EuiFlexGroup
|
||||||
data-test-subj="dataViewTitleHeader"
|
data-test-subj="dataViewTitleHeader"
|
||||||
|
@ -483,7 +505,7 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</EuiPageContentHeaderSection>
|
</EuiPageContentHeaderSection>
|
||||||
|
|
||||||
{compact ? <EuiSpacer size="m" /> : null}
|
{isWithinXl ? <EuiSpacer size="m" /> : null}
|
||||||
<EuiFlexGroup
|
<EuiFlexGroup
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="flexEnd"
|
justifyContent="flexEnd"
|
||||||
|
@ -493,6 +515,8 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
||||||
{hasValidTimeField ? (
|
{hasValidTimeField ? (
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<FullTimeRangeSelector
|
<FullTimeRangeSelector
|
||||||
|
frozenDataPreference={frozenDataPreference}
|
||||||
|
setFrozenDataPreference={setFrozenDataPreference}
|
||||||
dataView={currentDataView}
|
dataView={currentDataView}
|
||||||
query={undefined}
|
query={undefined}
|
||||||
disabled={false}
|
disabled={false}
|
||||||
|
@ -504,7 +528,6 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
||||||
<DatePickerWrapper
|
<DatePickerWrapper
|
||||||
isAutoRefreshOnly={!hasValidTimeField}
|
isAutoRefreshOnly={!hasValidTimeField}
|
||||||
showRefresh={!hasValidTimeField}
|
showRefresh={!hasValidTimeField}
|
||||||
compact={compact}
|
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
|
@ -513,7 +536,7 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
<EuiSpacer size="m" />
|
<EuiSpacer size="m" />
|
||||||
<EuiPageContentBody>
|
<EuiPageContentBody>
|
||||||
<EuiFlexGroup gutterSize="m" direction={compact ? 'column' : 'row'}>
|
<EuiFlexGroup gutterSize="m" direction={isWithinXl ? 'column' : 'row'}>
|
||||||
<EuiFlexItem>
|
<EuiFlexItem>
|
||||||
<EuiPanel hasShadow={false} hasBorder>
|
<EuiPanel hasShadow={false} hasBorder>
|
||||||
<SearchPanel
|
<SearchPanel
|
||||||
|
@ -530,7 +553,6 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
||||||
setVisibleFieldNames={setVisibleFieldNames}
|
setVisibleFieldNames={setVisibleFieldNames}
|
||||||
showEmptyFields={showEmptyFields}
|
showEmptyFields={showEmptyFields}
|
||||||
onAddFilter={onAddFilter}
|
onAddFilter={onAddFilter}
|
||||||
compact={compact}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{overallStats?.totalCount !== undefined && (
|
{overallStats?.totalCount !== undefined && (
|
||||||
|
@ -576,14 +598,13 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
||||||
/>
|
/>
|
||||||
</EuiPanel>
|
</EuiPanel>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
{compact ? <EuiSpacer size="m" /> : null}
|
{isWithinXl ? <EuiSpacer size="m" /> : null}
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<ActionsPanel
|
<ActionsPanel
|
||||||
dataView={currentDataView}
|
dataView={currentDataView}
|
||||||
searchQueryLanguage={searchQueryLanguage}
|
searchQueryLanguage={searchQueryLanguage}
|
||||||
searchString={searchString}
|
searchString={searchString}
|
||||||
getAdditionalLinks={getAdditionalLinks}
|
getAdditionalLinks={getAdditionalLinks}
|
||||||
compact={compact}
|
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
|
|
|
@ -7,7 +7,13 @@
|
||||||
|
|
||||||
import React, { FC, useEffect, useState } from 'react';
|
import React, { FC, useEffect, useState } from 'react';
|
||||||
import { css } from '@emotion/react';
|
import { css } from '@emotion/react';
|
||||||
import { useEuiBreakpoint, EuiFlexItem, EuiFlexGroup, EuiSpacer } from '@elastic/eui';
|
import {
|
||||||
|
useEuiBreakpoint,
|
||||||
|
useIsWithinMaxBreakpoint,
|
||||||
|
EuiFlexItem,
|
||||||
|
EuiFlexGroup,
|
||||||
|
EuiSpacer,
|
||||||
|
} from '@elastic/eui';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { Query, Filter } from '@kbn/es-query';
|
import { Query, Filter } from '@kbn/es-query';
|
||||||
import type { TimeRange } from '@kbn/es-query';
|
import type { TimeRange } from '@kbn/es-query';
|
||||||
|
@ -45,7 +51,6 @@ interface Props {
|
||||||
}): void;
|
}): void;
|
||||||
showEmptyFields: boolean;
|
showEmptyFields: boolean;
|
||||||
onAddFilter?: (field: DataViewField | string, value: string, type: '+' | '-') => void;
|
onAddFilter?: (field: DataViewField | string, value: string, type: '+' | '-') => void;
|
||||||
compact?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SearchPanel: FC<Props> = ({
|
export const SearchPanel: FC<Props> = ({
|
||||||
|
@ -60,7 +65,6 @@ export const SearchPanel: FC<Props> = ({
|
||||||
visibleFieldNames,
|
visibleFieldNames,
|
||||||
setSearchParams,
|
setSearchParams,
|
||||||
showEmptyFields,
|
showEmptyFields,
|
||||||
compact,
|
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
services: {
|
services: {
|
||||||
|
@ -139,6 +143,8 @@ export const SearchPanel: FC<Props> = ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isWithinXl = useIsWithinMaxBreakpoint('xl');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiFlexGroup
|
<EuiFlexGroup
|
||||||
gutterSize="none"
|
gutterSize="none"
|
||||||
|
@ -168,7 +174,7 @@ export const SearchPanel: FC<Props> = ({
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
|
|
||||||
{compact ? <EuiSpacer size="s" /> : null}
|
{isWithinXl ? <EuiSpacer size="s" /> : null}
|
||||||
<EuiFlexItem grow={2} css={dvSearchPanelControls}>
|
<EuiFlexItem grow={2} css={dvSearchPanelControls}>
|
||||||
<DataVisualizerFieldNamesFilter
|
<DataVisualizerFieldNamesFilter
|
||||||
overallStats={overallStats}
|
overallStats={overallStats}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { pick } from 'lodash';
|
||||||
import { Observable, Subject } from 'rxjs';
|
import { Observable, Subject } from 'rxjs';
|
||||||
import { CoreStart } from '@kbn/core/public';
|
import { CoreStart } from '@kbn/core/public';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
@ -20,9 +21,12 @@ import {
|
||||||
EmbeddableOutput,
|
EmbeddableOutput,
|
||||||
IContainer,
|
IContainer,
|
||||||
} from '@kbn/embeddable-plugin/public';
|
} from '@kbn/embeddable-plugin/public';
|
||||||
|
import { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||||
|
import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public';
|
||||||
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
|
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
|
||||||
import type { Query } from '@kbn/es-query';
|
import type { Query } from '@kbn/es-query';
|
||||||
import { DataView, DataViewField } from '@kbn/data-views-plugin/public';
|
import { DataView, DataViewField } from '@kbn/data-views-plugin/public';
|
||||||
|
import { DatePickerContextProvider } from '@kbn/ml-date-picker';
|
||||||
import { SavedSearch } from '@kbn/discover-plugin/public';
|
import { SavedSearch } from '@kbn/discover-plugin/public';
|
||||||
import { SamplingOption } from '../../../../../common/types/field_stats';
|
import { SamplingOption } from '../../../../../common/types/field_stats';
|
||||||
import { DATA_VISUALIZER_GRID_EMBEDDABLE_TYPE } from './constants';
|
import { DATA_VISUALIZER_GRID_EMBEDDABLE_TYPE } from './constants';
|
||||||
|
@ -215,18 +219,28 @@ export class DataVisualizerGridEmbeddable extends Embeddable<
|
||||||
|
|
||||||
const I18nContext = this.services[0].i18n.Context;
|
const I18nContext = this.services[0].i18n.Context;
|
||||||
|
|
||||||
|
const services = { ...this.services[0], ...this.services[1] };
|
||||||
|
const datePickerDeps = {
|
||||||
|
...pick(services, ['data', 'http', 'notifications', 'theme', 'uiSettings']),
|
||||||
|
toMountPoint,
|
||||||
|
wrapWithTheme,
|
||||||
|
uiSettingsKeys: UI_SETTINGS,
|
||||||
|
};
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<I18nContext>
|
<I18nContext>
|
||||||
<KibanaThemeProvider theme$={this.services[0].theme.theme$}>
|
<KibanaThemeProvider theme$={this.services[0].theme.theme$}>
|
||||||
<KibanaContextProvider services={{ ...this.services[0], ...this.services[1] }}>
|
<KibanaContextProvider services={services}>
|
||||||
<Suspense fallback={<EmbeddableLoading />}>
|
<DatePickerContextProvider {...datePickerDeps}>
|
||||||
<IndexDataVisualizerViewWrapper
|
<Suspense fallback={<EmbeddableLoading />}>
|
||||||
id={this.input.id}
|
<IndexDataVisualizerViewWrapper
|
||||||
embeddableContext={this}
|
id={this.input.id}
|
||||||
embeddableInput={this.getInput$()}
|
embeddableContext={this}
|
||||||
onOutputChange={(output) => this.updateOutput(output)}
|
embeddableInput={this.getInput$()}
|
||||||
/>
|
onOutputChange={(output) => this.updateOutput(output)}
|
||||||
</Suspense>
|
/>
|
||||||
|
</Suspense>
|
||||||
|
</DatePickerContextProvider>
|
||||||
</KibanaContextProvider>
|
</KibanaContextProvider>
|
||||||
</KibanaThemeProvider>
|
</KibanaThemeProvider>
|
||||||
</I18nContext>,
|
</I18nContext>,
|
||||||
|
|
|
@ -15,13 +15,12 @@ import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types';
|
||||||
import seedrandom from 'seedrandom';
|
import seedrandom from 'seedrandom';
|
||||||
import type { SamplingOption } from '@kbn/discover-plugin/public/application/main/components/field_stats_table/field_stats_table';
|
import type { SamplingOption } from '@kbn/discover-plugin/public/application/main/components/field_stats_table/field_stats_table';
|
||||||
import type { Dictionary } from '@kbn/ml-url-state';
|
import type { Dictionary } from '@kbn/ml-url-state';
|
||||||
|
import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker';
|
||||||
import type { RandomSamplerOption } from '../constants/random_sampler';
|
import type { RandomSamplerOption } from '../constants/random_sampler';
|
||||||
import type { DataVisualizerIndexBasedAppState } from '../types/index_data_visualizer_state';
|
import type { DataVisualizerIndexBasedAppState } from '../types/index_data_visualizer_state';
|
||||||
import { useDataVisualizerKibana } from '../../kibana_context';
|
import { useDataVisualizerKibana } from '../../kibana_context';
|
||||||
import { getEsQueryFromSavedSearch } from '../utils/saved_search_utils';
|
import { getEsQueryFromSavedSearch } from '../utils/saved_search_utils';
|
||||||
import type { MetricFieldsStats } from '../../common/components/stats_table/components/field_count_stats';
|
import type { MetricFieldsStats } from '../../common/components/stats_table/components/field_count_stats';
|
||||||
import { useTimefilter } from './use_time_filter';
|
|
||||||
import { dataVisualizerRefresh$ } from '../services/timefilter_refresh_service';
|
|
||||||
import { TimeBuckets } from '../../../../common/services/time_buckets';
|
import { TimeBuckets } from '../../../../common/services/time_buckets';
|
||||||
import type { FieldVisConfig } from '../../common/components/stats_table/types';
|
import type { FieldVisConfig } from '../../common/components/stats_table/types';
|
||||||
import {
|
import {
|
||||||
|
@ -313,7 +312,7 @@ export const useDataVisualizerGridData = (
|
||||||
const timeUpdateSubscription = merge(
|
const timeUpdateSubscription = merge(
|
||||||
timefilter.getTimeUpdate$(),
|
timefilter.getTimeUpdate$(),
|
||||||
timefilter.getAutoRefreshFetch$(),
|
timefilter.getAutoRefreshFetch$(),
|
||||||
dataVisualizerRefresh$
|
mlTimefilterRefresh$
|
||||||
).subscribe(() => {
|
).subscribe(() => {
|
||||||
if (onUpdate) {
|
if (onUpdate) {
|
||||||
onUpdate({
|
onUpdate({
|
||||||
|
|
|
@ -1,59 +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
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import useObservable from 'react-use/lib/useObservable';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
import { useDataVisualizerKibana } from '../../kibana_context';
|
|
||||||
|
|
||||||
interface UseTimefilterOptions {
|
|
||||||
timeRangeSelector?: boolean;
|
|
||||||
autoRefreshSelector?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useTimefilter = ({
|
|
||||||
timeRangeSelector,
|
|
||||||
autoRefreshSelector,
|
|
||||||
}: UseTimefilterOptions = {}) => {
|
|
||||||
const { services } = useDataVisualizerKibana();
|
|
||||||
const { timefilter } = services.data.query.timefilter;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (timeRangeSelector === true) {
|
|
||||||
timefilter.enableTimeRangeSelector();
|
|
||||||
} else if (timeRangeSelector === false) {
|
|
||||||
timefilter.disableTimeRangeSelector();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (autoRefreshSelector === true) {
|
|
||||||
timefilter.enableAutoRefreshSelector();
|
|
||||||
} else if (autoRefreshSelector === false) {
|
|
||||||
timefilter.disableAutoRefreshSelector();
|
|
||||||
}
|
|
||||||
}, [timeRangeSelector, autoRefreshSelector, timefilter]);
|
|
||||||
|
|
||||||
return timefilter;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useRefreshIntervalUpdates = () => {
|
|
||||||
const timefilter = useTimefilter();
|
|
||||||
|
|
||||||
return useObservable(
|
|
||||||
timefilter.getRefreshIntervalUpdate$().pipe(map(timefilter.getRefreshInterval)),
|
|
||||||
timefilter.getRefreshInterval()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useTimeRangeUpdates = (absolute = false) => {
|
|
||||||
const timefilter = useTimefilter();
|
|
||||||
|
|
||||||
const getTimeCallback = absolute
|
|
||||||
? timefilter.getAbsoluteTime.bind(timefilter)
|
|
||||||
: timefilter.getTime.bind(timefilter);
|
|
||||||
|
|
||||||
return useObservable(timefilter.getTimeUpdate$().pipe(map(getTimeCallback)), getTimeCallback());
|
|
||||||
};
|
|
|
@ -5,19 +5,26 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
import '../_index.scss';
|
import '../_index.scss';
|
||||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
import { pick } from 'lodash';
|
||||||
|
import React, { FC, useCallback, useEffect, useState } from 'react';
|
||||||
import { useHistory, useLocation } from 'react-router-dom';
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
import { parse, stringify } from 'query-string';
|
import { parse, stringify } from 'query-string';
|
||||||
import { isEqual, throttle } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { EuiResizeObserver } from '@elastic/eui';
|
|
||||||
import { encode } from '@kbn/rison';
|
import { encode } from '@kbn/rison';
|
||||||
import { SimpleSavedObject } from '@kbn/core/public';
|
import { SimpleSavedObject } from '@kbn/core/public';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||||
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
|
import {
|
||||||
|
KibanaContextProvider,
|
||||||
|
KibanaThemeProvider,
|
||||||
|
toMountPoint,
|
||||||
|
wrapWithTheme,
|
||||||
|
} from '@kbn/kibana-react-plugin/public';
|
||||||
import { StorageContextProvider } from '@kbn/ml-local-storage';
|
import { StorageContextProvider } from '@kbn/ml-local-storage';
|
||||||
import { DataView } from '@kbn/data-views-plugin/public';
|
import { DataView } from '@kbn/data-views-plugin/public';
|
||||||
import { getNestedProperty } from '@kbn/ml-nested-property';
|
import { getNestedProperty } from '@kbn/ml-nested-property';
|
||||||
|
import { DatePickerContextProvider } from '@kbn/ml-date-picker';
|
||||||
|
import { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||||
import {
|
import {
|
||||||
Provider as UrlStateContextProvider,
|
Provider as UrlStateContextProvider,
|
||||||
parseUrlState,
|
parseUrlState,
|
||||||
|
@ -38,6 +45,8 @@ import { DATA_VISUALIZER_INDEX_VIEWER } from './constants/index_data_visualizer_
|
||||||
import { INDEX_DATA_VISUALIZER_NAME } from '../common/constants';
|
import { INDEX_DATA_VISUALIZER_NAME } from '../common/constants';
|
||||||
import { DV_STORAGE_KEYS } from './types/storage';
|
import { DV_STORAGE_KEYS } from './types/storage';
|
||||||
|
|
||||||
|
const XXL_BREAKPOINT = 1400;
|
||||||
|
|
||||||
const localStorage = new Storage(window.localStorage);
|
const localStorage = new Storage(window.localStorage);
|
||||||
|
|
||||||
export interface DataVisualizerStateContextProviderProps {
|
export interface DataVisualizerStateContextProviderProps {
|
||||||
|
@ -247,36 +256,15 @@ export const DataVisualizerStateContextProvider: FC<DataVisualizerStateContextPr
|
||||||
[history, urlSearchString]
|
[history, urlSearchString]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [panelWidth, setPanelWidth] = useState(1600);
|
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
const resizeHandler = useCallback(
|
|
||||||
throttle((e: { width: number; height: number }) => {
|
|
||||||
// When window or table is resized,
|
|
||||||
// update the page body width
|
|
||||||
setPanelWidth(e.width);
|
|
||||||
}, 500),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const compact = useMemo(() => panelWidth <= 1024, [panelWidth]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UrlStateContextProvider value={{ searchString: urlSearchString, setUrlState }}>
|
<UrlStateContextProvider value={{ searchString: urlSearchString, setUrlState }}>
|
||||||
{currentDataView ? (
|
{currentDataView ? (
|
||||||
// Needs ResizeObserver to measure window width - side bar navigation
|
<IndexDataVisualizerComponent
|
||||||
<EuiResizeObserver onResize={resizeHandler}>
|
currentDataView={currentDataView}
|
||||||
{(resizeRef) => (
|
currentSavedSearch={currentSavedSearch}
|
||||||
<div ref={resizeRef}>
|
currentSessionId={currentSessionId}
|
||||||
<IndexDataVisualizerComponent
|
getAdditionalLinks={getAdditionalLinks}
|
||||||
currentDataView={currentDataView}
|
/>
|
||||||
currentSavedSearch={currentSavedSearch}
|
|
||||||
currentSessionId={currentSessionId}
|
|
||||||
getAdditionalLinks={getAdditionalLinks}
|
|
||||||
compact={compact}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</EuiResizeObserver>
|
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div />
|
||||||
)}
|
)}
|
||||||
|
@ -317,15 +305,30 @@ export const IndexDataVisualizer: FC<{
|
||||||
unifiedSearch,
|
unifiedSearch,
|
||||||
...coreStart,
|
...coreStart,
|
||||||
};
|
};
|
||||||
|
const datePickerDeps = {
|
||||||
|
...pick(services, ['data', 'http', 'notifications', 'theme', 'uiSettings']),
|
||||||
|
toMountPoint,
|
||||||
|
wrapWithTheme,
|
||||||
|
uiSettingsKeys: UI_SETTINGS,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KibanaThemeProvider theme$={coreStart.theme.theme$}>
|
<KibanaThemeProvider
|
||||||
|
theme$={coreStart.theme.theme$}
|
||||||
|
modify={{
|
||||||
|
breakpoint: {
|
||||||
|
xxl: XXL_BREAKPOINT,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
<KibanaContextProvider services={{ ...services }}>
|
<KibanaContextProvider services={{ ...services }}>
|
||||||
<StorageContextProvider storage={localStorage} storageKeys={DV_STORAGE_KEYS}>
|
<StorageContextProvider storage={localStorage} storageKeys={DV_STORAGE_KEYS}>
|
||||||
<DataVisualizerStateContextProvider
|
<DatePickerContextProvider {...datePickerDeps}>
|
||||||
IndexDataVisualizerComponent={IndexDataVisualizerView}
|
<DataVisualizerStateContextProvider
|
||||||
getAdditionalLinks={getAdditionalLinks}
|
IndexDataVisualizerComponent={IndexDataVisualizerView}
|
||||||
/>
|
getAdditionalLinks={getAdditionalLinks}
|
||||||
|
/>
|
||||||
|
</DatePickerContextProvider>
|
||||||
</StorageContextProvider>
|
</StorageContextProvider>
|
||||||
</KibanaContextProvider>
|
</KibanaContextProvider>
|
||||||
</KibanaThemeProvider>
|
</KibanaThemeProvider>
|
||||||
|
|
|
@ -1,15 +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
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Subject } from 'rxjs';
|
|
||||||
|
|
||||||
export interface Refresh {
|
|
||||||
lastRefresh: number;
|
|
||||||
timeRange?: { start: string; end: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
export const dataVisualizerRefresh$ = new Subject<Refresh>();
|
|
|
@ -5,20 +5,14 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { type FrozenTierPreference } from '@kbn/ml-date-picker';
|
||||||
|
|
||||||
import { RandomSamplerOption } from '../constants/random_sampler';
|
import { RandomSamplerOption } from '../constants/random_sampler';
|
||||||
|
|
||||||
export const DV_FROZEN_TIER_PREFERENCE = 'dataVisualizer.frozenDataTierPreference';
|
export const DV_FROZEN_TIER_PREFERENCE = 'dataVisualizer.frozenDataTierPreference';
|
||||||
export const DV_RANDOM_SAMPLER_PREFERENCE = 'dataVisualizer.randomSamplerPreference';
|
export const DV_RANDOM_SAMPLER_PREFERENCE = 'dataVisualizer.randomSamplerPreference';
|
||||||
export const DV_RANDOM_SAMPLER_P_VALUE = 'dataVisualizer.randomSamplerPValue';
|
export const DV_RANDOM_SAMPLER_P_VALUE = 'dataVisualizer.randomSamplerPValue';
|
||||||
|
|
||||||
export const FROZEN_TIER_PREFERENCE = {
|
|
||||||
EXCLUDE: 'exclude-frozen',
|
|
||||||
INCLUDE: 'include-frozen',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type FrozenTierPreference =
|
|
||||||
typeof FROZEN_TIER_PREFERENCE[keyof typeof FROZEN_TIER_PREFERENCE];
|
|
||||||
|
|
||||||
export type DV = Partial<{
|
export type DV = Partial<{
|
||||||
[DV_FROZEN_TIER_PREFERENCE]: FrozenTierPreference;
|
[DV_FROZEN_TIER_PREFERENCE]: FrozenTierPreference;
|
||||||
[DV_RANDOM_SAMPLER_PREFERENCE]: RandomSamplerOption;
|
[DV_RANDOM_SAMPLER_PREFERENCE]: RandomSamplerOption;
|
||||||
|
|
|
@ -1,76 +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
|
|
||||||
* 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' } } }],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -53,6 +53,7 @@
|
||||||
"@kbn/ml-nested-property",
|
"@kbn/ml-nested-property",
|
||||||
"@kbn/ml-url-state",
|
"@kbn/ml-url-state",
|
||||||
"@kbn/ml-local-storage",
|
"@kbn/ml-local-storage",
|
||||||
|
"@kbn/ml-date-picker",
|
||||||
"@kbn/ml-is-defined",
|
"@kbn/ml-is-defined",
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { type FrozenTierPreference } from '@kbn/ml-date-picker';
|
||||||
|
|
||||||
import { EntityFieldType } from './anomalies';
|
import { EntityFieldType } from './anomalies';
|
||||||
|
|
||||||
export const ML_ENTITY_FIELDS_CONFIG = 'ml.singleMetricViewer.partitionFields' as const;
|
export const ML_ENTITY_FIELDS_CONFIG = 'ml.singleMetricViewer.partitionFields' as const;
|
||||||
|
@ -14,14 +16,6 @@ export const ML_FROZEN_TIER_PREFERENCE = 'ml.frozenDataTierPreference';
|
||||||
export const ML_ANOMALY_EXPLORER_PANELS = 'ml.anomalyExplorerPanels';
|
export const ML_ANOMALY_EXPLORER_PANELS = 'ml.anomalyExplorerPanels';
|
||||||
export const ML_NOTIFICATIONS_LAST_CHECKED_AT = 'ml.notificationsLastCheckedAt';
|
export const ML_NOTIFICATIONS_LAST_CHECKED_AT = 'ml.notificationsLastCheckedAt';
|
||||||
|
|
||||||
export const FROZEN_TIER_PREFERENCE = {
|
|
||||||
EXCLUDE: 'exclude-frozen',
|
|
||||||
INCLUDE: 'include-frozen',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type FrozenTierPreference =
|
|
||||||
typeof FROZEN_TIER_PREFERENCE[keyof typeof FROZEN_TIER_PREFERENCE];
|
|
||||||
|
|
||||||
export type PartitionFieldConfig =
|
export type PartitionFieldConfig =
|
||||||
| {
|
| {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,53 +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
|
|
||||||
* 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 '@kbn/ml-is-populated-object';
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
|
@ -8,24 +8,28 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import './_index.scss';
|
import './_index.scss';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
import { AppMountParameters, CoreStart, HttpStart } from '@kbn/core/public';
|
import { AppMountParameters, CoreStart, HttpStart } from '@kbn/core/public';
|
||||||
|
|
||||||
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
|
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
|
||||||
|
import { DatePickerContextProvider } from '@kbn/ml-date-picker';
|
||||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||||
|
import { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||||
|
import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public';
|
||||||
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
|
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
|
||||||
import { StorageContextProvider } from '@kbn/ml-local-storage';
|
import { StorageContextProvider } from '@kbn/ml-local-storage';
|
||||||
|
|
||||||
import { ML_STORAGE_KEYS } from '../../common/types/storage';
|
import { ML_STORAGE_KEYS } from '../../common/types/storage';
|
||||||
|
import { ML_APP_LOCATOR, ML_PAGES } from '../../common/constants/locator';
|
||||||
|
import type { MlSetupDependencies, MlStartDependencies } from '../plugin';
|
||||||
|
|
||||||
import { setDependencyCache, clearCache } from './util/dependency_cache';
|
import { setDependencyCache, clearCache } from './util/dependency_cache';
|
||||||
import { setLicenseCache } from './license';
|
import { setLicenseCache } from './license';
|
||||||
import type { MlSetupDependencies, MlStartDependencies } from '../plugin';
|
|
||||||
import { mlUsageCollectionProvider } from './services/usage_collection';
|
import { mlUsageCollectionProvider } from './services/usage_collection';
|
||||||
|
|
||||||
import { MlRouter } from './routing';
|
import { MlRouter } from './routing';
|
||||||
import { mlApiServicesProvider } from './services/ml_api_service';
|
import { mlApiServicesProvider } from './services/ml_api_service';
|
||||||
import { HttpService } from './services/http_service';
|
import { HttpService } from './services/http_service';
|
||||||
import { ML_APP_LOCATOR, ML_PAGES } from '../../common/constants/locator';
|
|
||||||
|
|
||||||
export type MlDependencies = Omit<
|
export type MlDependencies = Omit<
|
||||||
MlSetupDependencies,
|
MlSetupDependencies,
|
||||||
|
@ -98,6 +102,13 @@ const App: FC<AppProps> = ({ coreStart, deps, appMountParams }) => {
|
||||||
...coreStart,
|
...coreStart,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const datePickerDeps = {
|
||||||
|
...pick(services, ['data', 'http', 'notifications', 'theme', 'uiSettings']),
|
||||||
|
toMountPoint,
|
||||||
|
wrapWithTheme,
|
||||||
|
uiSettingsKeys: UI_SETTINGS,
|
||||||
|
};
|
||||||
|
|
||||||
const I18nContext = coreStart.i18n.Context;
|
const I18nContext = coreStart.i18n.Context;
|
||||||
const ApplicationUsageTrackingProvider =
|
const ApplicationUsageTrackingProvider =
|
||||||
deps.usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment;
|
deps.usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment;
|
||||||
|
@ -113,7 +124,9 @@ const App: FC<AppProps> = ({ coreStart, deps, appMountParams }) => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StorageContextProvider storage={localStorage} storageKeys={ML_STORAGE_KEYS}>
|
<StorageContextProvider storage={localStorage} storageKeys={ML_STORAGE_KEYS}>
|
||||||
<MlRouter pageDeps={pageDeps} />
|
<DatePickerContextProvider {...datePickerDeps}>
|
||||||
|
<MlRouter pageDeps={pageDeps} />
|
||||||
|
</DatePickerContextProvider>
|
||||||
</StorageContextProvider>
|
</StorageContextProvider>
|
||||||
</KibanaContextProvider>
|
</KibanaContextProvider>
|
||||||
</KibanaThemeProvider>
|
</KibanaThemeProvider>
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`FullTimeRangeSelector renders the selector 1`] = `
|
|
||||||
<EuiFlexGroup
|
|
||||||
alignItems="center"
|
|
||||||
gutterSize="xs"
|
|
||||||
responsive={false}
|
|
||||||
>
|
|
||||||
<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 {}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
delay="regular"
|
|
||||||
display="inlineBlock"
|
|
||||||
position="top"
|
|
||||||
>
|
|
||||||
<EuiButton
|
|
||||||
color="primary"
|
|
||||||
data-test-subj="mlButtonUseFullData"
|
|
||||||
isDisabled={false}
|
|
||||||
onClick={[Function]}
|
|
||||||
size="m"
|
|
||||||
>
|
|
||||||
<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="inline-block"
|
|
||||||
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>
|
|
||||||
`;
|
|
|
@ -1,166 +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
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { FC, useCallback, useMemo, useState } from 'react';
|
|
||||||
|
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
|
||||||
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 '@kbn/data-views-plugin/public';
|
|
||||||
import { useStorage } from '@kbn/ml-local-storage';
|
|
||||||
import { setFullTimeRange } from './full_time_range_selector_service';
|
|
||||||
import {
|
|
||||||
ML_FROZEN_TIER_PREFERENCE,
|
|
||||||
FROZEN_TIER_PREFERENCE,
|
|
||||||
type MlStorageKey,
|
|
||||||
type TMlStorageMapped,
|
|
||||||
type FrozenTierPreference,
|
|
||||||
} from '../../../../common/types/storage';
|
|
||||||
import { GetTimeFieldRangeResponse } from '../../services/ml_api_service';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
dataView: DataView;
|
|
||||||
query: QueryDslQueryContainer;
|
|
||||||
disabled: boolean;
|
|
||||||
callback?: (a: GetTimeFieldRangeResponse) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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: 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<
|
|
||||||
MlStorageKey,
|
|
||||||
TMlStorageMapped<typeof ML_FROZEN_TIER_PREFERENCE>
|
|
||||||
>(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();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const popoverContent = useMemo(
|
|
||||||
() => (
|
|
||||||
<EuiPanel>
|
|
||||||
<EuiRadioGroup
|
|
||||||
options={sortOptions}
|
|
||||||
idSelected={frozenDataPreference}
|
|
||||||
onChange={setPreference}
|
|
||||||
compressed
|
|
||||||
/>
|
|
||||||
</EuiPanel>
|
|
||||||
),
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[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 (
|
|
||||||
<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>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,68 +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
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import moment from 'moment';
|
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import dateMath from '@kbn/datemath';
|
|
||||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
|
||||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
|
||||||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
|
||||||
import { getTimefilter, getToastNotifications } from '../../util/dependency_cache';
|
|
||||||
import { ml, GetTimeFieldRangeResponse } from '../../services/ml_api_service';
|
|
||||||
import type { RuntimeMappings } from '../../../../common/types/fields';
|
|
||||||
import { addExcludeFrozenToQuery } from '../../../../common/util/query_utils';
|
|
||||||
|
|
||||||
export interface TimeRange {
|
|
||||||
from: number;
|
|
||||||
to: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function setFullTimeRange(
|
|
||||||
indexPattern: DataView,
|
|
||||||
query: QueryDslQueryContainer,
|
|
||||||
excludeFrozenData: boolean
|
|
||||||
): Promise<GetTimeFieldRangeResponse> {
|
|
||||||
try {
|
|
||||||
const timefilter = getTimefilter();
|
|
||||||
const runtimeMappings = indexPattern.getComputedFields().runtimeFields as RuntimeMappings;
|
|
||||||
const resp = await ml.getTimeFieldRange({
|
|
||||||
index: indexPattern.title,
|
|
||||||
timeFieldName: indexPattern.timeFieldName,
|
|
||||||
// By default we want to use full non-frozen time range
|
|
||||||
query: excludeFrozenData ? addExcludeFrozenToQuery(query) : query,
|
|
||||||
...(isPopulatedObject(runtimeMappings) ? { runtimeMappings } : {}),
|
|
||||||
});
|
|
||||||
|
|
||||||
timefilter.setTime({
|
|
||||||
from: moment(resp.start).toISOString(),
|
|
||||||
to: moment(resp.end).toISOString(),
|
|
||||||
});
|
|
||||||
return resp;
|
|
||||||
} catch (resp) {
|
|
||||||
const toastNotifications = getToastNotifications();
|
|
||||||
toastNotifications.addDanger(
|
|
||||||
i18n.translate('xpack.ml.fullTimeRangeSelector.errorSettingTimeRangeNotification', {
|
|
||||||
defaultMessage: 'An error occurred setting the time range.',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTimeFilterRange(): TimeRange {
|
|
||||||
const timefilter = getTimefilter();
|
|
||||||
const fromMoment = dateMath.parse(timefilter.getTime().from);
|
|
||||||
const toMoment = dateMath.parse(timefilter.getTime().to);
|
|
||||||
const from = fromMoment !== undefined ? fromMoment.valueOf() : 0;
|
|
||||||
const to = toMoment !== undefined ? toMoment.valueOf() : 0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
to,
|
|
||||||
from,
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,10 +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
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { FullTimeRangeSelector } from './full_time_range_selector';
|
|
||||||
export type { TimeRange } from './full_time_range_selector_service';
|
|
||||||
export { getTimeFilterRange } from './full_time_range_selector_service';
|
|
|
@ -5,20 +5,21 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createContext, FC, useMemo, useState } from 'react';
|
import React, { createContext, FC, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
import { EuiPageContentBody_Deprecated as EuiPageContentBody } from '@elastic/eui';
|
import { EuiPageContentBody_Deprecated as EuiPageContentBody } from '@elastic/eui';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { Redirect, Route, Switch } from 'react-router-dom';
|
import { Redirect, Route, Switch } from 'react-router-dom';
|
||||||
import type { AppMountParameters } from '@kbn/core/public';
|
import type { AppMountParameters } from '@kbn/core/public';
|
||||||
import { KibanaPageTemplate, RedirectAppLinks } from '@kbn/kibana-react-plugin/public';
|
import { KibanaPageTemplate, RedirectAppLinks } from '@kbn/kibana-react-plugin/public';
|
||||||
import { createHtmlPortalNode, HtmlPortalNode } from 'react-reverse-portal';
|
import { createHtmlPortalNode, HtmlPortalNode } from 'react-reverse-portal';
|
||||||
|
import { DatePickerWrapper } from '@kbn/ml-date-picker';
|
||||||
import { MlPageHeaderRenderer } from '../page_header/page_header';
|
import { MlPageHeaderRenderer } from '../page_header/page_header';
|
||||||
import { useSideNavItems } from './side_nav';
|
import { useSideNavItems } from './side_nav';
|
||||||
import * as routes from '../../routing/routes';
|
import * as routes from '../../routing/routes';
|
||||||
import { MlPageWrapper } from '../../routing/ml_page_wrapper';
|
import { MlPageWrapper } from '../../routing/ml_page_wrapper';
|
||||||
import { useMlKibana, useNavigateToPath } from '../../contexts/kibana';
|
import { useMlKibana, useNavigateToPath } from '../../contexts/kibana';
|
||||||
import { MlRoute, PageDependencies } from '../../routing/router';
|
import { MlRoute, PageDependencies } from '../../routing/router';
|
||||||
import { DatePickerWrapper } from '../navigation_menu/date_picker_wrapper';
|
|
||||||
import { useActiveRoute } from '../../routing/use_active_route';
|
import { useActiveRoute } from '../../routing/use_active_route';
|
||||||
import { useDocTitle } from '../../routing/use_doc_title';
|
import { useDocTitle } from '../../routing/use_doc_title';
|
||||||
|
|
||||||
|
@ -43,11 +44,28 @@ export const MlPage: FC<{ pageDeps: PageDependencies }> = React.memo(({ pageDeps
|
||||||
const {
|
const {
|
||||||
services: {
|
services: {
|
||||||
http: { basePath },
|
http: { basePath },
|
||||||
|
mlServices: { httpService },
|
||||||
},
|
},
|
||||||
} = useMlKibana();
|
} = useMlKibana();
|
||||||
|
|
||||||
const headerPortalNode = useMemo(() => createHtmlPortalNode(), []);
|
const headerPortalNode = useMemo(() => createHtmlPortalNode(), []);
|
||||||
const [isHeaderMounted, setIsHeaderMounted] = useState(false);
|
const [isHeaderMounted, setIsHeaderMounted] = useState(false);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const subscriptions = new Subscription();
|
||||||
|
|
||||||
|
subscriptions.add(
|
||||||
|
httpService.getLoadingCount$.subscribe((v) => {
|
||||||
|
setIsLoading(v !== 0);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return function cleanup() {
|
||||||
|
subscriptions.unsubscribe();
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
const routeList = useMemo(
|
const routeList = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
@ -61,8 +79,8 @@ export const MlPage: FC<{ pageDeps: PageDependencies }> = React.memo(({ pageDeps
|
||||||
const activeRoute = useActiveRoute(routeList);
|
const activeRoute = useActiveRoute(routeList);
|
||||||
|
|
||||||
const rightSideItems = useMemo(() => {
|
const rightSideItems = useMemo(() => {
|
||||||
return [...(activeRoute.enableDatePicker ? [<DatePickerWrapper />] : [])];
|
return [...(activeRoute.enableDatePicker ? [<DatePickerWrapper isLoading={isLoading} />] : [])];
|
||||||
}, [activeRoute.enableDatePicker]);
|
}, [activeRoute.enableDatePicker, isLoading]);
|
||||||
|
|
||||||
useDocTitle(activeRoute);
|
useDocTitle(activeRoute);
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ export { useMlKibana } from './kibana_context';
|
||||||
export type { NavigateToPath } from './use_navigate_to_path';
|
export type { NavigateToPath } from './use_navigate_to_path';
|
||||||
export { useNavigateToPath } from './use_navigate_to_path';
|
export { useNavigateToPath } from './use_navigate_to_path';
|
||||||
export { useUiSettings } from './use_ui_settings_context';
|
export { useUiSettings } from './use_ui_settings_context';
|
||||||
export { useTimefilter } from './use_timefilter';
|
|
||||||
export { useNotifications } from './use_notifications_context';
|
export { useNotifications } from './use_notifications_context';
|
||||||
export { useMlLocator, useMlLink } from './use_create_url';
|
export { useMlLocator, useMlLink } from './use_create_url';
|
||||||
export { useMlApiContext } from './use_ml_api_context';
|
export { useMlApiContext } from './use_ml_api_context';
|
||||||
|
|
|
@ -1,60 +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
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import useObservable from 'react-use/lib/useObservable';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
import { useMlKibana } from './kibana_context';
|
|
||||||
|
|
||||||
interface UseTimefilterOptions {
|
|
||||||
timeRangeSelector?: boolean;
|
|
||||||
autoRefreshSelector?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useTimefilter = ({
|
|
||||||
timeRangeSelector,
|
|
||||||
autoRefreshSelector,
|
|
||||||
}: UseTimefilterOptions = {}) => {
|
|
||||||
const { services } = useMlKibana();
|
|
||||||
const { timefilter } = services.data.query.timefilter;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (timeRangeSelector === true) {
|
|
||||||
timefilter.enableTimeRangeSelector();
|
|
||||||
} else if (timeRangeSelector === false) {
|
|
||||||
timefilter.disableTimeRangeSelector();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (autoRefreshSelector === true) {
|
|
||||||
timefilter.enableAutoRefreshSelector();
|
|
||||||
} else if (autoRefreshSelector === false) {
|
|
||||||
timefilter.disableAutoRefreshSelector();
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [timeRangeSelector, autoRefreshSelector]);
|
|
||||||
|
|
||||||
return timefilter;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useRefreshIntervalUpdates = () => {
|
|
||||||
const timefilter = useTimefilter();
|
|
||||||
|
|
||||||
return useObservable(
|
|
||||||
timefilter.getRefreshIntervalUpdate$().pipe(map(timefilter.getRefreshInterval)),
|
|
||||||
timefilter.getRefreshInterval()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useTimeRangeUpdates = (absolute = false) => {
|
|
||||||
const timefilter = useTimefilter();
|
|
||||||
|
|
||||||
const getTimeCallback = absolute
|
|
||||||
? timefilter.getAbsoluteTime.bind(timefilter)
|
|
||||||
: timefilter.getTime.bind(timefilter);
|
|
||||||
|
|
||||||
return useObservable(timefilter.getTimeUpdate$().pipe(map(getTimeCallback)), getTimeCallback());
|
|
||||||
};
|
|
|
@ -20,8 +20,9 @@ import {
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
|
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||||
import { isFullLicense } from '../license';
|
import { isFullLicense } from '../license';
|
||||||
import { useTimefilter, useMlKibana, useNavigateToPath } from '../contexts/kibana';
|
import { useMlKibana, useNavigateToPath } from '../contexts/kibana';
|
||||||
import { HelpMenu } from '../components/help_menu';
|
import { HelpMenu } from '../components/help_menu';
|
||||||
import { MlPageHeader } from '../components/page_header';
|
import { MlPageHeader } from '../components/page_header';
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import type {
|
||||||
GetAdditionalLinksParams,
|
GetAdditionalLinksParams,
|
||||||
GetAdditionalLinks,
|
GetAdditionalLinks,
|
||||||
} from '@kbn/data-visualizer-plugin/public';
|
} from '@kbn/data-visualizer-plugin/public';
|
||||||
import { useTimefilter } from '../../contexts/kibana';
|
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||||
import { HelpMenu } from '../../components/help_menu';
|
import { HelpMenu } from '../../components/help_menu';
|
||||||
import { useMlKibana, useMlLocator } from '../../contexts/kibana';
|
import { useMlKibana, useMlLocator } from '../../contexts/kibana';
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,8 @@ import type {
|
||||||
GetAdditionalLinks,
|
GetAdditionalLinks,
|
||||||
GetAdditionalLinksParams,
|
GetAdditionalLinksParams,
|
||||||
} from '@kbn/data-visualizer-plugin/public';
|
} from '@kbn/data-visualizer-plugin/public';
|
||||||
import { useMlKibana, useTimefilter, useMlLocator } from '../../contexts/kibana';
|
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||||
|
import { useMlKibana, useMlLocator } from '../../contexts/kibana';
|
||||||
import { HelpMenu } from '../../components/help_menu';
|
import { HelpMenu } from '../../components/help_menu';
|
||||||
import { ML_PAGES } from '../../../../common/constants/locator';
|
import { ML_PAGES } from '../../../../common/constants/locator';
|
||||||
import { isFullLicense } from '../../license';
|
import { isFullLicense } from '../../license';
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { switchMap, map } from 'rxjs/operators';
|
||||||
|
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { TimefilterContract } from '@kbn/data-plugin/public';
|
import { TimefilterContract } from '@kbn/data-plugin/public';
|
||||||
|
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||||
import {
|
import {
|
||||||
getDateFormatTz,
|
getDateFormatTz,
|
||||||
getSelectionInfluencers,
|
getSelectionInfluencers,
|
||||||
|
@ -28,7 +29,7 @@ import {
|
||||||
ExplorerJob,
|
ExplorerJob,
|
||||||
} from '../explorer_utils';
|
} from '../explorer_utils';
|
||||||
import { ExplorerState } from '../reducers';
|
import { ExplorerState } from '../reducers';
|
||||||
import { useMlKibana, useTimefilter } from '../../contexts/kibana';
|
import { useMlKibana } from '../../contexts/kibana';
|
||||||
import { MlResultsService, mlResultsServiceProvider } from '../../services/results_service';
|
import { MlResultsService, mlResultsServiceProvider } from '../../services/results_service';
|
||||||
import { AnomalyExplorerChartsService } from '../../services/anomaly_explorer_charts_service';
|
import { AnomalyExplorerChartsService } from '../../services/anomaly_explorer_charts_service';
|
||||||
import type { InfluencersFilterQuery } from '../../../../common/types/es_client';
|
import type { InfluencersFilterQuery } from '../../../../common/types/es_client';
|
||||||
|
|
|
@ -20,13 +20,13 @@ import { i18n } from '@kbn/i18n';
|
||||||
import useObservable from 'react-use/lib/useObservable';
|
import useObservable from 'react-use/lib/useObservable';
|
||||||
import type { Query, TimeRange } from '@kbn/es-query';
|
import type { Query, TimeRange } from '@kbn/es-query';
|
||||||
import { isDefined } from '@kbn/ml-is-defined';
|
import { isDefined } from '@kbn/ml-is-defined';
|
||||||
|
import { useTimeRangeUpdates } from '@kbn/ml-date-picker';
|
||||||
import { useAnomalyExplorerContext } from './anomaly_explorer_context';
|
import { useAnomalyExplorerContext } from './anomaly_explorer_context';
|
||||||
import { escapeKueryForFieldValuePair } from '../util/string_utils';
|
import { escapeKueryForFieldValuePair } from '../util/string_utils';
|
||||||
import { SEARCH_QUERY_LANGUAGE } from '../../../common/constants/search';
|
import { SEARCH_QUERY_LANGUAGE } from '../../../common/constants/search';
|
||||||
import { useCasesModal } from '../contexts/kibana/use_cases_modal';
|
import { useCasesModal } from '../contexts/kibana/use_cases_modal';
|
||||||
import { DEFAULT_MAX_SERIES_TO_PLOT } from '../services/anomaly_explorer_charts_service';
|
import { DEFAULT_MAX_SERIES_TO_PLOT } from '../services/anomaly_explorer_charts_service';
|
||||||
import { ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE } from '../../embeddables';
|
import { ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE } from '../../embeddables';
|
||||||
import { useTimeRangeUpdates } from '../contexts/kibana/use_timefilter';
|
|
||||||
import { useMlKibana } from '../contexts/kibana';
|
import { useMlKibana } from '../contexts/kibana';
|
||||||
import {
|
import {
|
||||||
AppStateSelectedCells,
|
AppStateSelectedCells,
|
||||||
|
|
|
@ -6,9 +6,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useContext, useEffect, useMemo, useState, type FC } from 'react';
|
import React, { useContext, useEffect, useMemo, useState, type FC } from 'react';
|
||||||
|
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||||
import { AnomalyTimelineStateService } from './anomaly_timeline_state_service';
|
import { AnomalyTimelineStateService } from './anomaly_timeline_state_service';
|
||||||
import { AnomalyExplorerCommonStateService } from './anomaly_explorer_common_state';
|
import { AnomalyExplorerCommonStateService } from './anomaly_explorer_common_state';
|
||||||
import { useMlKibana, useTimefilter } from '../contexts/kibana';
|
import { useMlKibana } from '../contexts/kibana';
|
||||||
import { mlResultsServiceProvider } from '../services/results_service';
|
import { mlResultsServiceProvider } from '../services/results_service';
|
||||||
import { AnomalyTimelineService } from '../services/anomaly_timeline_service';
|
import { AnomalyTimelineService } from '../services/anomaly_timeline_service';
|
||||||
import { useExplorerUrlState } from './hooks/use_explorer_url_state';
|
import { useExplorerUrlState } from './hooks/use_explorer_url_state';
|
||||||
|
|
|
@ -28,9 +28,9 @@ import useDebounce from 'react-use/lib/useDebounce';
|
||||||
import useObservable from 'react-use/lib/useObservable';
|
import useObservable from 'react-use/lib/useObservable';
|
||||||
import type { Query } from '@kbn/es-query';
|
import type { Query } from '@kbn/es-query';
|
||||||
import { isDefined } from '@kbn/ml-is-defined';
|
import { isDefined } from '@kbn/ml-is-defined';
|
||||||
|
import { useTimeRangeUpdates } from '@kbn/ml-date-picker';
|
||||||
import { SEARCH_QUERY_LANGUAGE } from '../../../common/constants/search';
|
import { SEARCH_QUERY_LANGUAGE } from '../../../common/constants/search';
|
||||||
import { useCasesModal } from '../contexts/kibana/use_cases_modal';
|
import { useCasesModal } from '../contexts/kibana/use_cases_modal';
|
||||||
import { useTimeRangeUpdates } from '../contexts/kibana/use_timefilter';
|
|
||||||
import { ANOMALY_SWIMLANE_EMBEDDABLE_TYPE } from '../..';
|
import { ANOMALY_SWIMLANE_EMBEDDABLE_TYPE } from '../..';
|
||||||
import {
|
import {
|
||||||
OVERALL_LABEL,
|
OVERALL_LABEL,
|
||||||
|
|
|
@ -19,6 +19,8 @@ import {
|
||||||
import { isEqual, sortBy, uniq } from 'lodash';
|
import { isEqual, sortBy, uniq } from 'lodash';
|
||||||
import type { TimefilterContract } from '@kbn/data-plugin/public';
|
import type { TimefilterContract } from '@kbn/data-plugin/public';
|
||||||
import type { TimeRangeBounds } from '@kbn/data-plugin/common';
|
import type { TimeRangeBounds } from '@kbn/data-plugin/common';
|
||||||
|
// FIXME get rid of the static import
|
||||||
|
import { mlTimefilterRefresh$ } from '@kbn/ml-date-picker';
|
||||||
import { AnomalyTimelineService } from '../services/anomaly_timeline_service';
|
import { AnomalyTimelineService } from '../services/anomaly_timeline_service';
|
||||||
import type {
|
import type {
|
||||||
AppStateSelectedCells,
|
AppStateSelectedCells,
|
||||||
|
@ -38,8 +40,6 @@ import { mlJobService } from '../services/job_service';
|
||||||
import { getSelectionInfluencers, getSelectionTimeRange } from './explorer_utils';
|
import { getSelectionInfluencers, getSelectionTimeRange } from './explorer_utils';
|
||||||
import type { TimeBucketsInterval } from '../util/time_buckets';
|
import type { TimeBucketsInterval } from '../util/time_buckets';
|
||||||
import { InfluencersFilterQuery } from '../../../common/types/es_client';
|
import { InfluencersFilterQuery } from '../../../common/types/es_client';
|
||||||
// FIXME get rid of the static import
|
|
||||||
import { mlTimefilterRefresh$ } from '../services/timefilter_refresh_service';
|
|
||||||
import type { Refresh } from '../routing/use_refresh';
|
import type { Refresh } from '../routing/use_refresh';
|
||||||
import { StateService } from '../services/state_service';
|
import { StateService } from '../services/state_service';
|
||||||
import type { AnomalyExplorerUrlStateService } from './hooks/use_explorer_url_state';
|
import type { AnomalyExplorerUrlStateService } from './hooks/use_explorer_url_state';
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { BehaviorSubject } from 'rxjs';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import { ES_FIELD_TYPES } from '@kbn/field-types';
|
import { ES_FIELD_TYPES } from '@kbn/field-types';
|
||||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||||
|
import { addExcludeFrozenToQuery } from '@kbn/ml-query-utils';
|
||||||
import { SavedSearchSavedObject } from '../../../../../../common/types/kibana';
|
import { SavedSearchSavedObject } from '../../../../../../common/types/kibana';
|
||||||
import { UrlConfig } from '../../../../../../common/types/custom_urls';
|
import { UrlConfig } from '../../../../../../common/types/custom_urls';
|
||||||
import { IndexPatternTitle } from '../../../../../../common/types/kibana';
|
import { IndexPatternTitle } from '../../../../../../common/types/kibana';
|
||||||
|
@ -28,7 +29,6 @@ import {
|
||||||
} from '../../../../../../common/types/anomaly_detection_jobs';
|
} from '../../../../../../common/types/anomaly_detection_jobs';
|
||||||
import { Aggregation, Field, RuntimeMappings } from '../../../../../../common/types/fields';
|
import { Aggregation, Field, RuntimeMappings } from '../../../../../../common/types/fields';
|
||||||
import { combineFieldsAndAggs } from '../../../../../../common/util/fields_utils';
|
import { combineFieldsAndAggs } from '../../../../../../common/util/fields_utils';
|
||||||
import { addExcludeFrozenToQuery } from '../../../../../../common/util/query_utils';
|
|
||||||
import { createEmptyJob, createEmptyDatafeed } from './util/default_configs';
|
import { createEmptyJob, createEmptyDatafeed } from './util/default_configs';
|
||||||
import { mlJobService } from '../../../../services/job_service';
|
import { mlJobService } from '../../../../services/job_service';
|
||||||
import { JobRunner, ProgressSubscriber } from '../job_runner';
|
import { JobRunner, ProgressSubscriber } from '../job_runner';
|
||||||
|
|
|
@ -10,19 +10,26 @@ import { i18n } from '@kbn/i18n';
|
||||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||||
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { FullTimeRangeSelector, FROZEN_TIER_PREFERENCE } from '@kbn/ml-date-picker';
|
||||||
|
import { useTimefilter, type GetTimeFieldRangeResponse } from '@kbn/ml-date-picker';
|
||||||
|
import { useStorage } from '@kbn/ml-local-storage';
|
||||||
import { WizardNav } from '../wizard_nav';
|
import { WizardNav } from '../wizard_nav';
|
||||||
import { StepProps, WIZARD_STEPS } from '../step_types';
|
import { StepProps, WIZARD_STEPS } from '../step_types';
|
||||||
import { JobCreatorContext } from '../job_creator_context';
|
import { JobCreatorContext } from '../job_creator_context';
|
||||||
import { useMlContext } from '../../../../../contexts/ml';
|
import { useMlContext } from '../../../../../contexts/ml';
|
||||||
import { FullTimeRangeSelector } from '../../../../../components/full_time_range_selector';
|
|
||||||
import { EventRateChart } from '../charts/event_rate_chart';
|
import { EventRateChart } from '../charts/event_rate_chart';
|
||||||
import { LineChartPoint } from '../../../common/chart_loader';
|
import { LineChartPoint } from '../../../common/chart_loader';
|
||||||
import { JOB_TYPE } from '../../../../../../../common/constants/new_job';
|
import { JOB_TYPE } from '../../../../../../../common/constants/new_job';
|
||||||
import { GetTimeFieldRangeResponse } from '../../../../../services/ml_api_service';
|
|
||||||
import { TimeRangePicker, TimeRange } from '../../../common/components';
|
import { TimeRangePicker, TimeRange } from '../../../common/components';
|
||||||
import { useMlKibana } from '../../../../../contexts/kibana';
|
import { useMlKibana } from '../../../../../contexts/kibana';
|
||||||
|
import {
|
||||||
|
ML_FROZEN_TIER_PREFERENCE,
|
||||||
|
type MlStorageKey,
|
||||||
|
type TMlStorageMapped,
|
||||||
|
} from '../../../../../../../common/types/storage';
|
||||||
|
|
||||||
export const TimeRangeStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) => {
|
export const TimeRangeStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) => {
|
||||||
|
const timefilter = useTimefilter();
|
||||||
const { services } = useMlKibana();
|
const { services } = useMlKibana();
|
||||||
const mlContext = useMlContext();
|
const mlContext = useMlContext();
|
||||||
|
|
||||||
|
@ -36,6 +43,15 @@ export const TimeRangeStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep })
|
||||||
const [eventRateChartData, setEventRateChartData] = useState<LineChartPoint[]>([]);
|
const [eventRateChartData, setEventRateChartData] = useState<LineChartPoint[]>([]);
|
||||||
const [loadingData, setLoadingData] = useState(false);
|
const [loadingData, setLoadingData] = useState(false);
|
||||||
|
|
||||||
|
const [frozenDataPreference, setFrozenDataPreference] = useStorage<
|
||||||
|
MlStorageKey,
|
||||||
|
TMlStorageMapped<typeof ML_FROZEN_TIER_PREFERENCE>
|
||||||
|
>(
|
||||||
|
ML_FROZEN_TIER_PREFERENCE,
|
||||||
|
// By default we will exclude frozen data tier
|
||||||
|
FROZEN_TIER_PREFERENCE.EXCLUDE
|
||||||
|
);
|
||||||
|
|
||||||
async function loadChart() {
|
async function loadChart() {
|
||||||
setLoadingData(true);
|
setLoadingData(true);
|
||||||
try {
|
try {
|
||||||
|
@ -61,8 +77,8 @@ export const TimeRangeStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep })
|
||||||
max: moment(end),
|
max: moment(end),
|
||||||
});
|
});
|
||||||
// update the timefilter, to keep the URL in sync
|
// update the timefilter, to keep the URL in sync
|
||||||
const { timefilter } = services.data.query.timefilter;
|
const { timefilter: timefilterService } = services.data.query.timefilter;
|
||||||
timefilter.setTime({
|
timefilterService.setTime({
|
||||||
from: moment(start).toISOString(),
|
from: moment(start).toISOString(),
|
||||||
to: moment(end).toISOString(),
|
to: moment(end).toISOString(),
|
||||||
});
|
});
|
||||||
|
@ -89,8 +105,8 @@ export const TimeRangeStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep })
|
||||||
function fullTimeRangeCallback(range: GetTimeFieldRangeResponse) {
|
function fullTimeRangeCallback(range: GetTimeFieldRangeResponse) {
|
||||||
if (range.start !== null && range.end !== null) {
|
if (range.start !== null && range.end !== null) {
|
||||||
setTimeRange({
|
setTimeRange({
|
||||||
start: range.start,
|
start: range.start.epoch,
|
||||||
end: range.end,
|
end: range.end.epoch,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const { toasts } = services.notifications;
|
const { toasts } = services.notifications;
|
||||||
|
@ -112,10 +128,13 @@ export const TimeRangeStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep })
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<FullTimeRangeSelector
|
<FullTimeRangeSelector
|
||||||
|
frozenDataPreference={frozenDataPreference}
|
||||||
|
setFrozenDataPreference={setFrozenDataPreference}
|
||||||
dataView={mlContext.currentDataView}
|
dataView={mlContext.currentDataView}
|
||||||
query={mlContext.combinedQuery}
|
query={mlContext.combinedQuery}
|
||||||
disabled={false}
|
disabled={false}
|
||||||
callback={fullTimeRangeCallback}
|
callback={fullTimeRangeCallback}
|
||||||
|
timefilter={timefilter}
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem />
|
<EuiFlexItem />
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
import { getTimeFilterRange, useTimefilter } from '@kbn/ml-date-picker';
|
||||||
import { useTimeBuckets } from '../../../../components/custom_hooks/use_time_buckets';
|
import { useTimeBuckets } from '../../../../components/custom_hooks/use_time_buckets';
|
||||||
import { Wizard } from './wizard';
|
import { Wizard } from './wizard';
|
||||||
import { WIZARD_STEPS } from '../components/step_types';
|
import { WIZARD_STEPS } from '../components/step_types';
|
||||||
|
@ -34,7 +35,6 @@ import { ResultsLoader } from '../../common/results_loader';
|
||||||
import { JobValidator } from '../../common/job_validator';
|
import { JobValidator } from '../../common/job_validator';
|
||||||
import { useMlContext } from '../../../../contexts/ml';
|
import { useMlContext } from '../../../../contexts/ml';
|
||||||
import { useMlKibana } from '../../../../contexts/kibana';
|
import { useMlKibana } from '../../../../contexts/kibana';
|
||||||
import { getTimeFilterRange } from '../../../../components/full_time_range_selector';
|
|
||||||
import { ExistingJobsAndGroups, mlJobService } from '../../../../services/job_service';
|
import { ExistingJobsAndGroups, mlJobService } from '../../../../services/job_service';
|
||||||
import { newJobCapsService } from '../../../../services/new_job_capabilities/new_job_capabilities_service';
|
import { newJobCapsService } from '../../../../services/new_job_capabilities/new_job_capabilities_service';
|
||||||
import { EVENT_RATE_FIELD_ID } from '../../../../../../common/types/fields';
|
import { EVENT_RATE_FIELD_ID } from '../../../../../../common/types/fields';
|
||||||
|
@ -52,6 +52,7 @@ export interface PageProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Page: FC<PageProps> = ({ existingJobsAndGroups, jobType }) => {
|
export const Page: FC<PageProps> = ({ existingJobsAndGroups, jobType }) => {
|
||||||
|
const timefilter = useTimefilter();
|
||||||
const mlContext = useMlContext();
|
const mlContext = useMlContext();
|
||||||
const {
|
const {
|
||||||
services: { maps: mapsPlugin },
|
services: { maps: mapsPlugin },
|
||||||
|
@ -74,7 +75,7 @@ export const Page: FC<PageProps> = ({ existingJobsAndGroups, jobType }) => {
|
||||||
|
|
||||||
const { displayErrorToast } = useToastNotificationService();
|
const { displayErrorToast } = useToastNotificationService();
|
||||||
|
|
||||||
const { from, to } = getTimeFilterRange();
|
const { from, to } = getTimeFilterRange(timefilter);
|
||||||
jobCreator.setTimeRange(from, to);
|
jobCreator.setTimeRange(from, to);
|
||||||
|
|
||||||
let firstWizardStep =
|
let firstWizardStep =
|
||||||
|
|
|
@ -19,8 +19,8 @@ import {
|
||||||
EuiSwitch,
|
EuiSwitch,
|
||||||
EuiTextAlign,
|
EuiTextAlign,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
|
import { getTimeFilterRange, useTimefilter } from '@kbn/ml-date-picker';
|
||||||
import { ModuleJobUI, SAVE_STATE } from '../page';
|
import { ModuleJobUI, SAVE_STATE } from '../page';
|
||||||
import { getTimeFilterRange } from '../../../../components/full_time_range_selector';
|
|
||||||
import { useMlContext } from '../../../../contexts/ml';
|
import { useMlContext } from '../../../../contexts/ml';
|
||||||
import {
|
import {
|
||||||
composeValidators,
|
composeValidators,
|
||||||
|
@ -51,7 +51,8 @@ export const JobSettingsForm: FC<JobSettingsFormProps> = ({
|
||||||
saveState,
|
saveState,
|
||||||
jobs,
|
jobs,
|
||||||
}) => {
|
}) => {
|
||||||
const { from, to } = getTimeFilterRange();
|
const timefilter = useTimefilter();
|
||||||
|
const { from, to } = getTimeFilterRange(timefilter);
|
||||||
const { currentDataView: dataView } = useMlContext();
|
const { currentDataView: dataView } = useMlContext();
|
||||||
|
|
||||||
const jobPrefixValidator = useMemo(
|
const jobPrefixValidator = useMemo(
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
import { isEqual, merge } from 'lodash';
|
import { isEqual, merge } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
||||||
|
import { addExcludeFrozenToQuery } from '@kbn/ml-query-utils';
|
||||||
import { useMlKibana, useMlLocator } from '../../../contexts/kibana';
|
import { useMlKibana, useMlLocator } from '../../../contexts/kibana';
|
||||||
import { useMlContext } from '../../../contexts/ml';
|
import { useMlContext } from '../../../contexts/ml';
|
||||||
import {
|
import {
|
||||||
|
@ -41,7 +42,6 @@ import { ML_PAGES } from '../../../../../common/constants/locator';
|
||||||
import { TIME_FORMAT } from '../../../../../common/constants/time_format';
|
import { TIME_FORMAT } from '../../../../../common/constants/time_format';
|
||||||
import { JobsAwaitingNodeWarning } from '../../../components/jobs_awaiting_node_warning';
|
import { JobsAwaitingNodeWarning } from '../../../components/jobs_awaiting_node_warning';
|
||||||
import { RuntimeMappings } from '../../../../../common/types/fields';
|
import { RuntimeMappings } from '../../../../../common/types/fields';
|
||||||
import { addExcludeFrozenToQuery } from '../../../../../common/util/query_utils';
|
|
||||||
import { MlPageHeader } from '../../../components/page_header';
|
import { MlPageHeader } from '../../../components/page_header';
|
||||||
|
|
||||||
export interface ModuleJobUI extends ModuleJob {
|
export interface ModuleJobUI extends ModuleJob {
|
||||||
|
|
|
@ -5,19 +5,65 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { type FC } from 'react';
|
||||||
import { I18nProvider } from '@kbn/i18n-react';
|
import { I18nProvider } from '@kbn/i18n-react';
|
||||||
import { render, waitFor } from '@testing-library/react';
|
import { render, waitFor } from '@testing-library/react';
|
||||||
|
import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker';
|
||||||
import { NotificationsList } from './notifications_list';
|
import { NotificationsList } from './notifications_list';
|
||||||
import { useMlKibana } from '../../contexts/kibana';
|
import { useMlKibana } from '../../contexts/kibana';
|
||||||
|
|
||||||
jest.mock('../../contexts/kibana');
|
jest.mock('../../contexts/kibana');
|
||||||
jest.mock('../../services/toast_notification_service');
|
jest.mock('../../services/toast_notification_service');
|
||||||
jest.mock('../../contexts/ml/ml_notifications_context');
|
jest.mock('../../contexts/ml/ml_notifications_context');
|
||||||
jest.mock('../../contexts/kibana/use_timefilter');
|
|
||||||
jest.mock('../../contexts/kibana/use_field_formatter');
|
jest.mock('../../contexts/kibana/use_field_formatter');
|
||||||
jest.mock('../../components/saved_objects_warning');
|
jest.mock('../../components/saved_objects_warning');
|
||||||
|
|
||||||
|
const getMockedTimefilter = () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const { of } = require('rxjs');
|
||||||
|
return {
|
||||||
|
timefilter: {
|
||||||
|
disableTimeRangeSelector: jest.fn(),
|
||||||
|
disableAutoRefreshSelector: jest.fn(),
|
||||||
|
enableTimeRangeSelector: jest.fn(),
|
||||||
|
enableAutoRefreshSelector: jest.fn(),
|
||||||
|
getRefreshInterval: jest.fn(),
|
||||||
|
setRefreshInterval: jest.fn(),
|
||||||
|
getTime: jest.fn(() => {
|
||||||
|
return { from: '', to: '' };
|
||||||
|
}),
|
||||||
|
setTime: jest.fn(),
|
||||||
|
isAutoRefreshSelectorEnabled: jest.fn(),
|
||||||
|
isTimeRangeSelectorEnabled: jest.fn(),
|
||||||
|
getRefreshIntervalUpdate$: jest.fn(),
|
||||||
|
getTimeUpdate$: jest.fn(() => {
|
||||||
|
return of();
|
||||||
|
}),
|
||||||
|
getEnabledUpdated$: jest.fn(),
|
||||||
|
},
|
||||||
|
history: { get: jest.fn() },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMockedDatePickeDependencies = () => {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
query: {
|
||||||
|
timefilter: getMockedTimefilter(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
notifications: {},
|
||||||
|
} as unknown as DatePickerDependencies;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Wrapper: FC = ({ children }) => (
|
||||||
|
<I18nProvider>
|
||||||
|
<DatePickerContextProvider {...getMockedDatePickeDependencies()}>
|
||||||
|
{children}
|
||||||
|
</DatePickerContextProvider>
|
||||||
|
</I18nProvider>
|
||||||
|
);
|
||||||
|
|
||||||
describe('NotificationsList', () => {
|
describe('NotificationsList', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
|
@ -29,7 +75,7 @@ describe('NotificationsList', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('starts fetching notification on mount with default params', async () => {
|
test('starts fetching notification on mount with default params', async () => {
|
||||||
const {} = render(<NotificationsList />, { wrapper: I18nProvider });
|
const {} = render(<NotificationsList />, { wrapper: Wrapper });
|
||||||
|
|
||||||
jest.advanceTimersByTime(500);
|
jest.advanceTimersByTime(500);
|
||||||
|
|
||||||
|
|
|
@ -24,11 +24,11 @@ import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
|
||||||
import useDebounce from 'react-use/lib/useDebounce';
|
import useDebounce from 'react-use/lib/useDebounce';
|
||||||
import useMount from 'react-use/lib/useMount';
|
import useMount from 'react-use/lib/useMount';
|
||||||
import { usePageUrlState } from '@kbn/ml-url-state';
|
import { usePageUrlState } from '@kbn/ml-url-state';
|
||||||
|
import { useTimefilter, useTimeRangeUpdates } from '@kbn/ml-date-picker';
|
||||||
import { EntityFilter } from './entity_filter';
|
import { EntityFilter } from './entity_filter';
|
||||||
import { useMlNotifications } from '../../contexts/ml/ml_notifications_context';
|
import { useMlNotifications } from '../../contexts/ml/ml_notifications_context';
|
||||||
import { ML_NOTIFICATIONS_MESSAGE_LEVEL } from '../../../../common/constants/notifications';
|
import { ML_NOTIFICATIONS_MESSAGE_LEVEL } from '../../../../common/constants/notifications';
|
||||||
import { SavedObjectsWarning } from '../../components/saved_objects_warning';
|
import { SavedObjectsWarning } from '../../components/saved_objects_warning';
|
||||||
import { useTimefilter, useTimeRangeUpdates } from '../../contexts/kibana/use_timefilter';
|
|
||||||
import { useToastNotificationService } from '../../services/toast_notification_service';
|
import { useToastNotificationService } from '../../services/toast_notification_service';
|
||||||
import { useFieldFormatter } from '../../contexts/kibana/use_field_formatter';
|
import { useFieldFormatter } from '../../contexts/kibana/use_field_formatter';
|
||||||
import { useRefresh } from '../../routing/use_refresh';
|
import { useRefresh } from '../../routing/use_refresh';
|
||||||
|
|
|
@ -7,8 +7,9 @@
|
||||||
|
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||||
import { NotificationsList } from './components/notifications_list';
|
import { NotificationsList } from './components/notifications_list';
|
||||||
import { useMlKibana, useTimefilter } from '../contexts/kibana';
|
import { useMlKibana } from '../contexts/kibana';
|
||||||
import { MlPageHeader } from '../components/page_header';
|
import { MlPageHeader } from '../components/page_header';
|
||||||
import { NodeAvailableWarning } from '../components/node_available_warning';
|
import { NodeAvailableWarning } from '../components/node_available_warning';
|
||||||
import { UpgradeWarning } from '../components/upgrade';
|
import { UpgradeWarning } from '../components/upgrade';
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { Action } from '@elastic/eui/src/components/basic_table/action_types';
|
import { Action } from '@elastic/eui/src/components/basic_table/action_types';
|
||||||
import { useMlLocator, useNavigateToPath, useTimefilter } from '../../../contexts/kibana';
|
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||||
|
import { useMlLocator, useNavigateToPath } from '../../../contexts/kibana';
|
||||||
import { ML_PAGES } from '../../../../../common/constants/locator';
|
import { ML_PAGES } from '../../../../../common/constants/locator';
|
||||||
import { Group } from './anomaly_detection_panel';
|
import { Group } from './anomaly_detection_panel';
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,12 @@
|
||||||
|
|
||||||
import React, { useEffect, useState, type FC } from 'react';
|
import React, { useEffect, useState, type FC } from 'react';
|
||||||
import { EuiSpacer } from '@elastic/eui';
|
import { EuiSpacer } from '@elastic/eui';
|
||||||
|
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||||
import { AnomalyDetectionPanel } from './anomaly_detection_panel';
|
import { AnomalyDetectionPanel } from './anomaly_detection_panel';
|
||||||
import { AnalyticsPanel } from './analytics_panel';
|
import { AnalyticsPanel } from './analytics_panel';
|
||||||
import { AnomalyTimelineService } from '../../services/anomaly_timeline_service';
|
import { AnomalyTimelineService } from '../../services/anomaly_timeline_service';
|
||||||
import { mlResultsServiceProvider } from '../../services/results_service';
|
import { mlResultsServiceProvider } from '../../services/results_service';
|
||||||
import { useMlKibana, useTimefilter } from '../../contexts/kibana';
|
import { useMlKibana } from '../../contexts/kibana';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
createAnomalyDetectionJobDisabled: boolean;
|
createAnomalyDetectionJobDisabled: boolean;
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import React, { FC, useState } from 'react';
|
import React, { FC, useState } from 'react';
|
||||||
import { EuiPanel, EuiSpacer } from '@elastic/eui';
|
import { EuiPanel, EuiSpacer } from '@elastic/eui';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker';
|
||||||
import { checkPermission } from '../capabilities/check_capabilities';
|
import { checkPermission } from '../capabilities/check_capabilities';
|
||||||
import { mlNodesAvailable } from '../ml_nodes_check';
|
import { mlNodesAvailable } from '../ml_nodes_check';
|
||||||
import { GettingStartedCallout } from './components/getting_started_callout';
|
import { GettingStartedCallout } from './components/getting_started_callout';
|
||||||
|
@ -17,9 +18,8 @@ import { JobsAwaitingNodeWarning } from '../components/jobs_awaiting_node_warnin
|
||||||
import { SavedObjectsWarning } from '../components/saved_objects_warning';
|
import { SavedObjectsWarning } from '../components/saved_objects_warning';
|
||||||
import { UpgradeWarning } from '../components/upgrade';
|
import { UpgradeWarning } from '../components/upgrade';
|
||||||
import { HelpMenu } from '../components/help_menu';
|
import { HelpMenu } from '../components/help_menu';
|
||||||
import { useMlKibana, useTimefilter } from '../contexts/kibana';
|
import { useMlKibana } from '../contexts/kibana';
|
||||||
import { NodesList } from '../trained_models/nodes_overview';
|
import { NodesList } from '../trained_models/nodes_overview';
|
||||||
import { mlTimefilterRefresh$ } from '../services/timefilter_refresh_service';
|
|
||||||
import { MlPageHeader } from '../components/page_header';
|
import { MlPageHeader } from '../components/page_header';
|
||||||
|
|
||||||
export const OverviewPage: FC = () => {
|
export const OverviewPage: FC = () => {
|
||||||
|
|
|
@ -14,7 +14,8 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import { EuiThemeProvider as StyledComponentsThemeProvider } from '@kbn/kibana-react-plugin/common';
|
import { EuiThemeProvider as StyledComponentsThemeProvider } from '@kbn/kibana-react-plugin/common';
|
||||||
import { useUrlState } from '@kbn/ml-url-state';
|
import { useUrlState } from '@kbn/ml-url-state';
|
||||||
import { NavigateToPath, useMlKibana, useTimefilter } from '../../contexts/kibana';
|
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||||
|
import { NavigateToPath, useMlKibana } from '../../contexts/kibana';
|
||||||
|
|
||||||
import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs';
|
import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs';
|
||||||
|
|
||||||
|
|
|
@ -8,18 +8,20 @@
|
||||||
import React, { useEffect, FC, useMemo } from 'react';
|
import React, { useEffect, FC, useMemo } from 'react';
|
||||||
import useObservable from 'react-use/lib/useObservable';
|
import useObservable from 'react-use/lib/useObservable';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import {
|
||||||
|
mlTimefilterRefresh$,
|
||||||
|
useRefreshIntervalUpdates,
|
||||||
|
useTimefilter,
|
||||||
|
} from '@kbn/ml-date-picker';
|
||||||
import { NavigateToPath } from '../../contexts/kibana';
|
import { NavigateToPath } from '../../contexts/kibana';
|
||||||
import { DEFAULT_REFRESH_INTERVAL_MS } from '../../../../common/constants/jobs_list';
|
import { DEFAULT_REFRESH_INTERVAL_MS } from '../../../../common/constants/jobs_list';
|
||||||
import { mlTimefilterRefresh$ } from '../../services/timefilter_refresh_service';
|
|
||||||
import { MlRoute, PageLoader, PageProps } from '../router';
|
import { MlRoute, PageLoader, PageProps } from '../router';
|
||||||
import { useResolver } from '../use_resolver';
|
import { useResolver } from '../use_resolver';
|
||||||
import { basicResolvers } from '../resolvers';
|
import { basicResolvers } from '../resolvers';
|
||||||
import { JobsPage } from '../../jobs/jobs_list';
|
import { JobsPage } from '../../jobs/jobs_list';
|
||||||
import { useTimefilter } from '../../contexts/kibana';
|
|
||||||
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
|
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
|
||||||
import { AnnotationUpdatesService } from '../../services/annotations_service';
|
import { AnnotationUpdatesService } from '../../services/annotations_service';
|
||||||
import { MlAnnotationUpdatesContext } from '../../contexts/ml/ml_annotation_updates_context';
|
import { MlAnnotationUpdatesContext } from '../../contexts/ml/ml_annotation_updates_context';
|
||||||
import { useRefreshIntervalUpdates } from '../../contexts/kibana/use_timefilter';
|
|
||||||
|
|
||||||
export const jobListRouteFactory = (navigateToPath: NavigateToPath, basePath: string): MlRoute => ({
|
export const jobListRouteFactory = (navigateToPath: NavigateToPath, basePath: string): MlRoute => ({
|
||||||
id: 'anomaly_detection',
|
id: 'anomaly_detection',
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import React, { FC, Suspense } from 'react';
|
import React, { FC, Suspense } from 'react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||||
import { PageLoader, PageProps } from '../router';
|
import { PageLoader, PageProps } from '../router';
|
||||||
import { useResolver } from '../use_resolver';
|
import { useResolver } from '../use_resolver';
|
||||||
import { checkFullLicense } from '../../license';
|
import { checkFullLicense } from '../../license';
|
||||||
|
@ -15,7 +16,7 @@ import { getMlNodeCount } from '../../ml_nodes_check';
|
||||||
import { loadMlServerInfo } from '../../services/ml_server_info';
|
import { loadMlServerInfo } from '../../services/ml_server_info';
|
||||||
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
|
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
|
||||||
import type { MlRoute } from '..';
|
import type { MlRoute } from '..';
|
||||||
import { NavigateToPath, useTimefilter } from '../../contexts/kibana';
|
import { NavigateToPath } from '../../contexts/kibana';
|
||||||
|
|
||||||
const NotificationsPage = React.lazy(() => import('../../notifications/page'));
|
const NotificationsPage = React.lazy(() => import('../../notifications/page'));
|
||||||
|
|
||||||
|
|
|
@ -10,16 +10,16 @@ import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
import { Redirect } from 'react-router-dom';
|
import { Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||||
|
|
||||||
import type { NavigateToPath } from '../../contexts/kibana';
|
import type { NavigateToPath } from '../../contexts/kibana';
|
||||||
|
|
||||||
import { MlRoute, PageLoader, PageProps } from '../router';
|
|
||||||
import { useResolver } from '../use_resolver';
|
|
||||||
|
|
||||||
import { checkFullLicense } from '../../license';
|
import { checkFullLicense } from '../../license';
|
||||||
import { checkGetJobsCapabilitiesResolver } from '../../capabilities/check_capabilities';
|
import { checkGetJobsCapabilitiesResolver } from '../../capabilities/check_capabilities';
|
||||||
import { getMlNodeCount } from '../../ml_nodes_check';
|
import { getMlNodeCount } from '../../ml_nodes_check';
|
||||||
import { loadMlServerInfo } from '../../services/ml_server_info';
|
import { loadMlServerInfo } from '../../services/ml_server_info';
|
||||||
import { useTimefilter } from '../../contexts/kibana';
|
|
||||||
|
import { MlRoute, PageLoader, PageProps } from '../router';
|
||||||
|
import { useResolver } from '../use_resolver';
|
||||||
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
|
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
|
||||||
|
|
||||||
const OverviewPage = React.lazy(() => import('../../overview/overview_page'));
|
const OverviewPage = React.lazy(() => import('../../overview/overview_page'));
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
|
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { NavigateToPath, useTimefilter } from '../../../contexts/kibana';
|
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||||
|
import { NavigateToPath } from '../../../contexts/kibana';
|
||||||
import { MlRoute, PageLoader, PageProps } from '../../router';
|
import { MlRoute, PageLoader, PageProps } from '../../router';
|
||||||
import { useResolver } from '../../use_resolver';
|
import { useResolver } from '../../use_resolver';
|
||||||
import { checkFullLicense } from '../../../license';
|
import { checkFullLicense } from '../../../license';
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
|
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { NavigateToPath, useTimefilter } from '../../../contexts/kibana';
|
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||||
|
import { NavigateToPath } from '../../../contexts/kibana';
|
||||||
import { MlRoute, PageLoader, PageProps } from '../../router';
|
import { MlRoute, PageLoader, PageProps } from '../../router';
|
||||||
import { useResolver } from '../../use_resolver';
|
import { useResolver } from '../../use_resolver';
|
||||||
import { checkFullLicense } from '../../../license';
|
import { checkFullLicense } from '../../../license';
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue