mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ML] Consolidate redundant time_buckets
into @kbn/ml-time-buckets
. (#178756)
## Summary Follow up to #46227. Consolidates multiple copies of `time_buckets.js` into `@kbn/ml-time-buckets`. The scope of this PR is just to consolidate the files. In follow ups we still need to: Refactor JS to TS and get rid of the code that uses this using "dependency cache" in the `ml` plugin. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
parent
495d7b5b68
commit
be9ad681ba
67 changed files with 358 additions and 1311 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -564,6 +564,7 @@ x-pack/packages/ml/response_stream @elastic/ml-ui
|
|||
x-pack/packages/ml/route_utils @elastic/ml-ui
|
||||
x-pack/packages/ml/runtime_field_utils @elastic/ml-ui
|
||||
x-pack/packages/ml/string_hash @elastic/ml-ui
|
||||
x-pack/packages/ml/time_buckets @elastic/ml-ui
|
||||
x-pack/packages/ml/trained_models_utils @elastic/ml-ui
|
||||
x-pack/packages/ml/ui_actions @elastic/ml-ui
|
||||
x-pack/packages/ml/url_state @elastic/ml-ui
|
||||
|
|
|
@ -585,6 +585,7 @@
|
|||
"@kbn/ml-route-utils": "link:x-pack/packages/ml/route_utils",
|
||||
"@kbn/ml-runtime-field-utils": "link:x-pack/packages/ml/runtime_field_utils",
|
||||
"@kbn/ml-string-hash": "link:x-pack/packages/ml/string_hash",
|
||||
"@kbn/ml-time-buckets": "link:x-pack/packages/ml/time_buckets",
|
||||
"@kbn/ml-trained-models-utils": "link:x-pack/packages/ml/trained_models_utils",
|
||||
"@kbn/ml-ui-actions": "link:x-pack/packages/ml/ui_actions",
|
||||
"@kbn/ml-url-state": "link:x-pack/packages/ml/url_state",
|
||||
|
|
|
@ -1122,6 +1122,8 @@
|
|||
"@kbn/ml-runtime-field-utils/*": ["x-pack/packages/ml/runtime_field_utils/*"],
|
||||
"@kbn/ml-string-hash": ["x-pack/packages/ml/string_hash"],
|
||||
"@kbn/ml-string-hash/*": ["x-pack/packages/ml/string_hash/*"],
|
||||
"@kbn/ml-time-buckets": ["x-pack/packages/ml/time_buckets"],
|
||||
"@kbn/ml-time-buckets/*": ["x-pack/packages/ml/time_buckets/*"],
|
||||
"@kbn/ml-trained-models-utils": ["x-pack/packages/ml/trained_models_utils"],
|
||||
"@kbn/ml-trained-models-utils/*": ["x-pack/packages/ml/trained_models_utils/*"],
|
||||
"@kbn/ml-ui-actions": ["x-pack/packages/ml/ui_actions"],
|
||||
|
|
7
x-pack/packages/ml/time_buckets/README.md
Normal file
7
x-pack/packages/ml/time_buckets/README.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# @kbn/ml-time-buckets
|
||||
|
||||
`TimeBuckets` is a helper class for wrapping the concept of an "Interval", which describes a timespan that will separate buckets of time, for example the interval between points on a time series chart.
|
||||
|
||||
Back in 2019 for Kibana New Platform it was decided that the original `TimeBuckets` would not longer be exposed from Kibana itself, it was therefor copied over to the `ml` plugin (see https://github.com/elastic/kibana/issues/44249). Over time, before we had the package system, several copies of this class spread over more plugins. All these usage are now consolidated into this package.
|
||||
|
||||
In the meantime, the original `TimeBuckets` class has been reworked and migrated to TS (https://github.com/elastic/kibana/issues/60130). In contrast to the original idea, it is again available as an export from Kibana's `data` plugin. Because of this we might want to look into using the original `TimeBuckets` again if it solves our use cases.
|
10
x-pack/packages/ml/time_buckets/index.ts
Normal file
10
x-pack/packages/ml/time_buckets/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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 type { TimeBucketsConfig, TimeBucketsInterval, TimeRangeBounds } from './time_buckets';
|
||||
export { getBoundsRoundedToInterval, TimeBuckets } from './time_buckets';
|
||||
export { useTimeBuckets } from './use_time_buckets';
|
12
x-pack/packages/ml/time_buckets/jest.config.js
Normal file
12
x-pack/packages/ml/time_buckets/jest.config.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../../..',
|
||||
roots: ['<rootDir>/x-pack/packages/ml/time_buckets'],
|
||||
};
|
5
x-pack/packages/ml/time_buckets/kibana.jsonc
Normal file
5
x-pack/packages/ml/time_buckets/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/ml-time-buckets",
|
||||
"owner": "@elastic/ml-ui"
|
||||
}
|
6
x-pack/packages/ml/time_buckets/package.json
Normal file
6
x-pack/packages/ml/time_buckets/package.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/ml-time-buckets",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "Elastic License 2.0"
|
||||
}
|
135
x-pack/packages/ml/time_buckets/time_buckets.d.ts
vendored
Normal file
135
x-pack/packages/ml/time_buckets/time_buckets.d.ts
vendored
Normal file
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* 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 type { Moment } from 'moment';
|
||||
|
||||
/**
|
||||
* Represents the minimum and maximum time bounds for a time range.
|
||||
*/
|
||||
export interface TimeRangeBounds {
|
||||
/**
|
||||
* The minimum bound of the time range (optional).
|
||||
*/
|
||||
min?: Moment;
|
||||
/**
|
||||
* The maximum bound of the time range (optional).
|
||||
*/
|
||||
max?: Moment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the structure for time intervals used within TimeBuckets.
|
||||
*/
|
||||
export declare interface TimeBucketsInterval {
|
||||
/**
|
||||
* Returns the interval in milliseconds.
|
||||
*/
|
||||
asMilliseconds: () => number;
|
||||
/**
|
||||
* Returns the interval in seconds.
|
||||
*/
|
||||
asSeconds: () => number;
|
||||
/**
|
||||
* The string expression representing the interval.
|
||||
*/
|
||||
expression: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration options for initializing TimeBuckets.
|
||||
*/
|
||||
export interface TimeBucketsConfig {
|
||||
/**
|
||||
* The maximum number of bars to display on the histogram.
|
||||
*/
|
||||
'histogram:maxBars': number;
|
||||
/**
|
||||
* The targeted number of bars for the histogram.
|
||||
*/
|
||||
'histogram:barTarget': number;
|
||||
/**
|
||||
* The date format string.
|
||||
*/
|
||||
dateFormat: string;
|
||||
/**
|
||||
* The scaled date format strings.
|
||||
*/
|
||||
'dateFormat:scaled': string[][];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a configurable utility class for working with time buckets.
|
||||
*/
|
||||
export declare class TimeBuckets {
|
||||
/**
|
||||
* Creates an instance of TimeBuckets.
|
||||
* @param timeBucketsConfig - Configuration for the TimeBuckets instance.
|
||||
*/
|
||||
constructor(timeBucketsConfig: TimeBucketsConfig);
|
||||
|
||||
/**
|
||||
* Sets the target number of bars for the histogram.
|
||||
* @param barTarget - The target bar count.
|
||||
*/
|
||||
public setBarTarget(barTarget: number): void;
|
||||
|
||||
/**
|
||||
* Sets the maximum number of bars for the histogram.
|
||||
* @param maxBars - The maximum bar count.
|
||||
*/
|
||||
public setMaxBars(maxBars: number): void;
|
||||
|
||||
/**
|
||||
* Sets the interval for the time buckets.
|
||||
* @param interval - The interval expression, e.g., "1h" for one hour.
|
||||
*/
|
||||
public setInterval(interval: string): void;
|
||||
|
||||
/**
|
||||
* Sets the bounds of the time range for the buckets.
|
||||
* @param bounds - The minimum and maximum time bounds.
|
||||
*/
|
||||
public setBounds(bounds: TimeRangeBounds): void;
|
||||
|
||||
/**
|
||||
* Gets the current bounds of the time range.
|
||||
* @returns The current time range bounds.
|
||||
*/
|
||||
public getBounds(): { min: Moment; max: Moment };
|
||||
|
||||
/**
|
||||
* Retrieves the configured interval for the buckets.
|
||||
* @returns The current interval settings for the buckets.
|
||||
*/
|
||||
public getInterval(): TimeBucketsInterval;
|
||||
|
||||
/**
|
||||
* Calculates the nearest interval that is a multiple of a specified divisor.
|
||||
* @param divisorSecs - The divisor in seconds.
|
||||
* @returns The nearest interval as a multiple of the divisor.
|
||||
*/
|
||||
public getIntervalToNearestMultiple(divisorSecs: number): TimeBucketsInterval;
|
||||
|
||||
/**
|
||||
* Retrieves the date format that should be used for scaled intervals.
|
||||
* @returns The scaled date format string.
|
||||
*/
|
||||
public getScaledDateFormat(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the given time range bounds to align with the specified interval.
|
||||
* @param bounds The current time range bounds.
|
||||
* @param interval The interval to align the time range bounds with.
|
||||
* @param inclusiveEnd Whether the end of the range should be inclusive.
|
||||
* @returns The adjusted time range bounds.
|
||||
*/
|
||||
export declare function getBoundsRoundedToInterval(
|
||||
bounds: TimeRangeBounds,
|
||||
interval: TimeBucketsInterval,
|
||||
inclusiveEnd?: boolean
|
||||
): Required<TimeRangeBounds>;
|
|
@ -5,15 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { isPlainObject, isString, ary, sortBy, assign } from 'lodash';
|
||||
import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
|
||||
import { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||
import { ary, assign, isPlainObject, isString, sortBy } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import dateMath from '@kbn/datemath';
|
||||
|
||||
import { parseInterval } from './parse_interval';
|
||||
import { timeBucketsCalcAutoIntervalProvider } from './calc_auto_interval';
|
||||
import { parseInterval } from '../../../common/util/parse_interval';
|
||||
import { getFieldFormats, getUiSettings } from './dependency_cache';
|
||||
import { UI_SETTINGS } from '@kbn/data-plugin/public';
|
||||
import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
|
||||
|
||||
const unitsDesc = dateMath.unitsDesc;
|
||||
|
||||
|
@ -24,23 +22,14 @@ const timeUnitsMaxSupportedIndex = unitsDesc.indexOf('w');
|
|||
|
||||
const calcAuto = timeBucketsCalcAutoIntervalProvider();
|
||||
|
||||
export function getTimeBucketsFromCache() {
|
||||
const uiSettings = getUiSettings();
|
||||
return new TimeBuckets({
|
||||
[UI_SETTINGS.HISTOGRAM_MAX_BARS]: uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS),
|
||||
[UI_SETTINGS.HISTOGRAM_BAR_TARGET]: uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
|
||||
dateFormat: uiSettings.get('dateFormat'),
|
||||
'dateFormat:scaled': uiSettings.get('dateFormat:scaled'),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper object for wrapping the concept of an "Interval", which
|
||||
* describes a timespan that will separate buckets of time,
|
||||
* for example the interval between points on a time series chart.
|
||||
*/
|
||||
export function TimeBuckets(timeBucketsConfig) {
|
||||
export function TimeBuckets(timeBucketsConfig, fieldFormats) {
|
||||
this._timeBucketsConfig = timeBucketsConfig;
|
||||
this._fieldFormats = fieldFormats;
|
||||
this.barTarget = this._timeBucketsConfig[UI_SETTINGS.HISTOGRAM_BAR_TARGET];
|
||||
this.maxBars = this._timeBucketsConfig[UI_SETTINGS.HISTOGRAM_MAX_BARS];
|
||||
}
|
||||
|
@ -299,6 +288,39 @@ TimeBuckets.prototype.getIntervalToNearestMultiple = function (divisorSecs) {
|
|||
return nearestMultipleInt;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an interval which in the last step of calculation is rounded to
|
||||
* the closest multiple of the supplied divisor (in seconds).
|
||||
*
|
||||
* @return {moment.duration|undefined}
|
||||
*/
|
||||
TimeBuckets.prototype.getIntervalToNearestMultiple = function (divisorSecs) {
|
||||
const interval = this.getInterval();
|
||||
const intervalSecs = interval.asSeconds();
|
||||
|
||||
const remainder = intervalSecs % divisorSecs;
|
||||
if (remainder === 0) {
|
||||
return interval;
|
||||
}
|
||||
|
||||
// Create a new interval which is a multiple of the supplied divisor (not zero).
|
||||
let nearestMultiple =
|
||||
remainder > divisorSecs / 2 ? intervalSecs + divisorSecs - remainder : intervalSecs - remainder;
|
||||
nearestMultiple = nearestMultiple === 0 ? divisorSecs : nearestMultiple;
|
||||
const nearestMultipleInt = moment.duration(nearestMultiple, 'seconds');
|
||||
decorateInterval(nearestMultipleInt, this.getDuration());
|
||||
|
||||
// Check to see if the new interval is scaled compared to the original.
|
||||
const preScaled = interval.preScaled;
|
||||
if (preScaled !== undefined && preScaled < nearestMultipleInt) {
|
||||
nearestMultipleInt.preScaled = preScaled;
|
||||
nearestMultipleInt.scale = preScaled / nearestMultipleInt;
|
||||
nearestMultipleInt.scaled = true;
|
||||
}
|
||||
|
||||
return nearestMultipleInt;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a date format string that will represent dates that
|
||||
* progress at our interval.
|
||||
|
@ -325,7 +347,7 @@ TimeBuckets.prototype.getScaledDateFormat = function () {
|
|||
};
|
||||
|
||||
TimeBuckets.prototype.getScaledDateFormatter = function () {
|
||||
const fieldFormats = getFieldFormats();
|
||||
const fieldFormats = this._fieldFormats;
|
||||
const DateFieldFormat = fieldFormats.getType(FIELD_FORMAT_IDS.DATE);
|
||||
return new DateFieldFormat(
|
||||
{
|
||||
|
@ -377,10 +399,9 @@ export function getBoundsRoundedToInterval(bounds, interval, inclusiveEnd = fals
|
|||
return { min: moment(adjustedMinMs), max: moment(adjustedMaxMs) };
|
||||
}
|
||||
|
||||
// Converts a moment.duration into an Elasticsearch compatible interval expression,
|
||||
// and provides associated metadata.
|
||||
export function calcEsInterval(duration) {
|
||||
// Converts a moment.duration into an Elasticsearch compatible interval expression,
|
||||
// and provides associated metadata.
|
||||
|
||||
// Note this was a copy of Kibana's original ui/time_buckets/calc_es_interval,
|
||||
// but with the definition of a 'large' unit changed from 'M' to 'w',
|
||||
// bringing it into line with the time units supported by Elasticsearch
|
||||
|
@ -399,7 +420,7 @@ export function calcEsInterval(duration) {
|
|||
|
||||
return {
|
||||
value: val,
|
||||
unit: unit,
|
||||
unit,
|
||||
expression: val + unit,
|
||||
};
|
||||
}
|
23
x-pack/packages/ml/time_buckets/tsconfig.json
Normal file
23
x-pack/packages/ml/time_buckets/tsconfig.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
"react"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/datemath",
|
||||
"@kbn/data-plugin",
|
||||
"@kbn/core-ui-settings-browser",
|
||||
]
|
||||
}
|
|
@ -7,12 +7,17 @@
|
|||
|
||||
import { useMemo } from 'react';
|
||||
import { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||
import { TimeBuckets } from '../../common/time_buckets';
|
||||
import { useAiopsAppContext } from './use_aiops_app_context';
|
||||
import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
|
||||
|
||||
export const useTimeBuckets = () => {
|
||||
const { uiSettings } = useAiopsAppContext();
|
||||
import { TimeBuckets } from './time_buckets';
|
||||
|
||||
/**
|
||||
* Custom hook to get `TimeBuckets` configured with settings from the `IUiSettingsClient`.
|
||||
*
|
||||
* @param uiSettings The UI settings client instance used to retrieve UI settings.
|
||||
* @returns A memoized `TimeBuckets` instance configured with relevant UI settings.
|
||||
*/
|
||||
export const useTimeBuckets = (uiSettings: IUiSettingsClient) => {
|
||||
return useMemo(() => {
|
||||
return new TimeBuckets({
|
||||
[UI_SETTINGS.HISTOGRAM_MAX_BARS]: uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS),
|
45
x-pack/plugins/aiops/common/time_buckets.d.ts
vendored
45
x-pack/plugins/aiops/common/time_buckets.d.ts
vendored
|
@ -1,45 +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 type { Moment } from 'moment';
|
||||
|
||||
export interface TimeRangeBounds {
|
||||
min?: Moment;
|
||||
max?: Moment;
|
||||
}
|
||||
|
||||
export declare interface TimeBucketsInterval {
|
||||
asMilliseconds: () => number;
|
||||
asSeconds: () => number;
|
||||
expression: string;
|
||||
}
|
||||
|
||||
export interface TimeBucketsConfig {
|
||||
'histogram:maxBars': number;
|
||||
'histogram:barTarget': number;
|
||||
dateFormat: string;
|
||||
'dateFormat:scaled': string[][];
|
||||
}
|
||||
|
||||
export declare class TimeBuckets {
|
||||
constructor(timeBucketsConfig: TimeBucketsConfig);
|
||||
public setBarTarget(barTarget: number): void;
|
||||
public setMaxBars(maxBars: number): void;
|
||||
public setInterval(interval: string): void;
|
||||
public setBounds(bounds: TimeRangeBounds): void;
|
||||
public getBounds(): { min: Moment; max: Moment };
|
||||
public getInterval(): TimeBucketsInterval;
|
||||
public getScaledDateFormat(): string;
|
||||
}
|
||||
|
||||
export declare function getTimeBucketsFromCache(): InstanceType<typeof TimeBuckets>;
|
||||
|
||||
export declare function getBoundsRoundedToInterval(
|
||||
bounds: TimeRangeBounds,
|
||||
interval: TimeBucketsInterval,
|
||||
inclusiveEnd?: boolean
|
||||
): Required<TimeRangeBounds>;
|
|
@ -1,516 +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 { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
|
||||
import { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||
import { ary, assign, isPlainObject, isString, sortBy } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import dateMath from '@kbn/datemath';
|
||||
import { parseInterval } from './parse_interval';
|
||||
|
||||
const { duration: d } = moment;
|
||||
|
||||
export function timeBucketsCalcAutoIntervalProvider() {
|
||||
// Note there is a current issue with Kibana (Kibana issue #9184)
|
||||
// which means we can't round to, for example, 2 week or 3 week buckets,
|
||||
// so there is a large gap between the 1 week and 1 month rule.
|
||||
const roundingRules = [
|
||||
[d(500, 'ms'), d(100, 'ms')],
|
||||
[d(5, 'second'), d(1, 'second')],
|
||||
[d(10, 'second'), d(5, 'second')],
|
||||
[d(15, 'second'), d(10, 'second')],
|
||||
[d(30, 'second'), d(15, 'second')],
|
||||
[d(1, 'minute'), d(30, 'second')],
|
||||
[d(5, 'minute'), d(1, 'minute')],
|
||||
[d(10, 'minute'), d(5, 'minute')],
|
||||
[d(15, 'minute'), d(10, 'minute')],
|
||||
[d(30, 'minute'), d(10, 'minute')],
|
||||
[d(1, 'hour'), d(30, 'minute')],
|
||||
[d(2, 'hour'), d(1, 'hour')],
|
||||
[d(4, 'hour'), d(2, 'hour')],
|
||||
[d(6, 'hour'), d(4, 'hour')],
|
||||
[d(8, 'hour'), d(6, 'hour')],
|
||||
[d(12, 'hour'), d(8, 'hour')],
|
||||
[d(24, 'hour'), d(12, 'hour')],
|
||||
[d(2, 'd'), d(1, 'd')],
|
||||
[d(4, 'd'), d(2, 'd')],
|
||||
[d(1, 'week'), d(4, 'd')],
|
||||
// [ d(2, 'week'), d(1, 'week') ],
|
||||
// [ d(1, 'month'), d(2, 'week') ],
|
||||
[d(1, 'month'), d(1, 'week')],
|
||||
[d(1, 'year'), d(1, 'month')],
|
||||
[Infinity, d(1, 'year')],
|
||||
];
|
||||
|
||||
const revRoundingRules = roundingRules.slice(0).reverse();
|
||||
|
||||
function find(rules, check, last) {
|
||||
function pick(buckets, duration) {
|
||||
const target = duration / buckets;
|
||||
let lastResp;
|
||||
|
||||
for (let i = 0; i < rules.length; i++) {
|
||||
const rule = rules[i];
|
||||
const resp = check(rule[0], rule[1], target);
|
||||
|
||||
if (resp == null) {
|
||||
if (!last) {
|
||||
continue;
|
||||
}
|
||||
if (lastResp) {
|
||||
return lastResp;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!last) {
|
||||
return resp;
|
||||
}
|
||||
lastResp = resp;
|
||||
}
|
||||
|
||||
// fallback to just a number of milliseconds, ensure ms is >= 1
|
||||
const ms = Math.max(Math.floor(target), 1);
|
||||
return moment.duration(ms, 'ms');
|
||||
}
|
||||
|
||||
return function (buckets, duration) {
|
||||
const interval = pick(buckets, duration);
|
||||
if (interval) {
|
||||
return moment.duration(interval._data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
near: find(
|
||||
revRoundingRules,
|
||||
function near(upperBound, lowerBound, target) {
|
||||
// upperBound - first duration in rule
|
||||
// lowerBound - second duration in rule
|
||||
// target - target interval in milliseconds.
|
||||
if (upperBound > target) {
|
||||
if (upperBound === Infinity) {
|
||||
return lowerBound;
|
||||
}
|
||||
|
||||
const boundMs = upperBound.asMilliseconds();
|
||||
const intervalMs = lowerBound.asMilliseconds();
|
||||
const retInterval =
|
||||
Math.abs(boundMs - target) <= Math.abs(intervalMs) ? upperBound : lowerBound;
|
||||
return retInterval;
|
||||
}
|
||||
},
|
||||
true
|
||||
),
|
||||
|
||||
lessThan: find(revRoundingRules, function (upperBound, lowerBound, target) {
|
||||
// upperBound - first duration in rule
|
||||
// lowerBound - second duration in rule
|
||||
// target - target interval in milliseconds. Must not return intervals less than this duration.
|
||||
if (lowerBound < target) {
|
||||
return upperBound !== Infinity ? upperBound : lowerBound;
|
||||
}
|
||||
}),
|
||||
|
||||
atLeast: find(revRoundingRules, function atLeast(upperBound, lowerBound, target) {
|
||||
// Unmodified from Kibana ui/time_buckets/calc_auto_interval.js.
|
||||
if (lowerBound <= target) {
|
||||
return lowerBound;
|
||||
}
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const unitsDesc = dateMath.unitsDesc;
|
||||
|
||||
// Index of the list of time interval units at which larger units (i.e. weeks, months, years) need
|
||||
// need to be converted to multiples of the largest unit supported in ES aggregation intervals (i.e. days).
|
||||
// Note that similarly the largest interval supported for ML bucket spans is 'd'.
|
||||
const timeUnitsMaxSupportedIndex = unitsDesc.indexOf('w');
|
||||
|
||||
const calcAuto = timeBucketsCalcAutoIntervalProvider();
|
||||
|
||||
/**
|
||||
* Helper object for wrapping the concept of an "Interval", which
|
||||
* describes a timespan that will separate buckets of time,
|
||||
* for example the interval between points on a time series chart.
|
||||
*/
|
||||
export function TimeBuckets(timeBucketsConfig, fieldFormats) {
|
||||
this._timeBucketsConfig = timeBucketsConfig;
|
||||
this._fieldFormats = fieldFormats;
|
||||
this.barTarget = this._timeBucketsConfig[UI_SETTINGS.HISTOGRAM_BAR_TARGET];
|
||||
this.maxBars = this._timeBucketsConfig[UI_SETTINGS.HISTOGRAM_MAX_BARS];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the target number of bars.
|
||||
*
|
||||
* @param {number} bt - target number of bars (buckets).
|
||||
*
|
||||
* @returns {undefined}
|
||||
*/
|
||||
TimeBuckets.prototype.setBarTarget = function (bt) {
|
||||
this.barTarget = bt;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the maximum number of bars.
|
||||
*
|
||||
* @param {number} mb - maximum number of bars (buckets).
|
||||
*
|
||||
* @returns {undefined}
|
||||
*/
|
||||
TimeBuckets.prototype.setMaxBars = function (mb) {
|
||||
this.maxBars = mb;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the bounds that these buckets are expected to cover.
|
||||
* This is required to support interval "auto" as well
|
||||
* as interval scaling.
|
||||
*
|
||||
* @param {object} input - an object with properties min and max,
|
||||
* representing the edges for the time span
|
||||
* we should cover
|
||||
*
|
||||
* @returns {undefined}
|
||||
*/
|
||||
TimeBuckets.prototype.setBounds = function (input) {
|
||||
if (!input) return this.clearBounds();
|
||||
|
||||
let bounds;
|
||||
if (isPlainObject(input)) {
|
||||
// accept the response from timefilter.getActiveBounds()
|
||||
bounds = [input.min, input.max];
|
||||
} else {
|
||||
bounds = Array.isArray(input) ? input : [];
|
||||
}
|
||||
|
||||
const moments = sortBy(bounds.map(ary(moment, 1)), Number);
|
||||
|
||||
const valid = moments.length === 2 && moments.every(isValidMoment);
|
||||
if (!valid) {
|
||||
this.clearBounds();
|
||||
throw new Error('invalid bounds set: ' + input);
|
||||
}
|
||||
|
||||
this._lb = moments.shift();
|
||||
this._ub = moments.pop();
|
||||
if (this.getDuration().asSeconds() < 0) {
|
||||
throw new TypeError('Intervals must be positive');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the stored bounds
|
||||
*
|
||||
* @return {undefined}
|
||||
*/
|
||||
TimeBuckets.prototype.clearBounds = function () {
|
||||
this._lb = this._ub = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check to see if we have received bounds yet
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
TimeBuckets.prototype.hasBounds = function () {
|
||||
return isValidMoment(this._ub) && isValidMoment(this._lb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the current bounds, if we have any.
|
||||
*
|
||||
* Note that this does not clone the bounds, so editing them may have unexpected side-effects.
|
||||
* Always call bounds.min.clone() before editing.
|
||||
*
|
||||
* @return {object|undefined} - If bounds are not defined, this
|
||||
* returns undefined, else it returns the bounds
|
||||
* for these buckets. This object has two props,
|
||||
* min and max. Each property will be a moment()
|
||||
* object
|
||||
*/
|
||||
TimeBuckets.prototype.getBounds = function () {
|
||||
if (!this.hasBounds()) return;
|
||||
return {
|
||||
min: this._lb,
|
||||
max: this._ub,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a moment duration object representing
|
||||
* the distance between the bounds, if the bounds
|
||||
* are set.
|
||||
*
|
||||
* @return {moment.duration|undefined}
|
||||
*/
|
||||
TimeBuckets.prototype.getDuration = function () {
|
||||
if (!this.hasBounds()) return;
|
||||
return moment.duration(this._ub - this._lb, 'ms');
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the interval at which buckets should be
|
||||
* generated.
|
||||
*
|
||||
* Input can be one of the following:
|
||||
* - "auto"
|
||||
* - an interval String, such as 7d, 1h or 30m which can be parsed to a moment duration using ml/common/util/parse_interval
|
||||
* - a moment.duration object.
|
||||
*
|
||||
* @param {string|moment.duration} input - see desc
|
||||
*/
|
||||
TimeBuckets.prototype.setInterval = function (input) {
|
||||
// Preserve the original units because they're lost when the interval is converted to a
|
||||
// moment duration object.
|
||||
this.originalInterval = input;
|
||||
|
||||
let interval = input;
|
||||
|
||||
if (!interval || interval === 'auto') {
|
||||
this._i = 'auto';
|
||||
return;
|
||||
}
|
||||
|
||||
if (isString(interval)) {
|
||||
input = interval;
|
||||
interval = parseInterval(interval);
|
||||
if (+interval === 0) {
|
||||
interval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// If the value wasn't converted to a duration, and isn't already a duration, we have a problem
|
||||
if (!moment.isDuration(interval)) {
|
||||
throw new TypeError('"' + input + '" is not a valid interval.');
|
||||
}
|
||||
|
||||
this._i = interval;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the interval for the buckets. If the
|
||||
* number of buckets created by the interval set
|
||||
* is larger than config:histogram:maxBars then the
|
||||
* interval will be scaled up. If the number of buckets
|
||||
* created is less than one, the interval is scaled back.
|
||||
*
|
||||
* The interval object returned is a moment.duration
|
||||
* object that has been decorated with the following
|
||||
* properties.
|
||||
*
|
||||
* interval.description: a text description of the interval.
|
||||
* designed to be used list "field per {{ desc }}".
|
||||
* - "minute"
|
||||
* - "10 days"
|
||||
* - "3 years"
|
||||
*
|
||||
* interval.expr: the elasticsearch expression that creates this
|
||||
* interval. If the interval does not properly form an elasticsearch
|
||||
* expression it will be forced into one.
|
||||
*
|
||||
* interval.scaled: the interval was adjusted to
|
||||
* accommodate the maxBars setting.
|
||||
*
|
||||
* interval.scale: the number that y-values should be
|
||||
* multiplied by
|
||||
*
|
||||
* interval.scaleDescription: a description that reflects
|
||||
* the values which will be produced by using the
|
||||
* interval.scale.
|
||||
*
|
||||
*
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
TimeBuckets.prototype.getInterval = function () {
|
||||
const self = this;
|
||||
const duration = self.getDuration();
|
||||
return decorateInterval(maybeScaleInterval(readInterval()), duration);
|
||||
|
||||
// either pull the interval from state or calculate the auto-interval
|
||||
function readInterval() {
|
||||
const interval = self._i;
|
||||
if (moment.isDuration(interval)) return interval;
|
||||
return calcAuto.near(self.barTarget, duration);
|
||||
}
|
||||
|
||||
// check to see if the interval should be scaled, and scale it if so
|
||||
function maybeScaleInterval(interval) {
|
||||
if (!self.hasBounds()) return interval;
|
||||
|
||||
const maxLength = self.maxBars;
|
||||
const approxLen = duration / interval;
|
||||
let scaled;
|
||||
|
||||
// If the number of buckets we got back from using the barTarget is less than
|
||||
// maxBars, than use the lessThan rule to try and get closer to maxBars.
|
||||
if (approxLen > maxLength) {
|
||||
scaled = calcAuto.lessThan(maxLength, duration);
|
||||
} else {
|
||||
return interval;
|
||||
}
|
||||
|
||||
if (+scaled === +interval) return interval;
|
||||
|
||||
decorateInterval(interval, duration);
|
||||
return assign(scaled, {
|
||||
preScaled: interval,
|
||||
scale: interval / scaled,
|
||||
scaled: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an interval which in the last step of calculation is rounded to
|
||||
* the closest multiple of the supplied divisor (in seconds).
|
||||
*
|
||||
* @return {moment.duration|undefined}
|
||||
*/
|
||||
TimeBuckets.prototype.getIntervalToNearestMultiple = function (divisorSecs) {
|
||||
const interval = this.getInterval();
|
||||
const intervalSecs = interval.asSeconds();
|
||||
|
||||
const remainder = intervalSecs % divisorSecs;
|
||||
if (remainder === 0) {
|
||||
return interval;
|
||||
}
|
||||
|
||||
// Create a new interval which is a multiple of the supplied divisor (not zero).
|
||||
let nearestMultiple =
|
||||
remainder > divisorSecs / 2 ? intervalSecs + divisorSecs - remainder : intervalSecs - remainder;
|
||||
nearestMultiple = nearestMultiple === 0 ? divisorSecs : nearestMultiple;
|
||||
const nearestMultipleInt = moment.duration(nearestMultiple, 'seconds');
|
||||
decorateInterval(nearestMultipleInt, this.getDuration());
|
||||
|
||||
// Check to see if the new interval is scaled compared to the original.
|
||||
const preScaled = interval.preScaled;
|
||||
if (preScaled !== undefined && preScaled < nearestMultipleInt) {
|
||||
nearestMultipleInt.preScaled = preScaled;
|
||||
nearestMultipleInt.scale = preScaled / nearestMultipleInt;
|
||||
nearestMultipleInt.scaled = true;
|
||||
}
|
||||
|
||||
return nearestMultipleInt;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a date format string that will represent dates that
|
||||
* progress at our interval.
|
||||
*
|
||||
* Since our interval can be as small as 1ms, the default
|
||||
* date format is usually way too much. with `dateFormat:scaled`
|
||||
* users can modify how dates are formatted within series
|
||||
* produced by TimeBuckets
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
TimeBuckets.prototype.getScaledDateFormat = function () {
|
||||
const interval = this.getInterval();
|
||||
const rules = this._timeBucketsConfig['dateFormat:scaled'];
|
||||
|
||||
for (let i = rules.length - 1; i >= 0; i--) {
|
||||
const rule = rules[i];
|
||||
if (!rule[0] || interval >= moment.duration(rule[0])) {
|
||||
return rule[1];
|
||||
}
|
||||
}
|
||||
|
||||
return this._timeBucketsConfig.dateFormat;
|
||||
};
|
||||
|
||||
TimeBuckets.prototype.getScaledDateFormatter = function () {
|
||||
const fieldFormats = this._fieldFormats;
|
||||
const DateFieldFormat = fieldFormats.getType(FIELD_FORMAT_IDS.DATE);
|
||||
return new DateFieldFormat(
|
||||
{
|
||||
pattern: this.getScaledDateFormat(),
|
||||
},
|
||||
// getConfig
|
||||
this._timeBucketsConfig
|
||||
);
|
||||
};
|
||||
|
||||
// Appends some TimeBuckets specific properties to the moment.js duration interval.
|
||||
// Uses the originalDuration from which the time bucket was created to calculate the overflow
|
||||
// property (i.e. difference between the supplied duration and the calculated bucket interval).
|
||||
function decorateInterval(interval, originalDuration) {
|
||||
const esInterval = calcEsInterval(interval);
|
||||
interval.esValue = esInterval.value;
|
||||
interval.esUnit = esInterval.unit;
|
||||
interval.expression = esInterval.expression;
|
||||
interval.overflow =
|
||||
originalDuration > interval ? moment.duration(interval - originalDuration) : false;
|
||||
|
||||
const prettyUnits = moment.normalizeUnits(esInterval.unit);
|
||||
if (esInterval.value === 1) {
|
||||
interval.description = prettyUnits;
|
||||
} else {
|
||||
interval.description = `${esInterval.value} ${prettyUnits}s`;
|
||||
}
|
||||
|
||||
return interval;
|
||||
}
|
||||
|
||||
function isValidMoment(m) {
|
||||
return m && 'isValid' in m && m.isValid();
|
||||
}
|
||||
|
||||
export function getBoundsRoundedToInterval(bounds, interval, inclusiveEnd = false) {
|
||||
// Returns new bounds, created by flooring the min of the provided bounds to the start of
|
||||
// the specified interval (a moment duration), and rounded upwards (Math.ceil) to 1ms before
|
||||
// the start of the next interval (Kibana dashboards search >= bounds min, and <= bounds max,
|
||||
// so we subtract 1ms off the max to avoid querying start of the new Elasticsearch aggregation bucket).
|
||||
const intervalMs = interval.asMilliseconds();
|
||||
const adjustedMinMs = Math.floor(bounds.min.valueOf() / intervalMs) * intervalMs;
|
||||
let adjustedMaxMs = Math.ceil(bounds.max.valueOf() / intervalMs) * intervalMs;
|
||||
|
||||
// Don't include the start ms of the next bucket unless specified..
|
||||
if (inclusiveEnd === false) {
|
||||
adjustedMaxMs = adjustedMaxMs - 1;
|
||||
}
|
||||
return { min: moment(adjustedMinMs), max: moment(adjustedMaxMs) };
|
||||
}
|
||||
|
||||
export function calcEsInterval(duration) {
|
||||
// Converts a moment.duration into an Elasticsearch compatible interval expression,
|
||||
// and provides associated metadata.
|
||||
|
||||
// Note this was a copy of Kibana's original ui/time_buckets/calc_es_interval,
|
||||
// but with the definition of a 'large' unit changed from 'M' to 'w',
|
||||
// bringing it into line with the time units supported by Elasticsearch
|
||||
for (let i = 0; i < unitsDesc.length; i++) {
|
||||
const unit = unitsDesc[i];
|
||||
const val = duration.as(unit);
|
||||
// find a unit that rounds neatly
|
||||
if (val >= 1 && Math.floor(val) === val) {
|
||||
// Apart from for date histograms, ES only supports time units up to 'd',
|
||||
// meaning we can't for example use 'w' for job bucket spans.
|
||||
// See https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#time-units
|
||||
// So keep going until we get out of the "large" units.
|
||||
if (i <= timeUnitsMaxSupportedIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return {
|
||||
value: val,
|
||||
unit,
|
||||
expression: val + unit,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const ms = duration.as('ms');
|
||||
return {
|
||||
value: ms,
|
||||
unit: 'ms',
|
||||
expression: ms + 'ms',
|
||||
};
|
||||
}
|
|
@ -14,6 +14,8 @@ import { usePageUrlState } from '@kbn/ml-url-state';
|
|||
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||
import { ES_FIELD_TYPES } from '@kbn/field-types';
|
||||
import { type QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types';
|
||||
import type { TimeBuckets, TimeBucketsInterval } from '@kbn/ml-time-buckets';
|
||||
import { useTimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import { useFilterQueryUpdates } from '../../hooks/use_filters_query';
|
||||
import { type ChangePointType, DEFAULT_AGG_FUNCTION } from './constants';
|
||||
import {
|
||||
|
@ -21,10 +23,7 @@ import {
|
|||
getEsQueryFromSavedSearch,
|
||||
} from '../../application/utils/search_utils';
|
||||
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
||||
import type { TimeBucketsInterval } from '../../../common/time_buckets';
|
||||
import { type TimeBuckets } from '../../../common/time_buckets';
|
||||
import { useDataSource } from '../../hooks/use_data_source';
|
||||
import { useTimeBuckets } from '../../hooks/use_time_buckets';
|
||||
|
||||
export interface ChangePointDetectionPageUrlState {
|
||||
pageKey: 'changePoint';
|
||||
|
@ -147,7 +146,7 @@ export const ChangePointDetectionContextProvider: FC = ({ children }) => {
|
|||
}, [dataView, savedSearch, uiSettings, filterManager]);
|
||||
|
||||
const timefilter = useTimefilter();
|
||||
const timeBuckets = useTimeBuckets();
|
||||
const timeBuckets = useTimeBuckets(uiSettings);
|
||||
|
||||
const { searchBounds } = useFilterQueryUpdates();
|
||||
|
||||
|
|
|
@ -9,20 +9,20 @@ import { useEffect, useMemo, useState } from 'react';
|
|||
import { merge } from 'rxjs';
|
||||
import type { Moment } from 'moment';
|
||||
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
|
||||
import { useExecutionContext } from '@kbn/kibana-react-plugin/public';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import type { SignificantItem } from '@kbn/ml-agg-utils';
|
||||
|
||||
import type { Dictionary } from '@kbn/ml-url-state';
|
||||
import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker';
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { useTimeBuckets } from '@kbn/ml-time-buckets';
|
||||
|
||||
import { PLUGIN_ID } from '../../common';
|
||||
|
||||
import type { DocumentStatsSearchStrategyParams } from '../get_document_stats';
|
||||
import type { GroupTableItem } from '../components/log_rate_analysis_results_table/types';
|
||||
|
||||
import { useTimeBuckets } from './use_time_buckets';
|
||||
import { useAiopsAppContext } from './use_aiops_app_context';
|
||||
|
||||
import { useDocumentCountStats } from './use_document_count_stats';
|
||||
|
@ -39,7 +39,7 @@ export const useData = (
|
|||
barTarget: number = DEFAULT_BAR_TARGET,
|
||||
timeRange?: { min: Moment; max: Moment }
|
||||
) => {
|
||||
const { executionContext } = useAiopsAppContext();
|
||||
const { executionContext, uiSettings } = useAiopsAppContext();
|
||||
|
||||
useExecutionContext(executionContext, {
|
||||
name: PLUGIN_ID,
|
||||
|
@ -49,7 +49,7 @@ export const useData = (
|
|||
|
||||
const [lastRefresh, setLastRefresh] = useState(0);
|
||||
|
||||
const _timeBuckets = useTimeBuckets();
|
||||
const _timeBuckets = useTimeBuckets(uiSettings);
|
||||
const timefilter = useTimefilter({
|
||||
timeRangeSelector: selectedDataView?.timeFieldName !== undefined,
|
||||
autoRefreshSelector: true,
|
||||
|
|
|
@ -10,8 +10,7 @@ import type { Filter, Query, TimeRange } from '@kbn/es-query';
|
|||
import { useTimeRangeUpdates } from '@kbn/ml-date-picker';
|
||||
import { type AggregateQuery } from '@kbn/es-query';
|
||||
import type { TimeRangeBounds } from '@kbn/data-plugin/common';
|
||||
import { getBoundsRoundedToInterval } from '../../common/time_buckets';
|
||||
import { useTimeBuckets } from './use_time_buckets';
|
||||
import { getBoundsRoundedToInterval, useTimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import { useAiopsAppContext } from './use_aiops_app_context';
|
||||
import { useReload } from './use_reload';
|
||||
|
||||
|
@ -58,9 +57,10 @@ export const FilterQueryContextProvider: FC<{ timeRange?: TimeRange }> = ({
|
|||
data: {
|
||||
query: { filterManager, queryString, timefilter },
|
||||
},
|
||||
uiSettings,
|
||||
} = useAiopsAppContext();
|
||||
|
||||
const timeBuckets = useTimeBuckets();
|
||||
const timeBuckets = useTimeBuckets(uiSettings);
|
||||
const reload = useReload();
|
||||
|
||||
const [resultFilters, setResultFilter] = useState<Filter[]>(filterManager.getFilters());
|
||||
|
|
|
@ -68,6 +68,7 @@
|
|||
"@kbn/ml-ui-actions",
|
||||
"@kbn/core-http-server",
|
||||
"@kbn/core-ui-settings-browser-mocks",
|
||||
"@kbn/ml-time-buckets",
|
||||
"@kbn/ebt-tools",
|
||||
"@kbn/aiops-test-utils",
|
||||
],
|
||||
|
|
|
@ -1,45 +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 type { Moment } from 'moment';
|
||||
|
||||
export interface TimeRangeBounds {
|
||||
min?: Moment;
|
||||
max?: Moment;
|
||||
}
|
||||
|
||||
export declare interface TimeBucketsInterval {
|
||||
asMilliseconds: () => number;
|
||||
asSeconds: () => number;
|
||||
expression: string;
|
||||
}
|
||||
|
||||
export interface TimeBucketsConfig {
|
||||
'histogram:maxBars': number;
|
||||
'histogram:barTarget': number;
|
||||
dateFormat: string;
|
||||
'dateFormat:scaled': string[][];
|
||||
}
|
||||
|
||||
export declare class TimeBuckets {
|
||||
constructor(timeBucketsConfig: TimeBucketsConfig);
|
||||
public setBarTarget(barTarget: number): void;
|
||||
public setMaxBars(maxBars: number): void;
|
||||
public setInterval(interval: string): void;
|
||||
public setBounds(bounds: TimeRangeBounds): void;
|
||||
public getBounds(): { min: Moment; max: Moment };
|
||||
public getInterval(): TimeBucketsInterval;
|
||||
public getScaledDateFormat(): string;
|
||||
}
|
||||
|
||||
export declare function getTimeBucketsFromCache(): InstanceType<typeof TimeBuckets>;
|
||||
|
||||
export declare function getBoundsRoundedToInterval(
|
||||
bounds: TimeRangeBounds,
|
||||
interval: TimeBucketsInterval,
|
||||
inclusiveEnd?: boolean
|
||||
): Required<TimeRangeBounds>;
|
|
@ -1,516 +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 { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
|
||||
import { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||
import { ary, assign, isPlainObject, isString, sortBy } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import dateMath from '@kbn/datemath';
|
||||
import { parseInterval } from '../utils/parse_interval';
|
||||
|
||||
const { duration: d } = moment;
|
||||
|
||||
export function timeBucketsCalcAutoIntervalProvider() {
|
||||
// Note there is a current issue with Kibana (Kibana issue #9184)
|
||||
// which means we can't round to, for example, 2 week or 3 week buckets,
|
||||
// so there is a large gap between the 1 week and 1 month rule.
|
||||
const roundingRules = [
|
||||
[d(500, 'ms'), d(100, 'ms')],
|
||||
[d(5, 'second'), d(1, 'second')],
|
||||
[d(10, 'second'), d(5, 'second')],
|
||||
[d(15, 'second'), d(10, 'second')],
|
||||
[d(30, 'second'), d(15, 'second')],
|
||||
[d(1, 'minute'), d(30, 'second')],
|
||||
[d(5, 'minute'), d(1, 'minute')],
|
||||
[d(10, 'minute'), d(5, 'minute')],
|
||||
[d(15, 'minute'), d(10, 'minute')],
|
||||
[d(30, 'minute'), d(10, 'minute')],
|
||||
[d(1, 'hour'), d(30, 'minute')],
|
||||
[d(2, 'hour'), d(1, 'hour')],
|
||||
[d(4, 'hour'), d(2, 'hour')],
|
||||
[d(6, 'hour'), d(4, 'hour')],
|
||||
[d(8, 'hour'), d(6, 'hour')],
|
||||
[d(12, 'hour'), d(8, 'hour')],
|
||||
[d(24, 'hour'), d(12, 'hour')],
|
||||
[d(2, 'd'), d(1, 'd')],
|
||||
[d(4, 'd'), d(2, 'd')],
|
||||
[d(1, 'week'), d(4, 'd')],
|
||||
//[ d(2, 'week'), d(1, 'week') ],
|
||||
//[ d(1, 'month'), d(2, 'week') ],
|
||||
[d(1, 'month'), d(1, 'week')],
|
||||
[d(1, 'year'), d(1, 'month')],
|
||||
[Infinity, d(1, 'year')],
|
||||
];
|
||||
|
||||
const revRoundingRules = roundingRules.slice(0).reverse();
|
||||
|
||||
function find(rules, check, last) {
|
||||
function pick(buckets, duration) {
|
||||
const target = duration / buckets;
|
||||
let lastResp;
|
||||
|
||||
for (let i = 0; i < rules.length; i++) {
|
||||
const rule = rules[i];
|
||||
const resp = check(rule[0], rule[1], target);
|
||||
|
||||
if (resp == null) {
|
||||
if (!last) {
|
||||
continue;
|
||||
}
|
||||
if (lastResp) {
|
||||
return lastResp;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!last) {
|
||||
return resp;
|
||||
}
|
||||
lastResp = resp;
|
||||
}
|
||||
|
||||
// fallback to just a number of milliseconds, ensure ms is >= 1
|
||||
const ms = Math.max(Math.floor(target), 1);
|
||||
return moment.duration(ms, 'ms');
|
||||
}
|
||||
|
||||
return function (buckets, duration) {
|
||||
const interval = pick(buckets, duration);
|
||||
if (interval) {
|
||||
return moment.duration(interval._data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
near: find(
|
||||
revRoundingRules,
|
||||
function near(upperBound, lowerBound, target) {
|
||||
// upperBound - first duration in rule
|
||||
// lowerBound - second duration in rule
|
||||
// target - target interval in milliseconds.
|
||||
if (upperBound > target) {
|
||||
if (upperBound === Infinity) {
|
||||
return lowerBound;
|
||||
}
|
||||
|
||||
const boundMs = upperBound.asMilliseconds();
|
||||
const intervalMs = lowerBound.asMilliseconds();
|
||||
const retInterval =
|
||||
Math.abs(boundMs - target) <= Math.abs(intervalMs) ? upperBound : lowerBound;
|
||||
return retInterval;
|
||||
}
|
||||
},
|
||||
true
|
||||
),
|
||||
|
||||
lessThan: find(revRoundingRules, function (upperBound, lowerBound, target) {
|
||||
// upperBound - first duration in rule
|
||||
// lowerBound - second duration in rule
|
||||
// target - target interval in milliseconds. Must not return intervals less than this duration.
|
||||
if (lowerBound < target) {
|
||||
return upperBound !== Infinity ? upperBound : lowerBound;
|
||||
}
|
||||
}),
|
||||
|
||||
atLeast: find(revRoundingRules, function atLeast(upperBound, lowerBound, target) {
|
||||
// Unmodified from Kibana ui/time_buckets/calc_auto_interval.js.
|
||||
if (lowerBound <= target) {
|
||||
return lowerBound;
|
||||
}
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const unitsDesc = dateMath.unitsDesc;
|
||||
|
||||
// Index of the list of time interval units at which larger units (i.e. weeks, months, years) need
|
||||
// need to be converted to multiples of the largest unit supported in ES aggregation intervals (i.e. days).
|
||||
// Note that similarly the largest interval supported for ML bucket spans is 'd'.
|
||||
const timeUnitsMaxSupportedIndex = unitsDesc.indexOf('w');
|
||||
|
||||
const calcAuto = timeBucketsCalcAutoIntervalProvider();
|
||||
|
||||
/**
|
||||
* Helper object for wrapping the concept of an "Interval", which
|
||||
* describes a timespan that will separate buckets of time,
|
||||
* for example the interval between points on a time series chart.
|
||||
*/
|
||||
export function TimeBuckets(timeBucketsConfig, fieldFormats) {
|
||||
this._timeBucketsConfig = timeBucketsConfig;
|
||||
this._fieldFormats = fieldFormats;
|
||||
this.barTarget = this._timeBucketsConfig[UI_SETTINGS.HISTOGRAM_BAR_TARGET];
|
||||
this.maxBars = this._timeBucketsConfig[UI_SETTINGS.HISTOGRAM_MAX_BARS];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the target number of bars.
|
||||
*
|
||||
* @param {number} bt - target number of bars (buckets).
|
||||
*
|
||||
* @returns {undefined}
|
||||
*/
|
||||
TimeBuckets.prototype.setBarTarget = function (bt) {
|
||||
this.barTarget = bt;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the maximum number of bars.
|
||||
*
|
||||
* @param {number} mb - maximum number of bars (buckets).
|
||||
*
|
||||
* @returns {undefined}
|
||||
*/
|
||||
TimeBuckets.prototype.setMaxBars = function (mb) {
|
||||
this.maxBars = mb;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the bounds that these buckets are expected to cover.
|
||||
* This is required to support interval "auto" as well
|
||||
* as interval scaling.
|
||||
*
|
||||
* @param {object} input - an object with properties min and max,
|
||||
* representing the edges for the time span
|
||||
* we should cover
|
||||
*
|
||||
* @returns {undefined}
|
||||
*/
|
||||
TimeBuckets.prototype.setBounds = function (input) {
|
||||
if (!input) return this.clearBounds();
|
||||
|
||||
let bounds;
|
||||
if (isPlainObject(input)) {
|
||||
// accept the response from timefilter.getActiveBounds()
|
||||
bounds = [input.min, input.max];
|
||||
} else {
|
||||
bounds = Array.isArray(input) ? input : [];
|
||||
}
|
||||
|
||||
const moments = sortBy(bounds.map(ary(moment, 1)), Number);
|
||||
|
||||
const valid = moments.length === 2 && moments.every(isValidMoment);
|
||||
if (!valid) {
|
||||
this.clearBounds();
|
||||
throw new Error('invalid bounds set: ' + input);
|
||||
}
|
||||
|
||||
this._lb = moments.shift();
|
||||
this._ub = moments.pop();
|
||||
if (this.getDuration().asSeconds() < 0) {
|
||||
throw new TypeError('Intervals must be positive');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the stored bounds
|
||||
*
|
||||
* @return {undefined}
|
||||
*/
|
||||
TimeBuckets.prototype.clearBounds = function () {
|
||||
this._lb = this._ub = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check to see if we have received bounds yet
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
TimeBuckets.prototype.hasBounds = function () {
|
||||
return isValidMoment(this._ub) && isValidMoment(this._lb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the current bounds, if we have any.
|
||||
*
|
||||
* Note that this does not clone the bounds, so editing them may have unexpected side-effects.
|
||||
* Always call bounds.min.clone() before editing.
|
||||
*
|
||||
* @return {object|undefined} - If bounds are not defined, this
|
||||
* returns undefined, else it returns the bounds
|
||||
* for these buckets. This object has two props,
|
||||
* min and max. Each property will be a moment()
|
||||
* object
|
||||
*/
|
||||
TimeBuckets.prototype.getBounds = function () {
|
||||
if (!this.hasBounds()) return;
|
||||
return {
|
||||
min: this._lb,
|
||||
max: this._ub,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a moment duration object representing
|
||||
* the distance between the bounds, if the bounds
|
||||
* are set.
|
||||
*
|
||||
* @return {moment.duration|undefined}
|
||||
*/
|
||||
TimeBuckets.prototype.getDuration = function () {
|
||||
if (!this.hasBounds()) return;
|
||||
return moment.duration(this._ub - this._lb, 'ms');
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the interval at which buckets should be
|
||||
* generated.
|
||||
*
|
||||
* Input can be one of the following:
|
||||
* - "auto"
|
||||
* - an interval String, such as 7d, 1h or 30m which can be parsed to a moment duration using ml/common/util/parse_interval
|
||||
* - a moment.duration object.
|
||||
*
|
||||
* @param {string|moment.duration} input - see desc
|
||||
*/
|
||||
TimeBuckets.prototype.setInterval = function (input) {
|
||||
// Preserve the original units because they're lost when the interval is converted to a
|
||||
// moment duration object.
|
||||
this.originalInterval = input;
|
||||
|
||||
let interval = input;
|
||||
|
||||
if (!interval || interval === 'auto') {
|
||||
this._i = 'auto';
|
||||
return;
|
||||
}
|
||||
|
||||
if (isString(interval)) {
|
||||
input = interval;
|
||||
interval = parseInterval(interval);
|
||||
if (+interval === 0) {
|
||||
interval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// If the value wasn't converted to a duration, and isn't already a duration, we have a problem
|
||||
if (!moment.isDuration(interval)) {
|
||||
throw new TypeError('"' + input + '" is not a valid interval.');
|
||||
}
|
||||
|
||||
this._i = interval;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the interval for the buckets. If the
|
||||
* number of buckets created by the interval set
|
||||
* is larger than config:histogram:maxBars then the
|
||||
* interval will be scaled up. If the number of buckets
|
||||
* created is less than one, the interval is scaled back.
|
||||
*
|
||||
* The interval object returned is a moment.duration
|
||||
* object that has been decorated with the following
|
||||
* properties.
|
||||
*
|
||||
* interval.description: a text description of the interval.
|
||||
* designed to be used list "field per {{ desc }}".
|
||||
* - "minute"
|
||||
* - "10 days"
|
||||
* - "3 years"
|
||||
*
|
||||
* interval.expr: the elasticsearch expression that creates this
|
||||
* interval. If the interval does not properly form an elasticsearch
|
||||
* expression it will be forced into one.
|
||||
*
|
||||
* interval.scaled: the interval was adjusted to
|
||||
* accommodate the maxBars setting.
|
||||
*
|
||||
* interval.scale: the number that y-values should be
|
||||
* multiplied by
|
||||
*
|
||||
* interval.scaleDescription: a description that reflects
|
||||
* the values which will be produced by using the
|
||||
* interval.scale.
|
||||
*
|
||||
*
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
TimeBuckets.prototype.getInterval = function () {
|
||||
const self = this;
|
||||
const duration = self.getDuration();
|
||||
return decorateInterval(maybeScaleInterval(readInterval()), duration);
|
||||
|
||||
// either pull the interval from state or calculate the auto-interval
|
||||
function readInterval() {
|
||||
const interval = self._i;
|
||||
if (moment.isDuration(interval)) return interval;
|
||||
return calcAuto.near(self.barTarget, duration);
|
||||
}
|
||||
|
||||
// check to see if the interval should be scaled, and scale it if so
|
||||
function maybeScaleInterval(interval) {
|
||||
if (!self.hasBounds()) return interval;
|
||||
|
||||
const maxLength = self.maxBars;
|
||||
const approxLen = duration / interval;
|
||||
let scaled;
|
||||
|
||||
// If the number of buckets we got back from using the barTarget is less than
|
||||
// maxBars, than use the lessThan rule to try and get closer to maxBars.
|
||||
if (approxLen > maxLength) {
|
||||
scaled = calcAuto.lessThan(maxLength, duration);
|
||||
} else {
|
||||
return interval;
|
||||
}
|
||||
|
||||
if (+scaled === +interval) return interval;
|
||||
|
||||
decorateInterval(interval, duration);
|
||||
return assign(scaled, {
|
||||
preScaled: interval,
|
||||
scale: interval / scaled,
|
||||
scaled: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an interval which in the last step of calculation is rounded to
|
||||
* the closest multiple of the supplied divisor (in seconds).
|
||||
*
|
||||
* @return {moment.duration|undefined}
|
||||
*/
|
||||
TimeBuckets.prototype.getIntervalToNearestMultiple = function (divisorSecs) {
|
||||
const interval = this.getInterval();
|
||||
const intervalSecs = interval.asSeconds();
|
||||
|
||||
const remainder = intervalSecs % divisorSecs;
|
||||
if (remainder === 0) {
|
||||
return interval;
|
||||
}
|
||||
|
||||
// Create a new interval which is a multiple of the supplied divisor (not zero).
|
||||
let nearestMultiple =
|
||||
remainder > divisorSecs / 2 ? intervalSecs + divisorSecs - remainder : intervalSecs - remainder;
|
||||
nearestMultiple = nearestMultiple === 0 ? divisorSecs : nearestMultiple;
|
||||
const nearestMultipleInt = moment.duration(nearestMultiple, 'seconds');
|
||||
decorateInterval(nearestMultipleInt, this.getDuration());
|
||||
|
||||
// Check to see if the new interval is scaled compared to the original.
|
||||
const preScaled = interval.preScaled;
|
||||
if (preScaled !== undefined && preScaled < nearestMultipleInt) {
|
||||
nearestMultipleInt.preScaled = preScaled;
|
||||
nearestMultipleInt.scale = preScaled / nearestMultipleInt;
|
||||
nearestMultipleInt.scaled = true;
|
||||
}
|
||||
|
||||
return nearestMultipleInt;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a date format string that will represent dates that
|
||||
* progress at our interval.
|
||||
*
|
||||
* Since our interval can be as small as 1ms, the default
|
||||
* date format is usually way too much. with `dateFormat:scaled`
|
||||
* users can modify how dates are formatted within series
|
||||
* produced by TimeBuckets
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
TimeBuckets.prototype.getScaledDateFormat = function () {
|
||||
const interval = this.getInterval();
|
||||
const rules = this._timeBucketsConfig['dateFormat:scaled'];
|
||||
|
||||
for (let i = rules.length - 1; i >= 0; i--) {
|
||||
const rule = rules[i];
|
||||
if (!rule[0] || interval >= moment.duration(rule[0])) {
|
||||
return rule[1];
|
||||
}
|
||||
}
|
||||
|
||||
return this._timeBucketsConfig.dateFormat;
|
||||
};
|
||||
|
||||
TimeBuckets.prototype.getScaledDateFormatter = function () {
|
||||
const fieldFormats = this._fieldFormats;
|
||||
const DateFieldFormat = fieldFormats.getType(FIELD_FORMAT_IDS.DATE);
|
||||
return new DateFieldFormat(
|
||||
{
|
||||
pattern: this.getScaledDateFormat(),
|
||||
},
|
||||
// getConfig
|
||||
this._timeBucketsConfig
|
||||
);
|
||||
};
|
||||
|
||||
// Appends some TimeBuckets specific properties to the moment.js duration interval.
|
||||
// Uses the originalDuration from which the time bucket was created to calculate the overflow
|
||||
// property (i.e. difference between the supplied duration and the calculated bucket interval).
|
||||
function decorateInterval(interval, originalDuration) {
|
||||
const esInterval = calcEsInterval(interval);
|
||||
interval.esValue = esInterval.value;
|
||||
interval.esUnit = esInterval.unit;
|
||||
interval.expression = esInterval.expression;
|
||||
interval.overflow =
|
||||
originalDuration > interval ? moment.duration(interval - originalDuration) : false;
|
||||
|
||||
const prettyUnits = moment.normalizeUnits(esInterval.unit);
|
||||
if (esInterval.value === 1) {
|
||||
interval.description = prettyUnits;
|
||||
} else {
|
||||
interval.description = `${esInterval.value} ${prettyUnits}s`;
|
||||
}
|
||||
|
||||
return interval;
|
||||
}
|
||||
|
||||
function isValidMoment(m) {
|
||||
return m && 'isValid' in m && m.isValid();
|
||||
}
|
||||
|
||||
export function getBoundsRoundedToInterval(bounds, interval, inclusiveEnd = false) {
|
||||
// Returns new bounds, created by flooring the min of the provided bounds to the start of
|
||||
// the specified interval (a moment duration), and rounded upwards (Math.ceil) to 1ms before
|
||||
// the start of the next interval (Kibana dashboards search >= bounds min, and <= bounds max,
|
||||
// so we subtract 1ms off the max to avoid querying start of the new Elasticsearch aggregation bucket).
|
||||
const intervalMs = interval.asMilliseconds();
|
||||
const adjustedMinMs = Math.floor(bounds.min.valueOf() / intervalMs) * intervalMs;
|
||||
let adjustedMaxMs = Math.ceil(bounds.max.valueOf() / intervalMs) * intervalMs;
|
||||
|
||||
// Don't include the start ms of the next bucket unless specified..
|
||||
if (inclusiveEnd === false) {
|
||||
adjustedMaxMs = adjustedMaxMs - 1;
|
||||
}
|
||||
return { min: moment(adjustedMinMs), max: moment(adjustedMaxMs) };
|
||||
}
|
||||
|
||||
export function calcEsInterval(duration) {
|
||||
// Converts a moment.duration into an Elasticsearch compatible interval expression,
|
||||
// and provides associated metadata.
|
||||
|
||||
// Note this was a copy of Kibana's original ui/time_buckets/calc_es_interval,
|
||||
// but with the definition of a 'large' unit changed from 'M' to 'w',
|
||||
// bringing it into line with the time units supported by Elasticsearch
|
||||
for (let i = 0; i < unitsDesc.length; i++) {
|
||||
const unit = unitsDesc[i];
|
||||
const val = duration.as(unit);
|
||||
// find a unit that rounds neatly
|
||||
if (val >= 1 && Math.floor(val) === val) {
|
||||
// Apart from for date histograms, ES only supports time units up to 'd',
|
||||
// meaning we can't for example use 'w' for job bucket spans.
|
||||
// See https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#time-units
|
||||
// So keep going until we get out of the "large" units.
|
||||
if (i <= timeUnitsMaxSupportedIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return {
|
||||
value: val,
|
||||
unit: unit,
|
||||
expression: val + unit,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const ms = duration.as('ms');
|
||||
return {
|
||||
value: ms,
|
||||
unit: 'ms',
|
||||
expression: ms + 'ms',
|
||||
};
|
||||
}
|
|
@ -10,7 +10,7 @@ import type { Query } from '@kbn/es-query';
|
|||
import type { IKibanaSearchResponse } from '@kbn/data-plugin/common';
|
||||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
||||
import type { KibanaExecutionContext } from '@kbn/core-execution-context-common';
|
||||
import type { TimeBucketsInterval } from '../services/time_buckets';
|
||||
import type { TimeBucketsInterval } from '@kbn/ml-time-buckets';
|
||||
|
||||
export interface RandomSamplingOption {
|
||||
mode: 'random_sampling';
|
||||
|
|
|
@ -18,6 +18,7 @@ import type { Query } from '@kbn/es-query';
|
|||
import { buildEsQuery } from '@kbn/es-query';
|
||||
import type { SearchQueryLanguage } from '@kbn/ml-query-utils';
|
||||
import { getEsQueryConfig } from '@kbn/data-plugin/common';
|
||||
import { useTimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import { useDataDriftStateManagerContext } from '../../data_drift/use_state_manager';
|
||||
import type { InitialSettings } from '../../data_drift/use_data_drift_result';
|
||||
import {
|
||||
|
@ -25,7 +26,6 @@ import {
|
|||
useDocumentCountStats,
|
||||
} from './use_document_count_stats';
|
||||
import { useDataVisualizerKibana } from '../../kibana_context';
|
||||
import { useTimeBuckets } from './use_time_buckets';
|
||||
|
||||
const DEFAULT_BAR_TARGET = 75;
|
||||
|
||||
|
@ -57,7 +57,7 @@ export const useData = (
|
|||
|
||||
const [lastRefresh, setLastRefresh] = useState(0);
|
||||
|
||||
const _timeBuckets = useTimeBuckets();
|
||||
const _timeBuckets = useTimeBuckets(uiSettings);
|
||||
const timefilter = useTimefilter({
|
||||
timeRangeSelector: selectedDataView?.timeFieldName !== undefined,
|
||||
autoRefreshSelector: true,
|
||||
|
|
|
@ -1,24 +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 { useMemo } from 'react';
|
||||
import { UI_SETTINGS } from '@kbn/data-service';
|
||||
import { TimeBuckets } from '../../../../common/services/time_buckets';
|
||||
import { useDataVisualizerKibana } from '../../kibana_context';
|
||||
|
||||
export const useTimeBuckets = () => {
|
||||
const { uiSettings } = useDataVisualizerKibana().services;
|
||||
|
||||
return useMemo(() => {
|
||||
return new TimeBuckets({
|
||||
[UI_SETTINGS.HISTOGRAM_MAX_BARS]: uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS),
|
||||
[UI_SETTINGS.HISTOGRAM_BAR_TARGET]: uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
|
||||
dateFormat: uiSettings.get('dateFormat'),
|
||||
'dateFormat:scaled': uiSettings.get('dateFormat:scaled'),
|
||||
});
|
||||
}, [uiSettings]);
|
||||
};
|
|
@ -10,10 +10,11 @@ import React, { useEffect, useState, useCallback, useRef, useMemo } from 'react'
|
|||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { IImporter } from '@kbn/file-upload-plugin/public';
|
||||
import moment, { type Moment } from 'moment';
|
||||
import { useTimeBuckets } from '../../../common/hooks/use_time_buckets';
|
||||
import { useTimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import { IMPORT_STATUS, type Statuses } from '../import_progress';
|
||||
import { EventRateChart, type LineChartPoint } from './event_rate_chart';
|
||||
import { runDocCountSearch } from './doc_count_search';
|
||||
import { useDataVisualizerKibana } from '../../../kibana_context';
|
||||
|
||||
const BAR_TARGET = 150;
|
||||
const PROGRESS_INCREMENT = 5;
|
||||
|
@ -26,7 +27,9 @@ export const DocCountChart: FC<{
|
|||
dataStart: DataPublicPluginStart;
|
||||
importer: IImporter;
|
||||
}> = ({ statuses, dataStart, importer }) => {
|
||||
const timeBuckets = useTimeBuckets();
|
||||
const { services } = useDataVisualizerKibana();
|
||||
const { uiSettings } = services;
|
||||
const timeBuckets = useTimeBuckets(uiSettings);
|
||||
const index = useMemo(() => importer.getIndex(), [importer]);
|
||||
const timeField = useMemo(() => importer.getTimeField(), [importer]);
|
||||
|
||||
|
|
|
@ -5,11 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
|
||||
import type estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
|
||||
import type { DataPublicPluginStart, IKibanaSearchResponse } from '@kbn/data-plugin/public';
|
||||
import type { TimeBuckets } from '@kbn/ml-time-buckets';
|
||||
|
||||
import type { LineChartPoint } from './event_rate_chart';
|
||||
import type { TimeBuckets } from '../../../../../common/services/time_buckets';
|
||||
|
||||
type EventRateResponse = IKibanaSearchResponse<
|
||||
estypes.SearchResponse<
|
||||
|
|
|
@ -18,10 +18,10 @@ import { SEARCH_QUERY_LANGUAGE } from '@kbn/ml-query-utils';
|
|||
import type { KibanaExecutionContext } from '@kbn/core-execution-context-common';
|
||||
import { useExecutionContext } from '@kbn/kibana-react-plugin/public';
|
||||
import type { AggregateQuery } from '@kbn/es-query';
|
||||
import { useTimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import type { SamplingOption } from '../../../../../common/types/field_stats';
|
||||
import type { FieldVisConfig } from '../../../../../common/types/field_vis_config';
|
||||
import type { SupportedFieldType } from '../../../../../common/types/job_field_type';
|
||||
import { useTimeBuckets } from '../../../common/hooks/use_time_buckets';
|
||||
import type { ItemIdToExpandedRowMap } from '../../../common/components/stats_table';
|
||||
import type {
|
||||
MetricFieldsStats,
|
||||
|
@ -105,7 +105,7 @@ export const useESQLDataVisualizerData = (
|
|||
|
||||
useExecutionContext(executionContext, embeddableExecutionContext);
|
||||
|
||||
const _timeBuckets = useTimeBuckets();
|
||||
const _timeBuckets = useTimeBuckets(uiSettings);
|
||||
const timefilter = useTimefilter({
|
||||
timeRangeSelector: true,
|
||||
autoRefreshSelector: true,
|
||||
|
|
|
@ -13,8 +13,8 @@ import { useCallback, useEffect, useMemo, useReducer, useRef } from 'react';
|
|||
import { type UseCancellableSearch, useCancellableSearch } from '@kbn/ml-cancellable-search';
|
||||
import type { estypes } from '@elastic/elasticsearch';
|
||||
import type { ISearchOptions } from '@kbn/data-plugin/common';
|
||||
import type { TimeBucketsInterval } from '@kbn/ml-time-buckets';
|
||||
import { OMIT_FIELDS } from '../../../../../common/constants';
|
||||
import type { TimeBucketsInterval } from '../../../../../common/services/time_buckets';
|
||||
import type {
|
||||
DataStatsFetchProgress,
|
||||
DocumentCountStats,
|
||||
|
|
|
@ -20,7 +20,7 @@ import useObservable from 'react-use/lib/useObservable';
|
|||
import type { KibanaExecutionContext } from '@kbn/core-execution-context-common';
|
||||
import { useExecutionContext } from '@kbn/kibana-react-plugin/public';
|
||||
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import { useTimeBuckets } from '../../common/hooks/use_time_buckets';
|
||||
import { useTimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import { DATA_VISUALIZER_GRID_EMBEDDABLE_TYPE } from '../embeddables/grid_embeddable/constants';
|
||||
import { filterFields } from '../../common/components/fields_stats_grid/filter_fields';
|
||||
import type { RandomSamplerOption } from '../constants/random_sampler';
|
||||
|
@ -174,7 +174,7 @@ export const useDataVisualizerGridData = (
|
|||
data.query.filterManager,
|
||||
]);
|
||||
|
||||
const _timeBuckets = useTimeBuckets();
|
||||
const _timeBuckets = useTimeBuckets(uiSettings);
|
||||
|
||||
const timefilter = useTimefilter({
|
||||
timeRangeSelector: currentDataView?.timeFieldName !== undefined,
|
||||
|
|
|
@ -76,7 +76,8 @@
|
|||
"@kbn/unified-search-plugin",
|
||||
"@kbn/usage-collection-plugin",
|
||||
"@kbn/utility-types",
|
||||
"@kbn/visualization-utils"
|
||||
"@kbn/visualization-utils",
|
||||
"@kbn/ml-time-buckets"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -42,6 +42,7 @@ import { isDefined } from '@kbn/ml-is-defined';
|
|||
import { escapeQuotes } from '@kbn/es-query';
|
||||
import { isQuery } from '@kbn/data-plugin/public';
|
||||
|
||||
import type { TimeRangeBounds } from '@kbn/ml-time-buckets';
|
||||
import { PLUGIN_ID } from '../../../../common/constants/app';
|
||||
import { findMessageField } from '../../util/index_utils';
|
||||
import { getInitialAnomaliesLayers, getInitialSourceIndexFieldLayers } from '../../../maps/util';
|
||||
|
@ -56,7 +57,6 @@ import { getUrlForRecord, openCustomUrlWindow } from '../../util/custom_url_util
|
|||
import type { SourceIndicesWithGeoFields } from '../../explorer/explorer_utils';
|
||||
import { escapeDoubleQuotes, getDateFormatTz } from '../../explorer/explorer_utils';
|
||||
import { usePermissionCheck } from '../../capabilities/check_capabilities';
|
||||
import type { TimeRangeBounds } from '../../util/time_buckets';
|
||||
import { useMlKibana } from '../../contexts/kibana';
|
||||
import { getFieldTypeFromMapping } from '../../services/mapping_service';
|
||||
import { useMlIndexUtils } from '../../util/index_service';
|
||||
|
|
|
@ -1,23 +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 { useMemo } from 'react';
|
||||
import { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||
import { useUiSettings } from '../../contexts/kibana';
|
||||
import { TimeBuckets } from '../../util/time_buckets';
|
||||
|
||||
export const useTimeBuckets = () => {
|
||||
const uiSettings = useUiSettings();
|
||||
return useMemo(() => {
|
||||
return new TimeBuckets({
|
||||
'histogram:maxBars': uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS),
|
||||
'histogram:barTarget': uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
|
||||
dateFormat: uiSettings.get('dateFormat'),
|
||||
'dateFormat:scaled': uiSettings.get('dateFormat:scaled'),
|
||||
});
|
||||
}, [uiSettings]);
|
||||
};
|
|
@ -17,6 +17,7 @@ import { useCallback, useMemo } from 'react';
|
|||
import type { TimefilterContract } from '@kbn/data-plugin/public';
|
||||
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||
import type { InfluencersFilterQuery } from '@kbn/ml-anomaly-utils';
|
||||
import type { TimeBucketsInterval, TimeRangeBounds } from '@kbn/ml-time-buckets';
|
||||
import type { AppStateSelectedCells, ExplorerJob } from '../explorer_utils';
|
||||
import {
|
||||
getDateFormatTz,
|
||||
|
@ -34,7 +35,6 @@ import { useMlKibana } from '../../contexts/kibana';
|
|||
import type { MlResultsService } from '../../services/results_service';
|
||||
import { mlResultsServiceProvider } from '../../services/results_service';
|
||||
import type { AnomalyExplorerChartsService } from '../../services/anomaly_explorer_charts_service';
|
||||
import type { TimeBucketsInterval, TimeRangeBounds } from '../../util/time_buckets';
|
||||
import { useAnomalyExplorerContext } from '../anomaly_explorer_context';
|
||||
|
||||
// Memoize the data fetching methods.
|
||||
|
|
|
@ -38,6 +38,7 @@ import {
|
|||
LazySavedObjectSaveModalDashboard,
|
||||
withSuspense,
|
||||
} from '@kbn/presentation-util-plugin/public';
|
||||
import type { TimeRangeBounds } from '@kbn/ml-time-buckets';
|
||||
import { useTableSeverity } from '../components/controls/select_severity';
|
||||
import type { JobId } from '../../../common/types/anomaly_detection_jobs';
|
||||
import { getDefaultExplorerChartsPanelTitle } from '../../embeddables/anomaly_charts/anomaly_charts_embeddable';
|
||||
|
@ -51,7 +52,6 @@ import { ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE } from '../../embeddables';
|
|||
import { useMlKibana } from '../contexts/kibana';
|
||||
import type { AppStateSelectedCells, ExplorerJob } from './explorer_utils';
|
||||
import { getSelectionInfluencers, getSelectionTimeRange } from './explorer_utils';
|
||||
import type { TimeRangeBounds } from '../util/time_buckets';
|
||||
|
||||
interface AnomalyContextMenuProps {
|
||||
selectedJobs: ExplorerJob[];
|
||||
|
|
|
@ -40,6 +40,7 @@ import {
|
|||
LazySavedObjectSaveModalDashboard,
|
||||
withSuspense,
|
||||
} from '@kbn/presentation-util-plugin/public';
|
||||
import { useTimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import type { JobId } from '../../../common/types/anomaly_detection_jobs';
|
||||
import { getDefaultSwimlanePanelTitle } from '../../embeddables/anomaly_swimlane/anomaly_swimlane_embeddable';
|
||||
import { useCasesModal } from '../contexts/kibana/use_cases_modal';
|
||||
|
@ -63,7 +64,6 @@ import { MlTooltipComponent } from '../components/chart_tooltip';
|
|||
import { SwimlaneAnnotationContainer, Y_AXIS_LABEL_WIDTH } from './swimlane_annotation_container';
|
||||
import { AnomalyTimelineService } from '../services/anomaly_timeline_service';
|
||||
import { useAnomalyExplorerContext } from './anomaly_explorer_context';
|
||||
import { useTimeBuckets } from '../components/custom_hooks/use_time_buckets';
|
||||
import { getTimeBoundsFromSelection } from './hooks/use_selected_cells';
|
||||
import { SwimLaneWrapper } from './alerts';
|
||||
|
||||
|
@ -95,6 +95,7 @@ export const AnomalyTimeline: FC<AnomalyTimelineProps> = React.memo(
|
|||
charts: chartsService,
|
||||
cases,
|
||||
embeddable,
|
||||
uiSettings,
|
||||
},
|
||||
} = useMlKibana();
|
||||
|
||||
|
@ -113,7 +114,7 @@ export const AnomalyTimeline: FC<AnomalyTimelineProps> = React.memo(
|
|||
|
||||
const canEditDashboards = capabilities.dashboard?.createNew ?? false;
|
||||
|
||||
const timeBuckets = useTimeBuckets();
|
||||
const timeBuckets = useTimeBuckets(uiSettings);
|
||||
|
||||
const { overallAnnotations } = explorerState;
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import type { TimeRangeBounds } from '@kbn/data-plugin/common';
|
|||
// FIXME get rid of the static import
|
||||
import { mlTimefilterRefresh$ } from '@kbn/ml-date-picker';
|
||||
import type { InfluencersFilterQuery } from '@kbn/ml-anomaly-utils';
|
||||
import type { TimeBucketsInterval } from '@kbn/ml-time-buckets';
|
||||
import type { AnomalyTimelineService } from '../services/anomaly_timeline_service';
|
||||
import type {
|
||||
AppStateSelectedCells,
|
||||
|
@ -40,7 +41,6 @@ import {
|
|||
// FIXME get rid of the static import
|
||||
import { mlJobService } from '../services/job_service';
|
||||
import { getSelectionInfluencers, getSelectionTimeRange } from './explorer_utils';
|
||||
import type { TimeBucketsInterval } from '../util/time_buckets';
|
||||
import type { Refresh } from '../routing/use_refresh';
|
||||
import { StateService } from '../services/state_service';
|
||||
import type { AnomalyExplorerUrlStateService } from './hooks/use_explorer_url_state';
|
||||
|
|
|
@ -34,6 +34,7 @@ import type { DataView } from '@kbn/data-views-plugin/common';
|
|||
import type { TimefilterContract } from '@kbn/data-plugin/public';
|
||||
import { useStorage } from '@kbn/ml-local-storage';
|
||||
import { isDefined } from '@kbn/ml-is-defined';
|
||||
import type { TimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import { HelpPopover } from '../components/help_popover';
|
||||
import { AnnotationFlyout } from '../components/annotations/annotation_flyout';
|
||||
// @ts-ignore
|
||||
|
@ -77,7 +78,6 @@ import { ANOMALY_DETECTION_DEFAULT_TIME_RANGE } from '../../../common/constants/
|
|||
import { AnomalyContextMenu } from './anomaly_context_menu';
|
||||
import type { JobSelectorProps } from '../components/job_selector/job_selector';
|
||||
import type { ExplorerState } from './reducers';
|
||||
import type { TimeBuckets } from '../util/time_buckets';
|
||||
import { useToastNotificationService } from '../services/toast_notification_service';
|
||||
import { useMlKibana, useMlLocator } from '../contexts/kibana';
|
||||
import { useAnomalyExplorerContext } from './anomaly_explorer_context';
|
||||
|
|
|
@ -13,10 +13,10 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import type { TimefilterContract } from '@kbn/data-plugin/public';
|
||||
import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
|
||||
import type { MlEntityFieldOperation } from '@kbn/ml-anomaly-utils';
|
||||
import type { TimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import { ExplorerChartsContainer } from './explorer_charts_container';
|
||||
import type { TableSeverity } from '../../components/controls/select_severity/select_severity';
|
||||
import { SelectSeverityUI } from '../../components/controls/select_severity/select_severity';
|
||||
import type { TimeBuckets } from '../../util/time_buckets';
|
||||
import type { ExplorerChartsData } from './explorer_charts_container_service';
|
||||
import type { MlLocator } from '../../../../common/types/locator';
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
} from '@kbn/ml-anomaly-utils';
|
||||
|
||||
import type { InfluencersFilterQuery } from '@kbn/ml-anomaly-utils';
|
||||
import type { TimeRangeBounds } from '@kbn/ml-time-buckets';
|
||||
import {
|
||||
ANNOTATIONS_TABLE_DEFAULT_QUERY_SIZE,
|
||||
ANOMALIES_TABLE_DEFAULT_QUERY_SIZE,
|
||||
|
@ -51,7 +52,6 @@ import {
|
|||
} from './explorer_constants';
|
||||
import type { CombinedJob } from '../../../common/types/anomaly_detection_jobs';
|
||||
import type { MlResultsService } from '../services/results_service';
|
||||
import type { TimeRangeBounds } from '../util/time_buckets';
|
||||
import type { Annotations, AnnotationsTable } from '../../../common/types/annotations';
|
||||
|
||||
export interface ExplorerJob {
|
||||
|
|
|
@ -48,13 +48,13 @@ import {
|
|||
} from '@kbn/ml-anomaly-utils';
|
||||
import { formatHumanReadableDateTime } from '@kbn/ml-date-utils';
|
||||
import { useIsDarkTheme } from '@kbn/ml-kibana-theme';
|
||||
import type { TimeBuckets as TimeBucketsClass } from '@kbn/ml-time-buckets';
|
||||
import { SwimLanePagination } from './swimlane_pagination';
|
||||
import type {
|
||||
AppStateSelectedCells,
|
||||
OverallSwimlaneData,
|
||||
ViewBySwimLaneData,
|
||||
} from './explorer_utils';
|
||||
import type { TimeBuckets as TimeBucketsClass } from '../util/time_buckets';
|
||||
import type { SwimlaneType } from './explorer_constants';
|
||||
import { SWIMLANE_TYPE } from './explorer_constants';
|
||||
import { mlEscape } from '../util/string_utils';
|
||||
|
|
|
@ -9,13 +9,13 @@ import { BehaviorSubject, lastValueFrom } from 'rxjs';
|
|||
|
||||
import type { ML_ANOMALY_SEVERITY } from '@kbn/ml-anomaly-utils';
|
||||
import { getSeverityType, ES_AGGREGATION } from '@kbn/ml-anomaly-utils';
|
||||
import type { TimeBuckets } from '@kbn/ml-time-buckets';
|
||||
|
||||
import { parseInterval } from '../../../../../../common/util/parse_interval';
|
||||
import { JOB_TYPE } from '../../../../../../common/constants/new_job';
|
||||
|
||||
import type { ModelPlotOutputResults } from '../../../../services/results_service';
|
||||
import { mlResultsService } from '../../../../services/results_service';
|
||||
import type { TimeBuckets } from '../../../../util/time_buckets';
|
||||
|
||||
import type { JobCreatorType } from '../job_creator';
|
||||
import { isMultiMetricJobCreator } from '../job_creator';
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { TimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import { useCurrentThemeVars } from '../../../../../../contexts/kibana';
|
||||
import type { JobCreatorType } from '../../../../common/job_creator';
|
||||
import { isMultiMetricJobCreator, isPopulationJobCreator } from '../../../../common/job_creator';
|
||||
import type { TimeBuckets } from '../../../../../../util/time_buckets';
|
||||
import { getTimeBucketsFromCache } from '../../../../../../util/time_buckets';
|
||||
import { getTimeBucketsFromCache } from '../../../../../../util/get_time_buckets_from_cache';
|
||||
|
||||
export function useChartColors() {
|
||||
const { euiTheme } = useCurrentThemeVars();
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { createContext } from 'react';
|
||||
import type { Field, Aggregation } from '@kbn/ml-anomaly-utils';
|
||||
import type { TimeBuckets } from '../../../../util/time_buckets';
|
||||
import type { TimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import type { JobCreatorType, SingleMetricJobCreator } from '../../common/job_creator';
|
||||
import type { ChartLoader } from '../../common/chart_loader';
|
||||
import type { MapLoader } from '../../common/map_loader';
|
||||
|
|
|
@ -12,7 +12,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { getTimeFilterRange, useTimefilter } from '@kbn/ml-date-picker';
|
||||
import { EVENT_RATE_FIELD_ID } from '@kbn/ml-anomaly-utils';
|
||||
import { useTimeBuckets } from '../../../../components/custom_hooks/use_time_buckets';
|
||||
import { useTimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import { Wizard } from './wizard';
|
||||
import { WIZARD_STEPS } from '../components/step_types';
|
||||
import { getJobCreatorTitle } from '../../common/job_creator/util/general';
|
||||
|
@ -54,10 +54,10 @@ export const Page: FC<PageProps> = ({ existingJobsAndGroups, jobType }) => {
|
|||
const timefilter = useTimefilter();
|
||||
const dataSourceContext = useDataSource();
|
||||
const {
|
||||
services: { maps: mapsPlugin },
|
||||
services: { maps: mapsPlugin, uiSettings },
|
||||
} = useMlKibana();
|
||||
|
||||
const chartInterval = useTimeBuckets();
|
||||
const chartInterval = useTimeBuckets(uiSettings);
|
||||
|
||||
const jobCreator = useMemo(
|
||||
() =>
|
||||
|
|
|
@ -7,11 +7,10 @@
|
|||
|
||||
import type { FC } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import type { TimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import { useModelMemoryEstimator } from '../../common/job_creator/util/model_memory_estimator';
|
||||
import { WIZARD_STEPS } from '../components/step_types';
|
||||
|
||||
import type { TimeBuckets } from '../../../../util/time_buckets';
|
||||
|
||||
import type { JobCreatorContextValue } from '../components/job_creator_context';
|
||||
import { JobCreatorContext } from '../components/job_creator_context';
|
||||
import type { ExistingJobsAndGroups } from '../../../../services/job_service';
|
||||
|
|
|
@ -13,12 +13,13 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
|
||||
import { formatHumanReadableDateTime } from '@kbn/ml-date-utils';
|
||||
import { useTimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import { useGroupActions } from './actions';
|
||||
import type { Group, GroupsDictionary } from './anomaly_detection_panel';
|
||||
import { JobSelectorBadge } from '../../../components/job_selector/job_selector_badge';
|
||||
import { toLocaleString } from '../../../util/string_utils';
|
||||
import { SwimlaneContainer } from '../../../explorer/swimlane_container';
|
||||
import { useTimeBuckets } from '../../../components/custom_hooks/use_time_buckets';
|
||||
import { useMlKibana } from '../../../contexts/kibana';
|
||||
|
||||
export enum AnomalyDetectionListColumns {
|
||||
id = 'id',
|
||||
|
@ -43,7 +44,10 @@ export const AnomalyDetectionTable: FC<Props> = ({ items, chartsService }) => {
|
|||
const [sortField, setSortField] = useState<string>(AnomalyDetectionListColumns.id);
|
||||
const [sortDirection, setSortDirection] = useState<Direction>('asc');
|
||||
|
||||
const timeBuckets = useTimeBuckets();
|
||||
const {
|
||||
services: { uiSettings },
|
||||
} = useMlKibana();
|
||||
const timeBuckets = useTimeBuckets(uiSettings);
|
||||
|
||||
const columns: Array<EuiBasicTableColumn<Group>> = [
|
||||
{
|
||||
|
|
|
@ -16,6 +16,7 @@ import { EuiThemeProvider as StyledComponentsThemeProvider } from '@kbn/kibana-r
|
|||
import { useUrlState } from '@kbn/ml-url-state';
|
||||
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||
import { ML_JOB_ID } from '@kbn/ml-anomaly-utils';
|
||||
import { useTimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import { basicResolvers } from '../resolvers';
|
||||
import { ML_PAGES } from '../../../locator';
|
||||
import type { NavigateToPath } from '../../contexts/kibana';
|
||||
|
@ -38,7 +39,6 @@ import { useTableSeverity } from '../../components/controls/select_severity';
|
|||
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
|
||||
import { MlAnnotationUpdatesContext } from '../../contexts/ml/ml_annotation_updates_context';
|
||||
import { AnnotationUpdatesService } from '../../services/annotations_service';
|
||||
import { useTimeBuckets } from '../../components/custom_hooks/use_time_buckets';
|
||||
import { MlPageHeader } from '../../components/page_header';
|
||||
import { PageTitle } from '../../components/page_title';
|
||||
import { AnomalyResultsViewSelector } from '../../components/anomaly_results_view_selector';
|
||||
|
@ -105,14 +105,14 @@ interface ExplorerUrlStateManagerProps {
|
|||
|
||||
const ExplorerUrlStateManager: FC<ExplorerUrlStateManagerProps> = ({ jobsWithTimeRange }) => {
|
||||
const {
|
||||
services: { cases, presentationUtil },
|
||||
services: { cases, presentationUtil, uiSettings },
|
||||
} = useMlKibana();
|
||||
|
||||
const [globalState] = useUrlState('_g');
|
||||
const [stoppedPartitions, setStoppedPartitions] = useState<string[] | undefined>();
|
||||
const [invalidTimeRangeError, setInValidTimeRangeError] = useState<boolean>(false);
|
||||
|
||||
const timeBuckets = useTimeBuckets();
|
||||
const timeBuckets = useTimeBuckets(uiSettings);
|
||||
const timefilter = useTimefilter({ timeRangeSelector: true, autoRefreshSelector: true });
|
||||
|
||||
const { jobIds } = useJobSelection(jobsWithTimeRange);
|
||||
|
|
|
@ -14,6 +14,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { useUrlState } from '@kbn/ml-url-state';
|
||||
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||
import type { IUiSettingsClient } from '@kbn/core/public';
|
||||
import type { TimeRangeBounds } from '@kbn/ml-time-buckets';
|
||||
import { ML_PAGES } from '../../../locator';
|
||||
import { getViewableDetectors } from '../../timeseriesexplorer/timeseriesexplorer_utils/get_viewable_detectors';
|
||||
import type { NavigateToPath } from '../../contexts/kibana';
|
||||
|
@ -45,7 +46,6 @@ import { AnnotationUpdatesService } from '../../services/annotations_service';
|
|||
import { MlAnnotationUpdatesContext } from '../../contexts/ml/ml_annotation_updates_context';
|
||||
import { useTimeSeriesExplorerUrlState } from '../../timeseriesexplorer/hooks/use_timeseriesexplorer_url_state';
|
||||
import type { TimeSeriesExplorerAppState } from '../../../../common/types/locator';
|
||||
import type { TimeRangeBounds } from '../../util/time_buckets';
|
||||
import { useJobSelectionFlyout } from '../../contexts/ml/use_job_selection_flyout';
|
||||
import { useRefresh } from '../use_refresh';
|
||||
import { TimeseriesexplorerNoChartData } from '../../timeseriesexplorer/components/timeseriesexplorer_no_chart_data';
|
||||
|
|
|
@ -17,11 +17,11 @@ import type {
|
|||
MlEntityField,
|
||||
MlRecordForInfluencer,
|
||||
} from '@kbn/ml-anomaly-utils';
|
||||
import type { TimeRangeBounds } from '@kbn/ml-time-buckets';
|
||||
import type { CombinedJob } from '../../../common/types/anomaly_detection_jobs';
|
||||
import type { SeriesConfigWithMetadata } from '../../../common/types/results';
|
||||
|
||||
import type { ExplorerChartsData } from '../explorer/explorer_charts/explorer_charts_container_service';
|
||||
import type { TimeRangeBounds } from '../util/time_buckets';
|
||||
import type { AppStateSelectedCells } from '../explorer/explorer_utils';
|
||||
import { SWIM_LANE_LABEL_WIDTH } from '../explorer/swimlane_container';
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@ import type { TimefilterContract } from '@kbn/data-plugin/public';
|
|||
import { UI_SETTINGS } from '@kbn/data-plugin/public';
|
||||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
||||
import type { InfluencersFilterQuery, MlEntityField } from '@kbn/ml-anomaly-utils';
|
||||
import type { TimeBucketsInterval, TimeRangeBounds } from '../util/time_buckets';
|
||||
import { getBoundsRoundedToInterval, TimeBuckets } from '../util/time_buckets';
|
||||
import type { TimeBucketsInterval, TimeRangeBounds } from '@kbn/ml-time-buckets';
|
||||
import { getBoundsRoundedToInterval, TimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import type {
|
||||
ExplorerJob,
|
||||
OverallSwimlaneData,
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
//import mockOverallSwimlaneData from './__mocks__/mock_overall_swimlane.json';
|
||||
|
||||
import moment from 'moment-timezone';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { TimeRangeBounds } from '../util/time_buckets';
|
||||
|
||||
import type { TimeRangeBounds } from '@kbn/ml-time-buckets';
|
||||
|
||||
interface Props {
|
||||
appStateHandler: (action: string, payload: any) => void;
|
||||
|
|
|
@ -13,14 +13,9 @@ import { find, get, has, isEqual } from 'lodash';
|
|||
import moment from 'moment-timezone';
|
||||
import { Subject, Subscription, forkJoin } from 'rxjs';
|
||||
import { map, debounceTime, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { createRef, Fragment } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { context } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiCheckbox,
|
||||
|
@ -34,6 +29,11 @@ import {
|
|||
EuiBadge,
|
||||
EuiTextColor,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { context } from '@kbn/kibana-react-plugin/public';
|
||||
import { getBoundsRoundedToInterval } from '@kbn/ml-time-buckets';
|
||||
import { ResizeChecker } from '@kbn/kibana-utils-plugin/public';
|
||||
import { TimeSeriesExplorerHelpPopover } from './timeseriesexplorer_help_popover';
|
||||
|
||||
|
@ -62,8 +62,6 @@ import { timeSeriesExplorerServiceFactory } from '../util/time_series_explorer_s
|
|||
import { mlJobService } from '../services/job_service';
|
||||
import { mlResultsServiceProvider } from '../services/results_service';
|
||||
|
||||
import { getBoundsRoundedToInterval } from '../util/time_buckets';
|
||||
|
||||
import {
|
||||
APP_STATE_ACTION,
|
||||
CHARTS_POINT_TARGET,
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { UI_SETTINGS } from '@kbn/data-plugin/public';
|
||||
import { TimeBuckets } from '@kbn/ml-time-buckets';
|
||||
|
||||
import { getUiSettings } from './dependency_cache';
|
||||
|
||||
export function getTimeBucketsFromCache() {
|
||||
const uiSettings = getUiSettings();
|
||||
return new TimeBuckets({
|
||||
[UI_SETTINGS.HISTOGRAM_MAX_BARS]: uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS),
|
||||
[UI_SETTINGS.HISTOGRAM_BAR_TARGET]: uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET),
|
||||
dateFormat: uiSettings.get('dateFormat'),
|
||||
'dateFormat:scaled': uiSettings.get('dateFormat:scaled'),
|
||||
});
|
||||
}
|
|
@ -1,46 +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 type { Moment } from 'moment';
|
||||
|
||||
export interface TimeRangeBounds {
|
||||
min?: Moment;
|
||||
max?: Moment;
|
||||
}
|
||||
|
||||
export declare interface TimeBucketsInterval {
|
||||
asMilliseconds: () => number;
|
||||
asSeconds: () => number;
|
||||
expression: string;
|
||||
}
|
||||
|
||||
export interface TimeBucketsConfig {
|
||||
'histogram:maxBars': number;
|
||||
'histogram:barTarget': number;
|
||||
dateFormat: string;
|
||||
'dateFormat:scaled': string[][];
|
||||
}
|
||||
|
||||
export declare class TimeBuckets {
|
||||
constructor(timeBucketsConfig: TimeBucketsConfig);
|
||||
public setBarTarget(barTarget: number): void;
|
||||
public setMaxBars(maxBars: number): void;
|
||||
public setInterval(interval: string): void;
|
||||
public setBounds(bounds: TimeRangeBounds): void;
|
||||
public getBounds(): { min: any; max: any };
|
||||
public getInterval(): TimeBucketsInterval;
|
||||
public getIntervalToNearestMultiple(divisorSecs: any): TimeBucketsInterval;
|
||||
public getScaledDateFormat(): string;
|
||||
}
|
||||
|
||||
export declare function getTimeBucketsFromCache(): InstanceType<typeof TimeBuckets>;
|
||||
|
||||
export declare function getBoundsRoundedToInterval(
|
||||
bounds: TimeRangeBounds,
|
||||
interval: TimeBucketsInterval,
|
||||
inclusiveEnd?: boolean
|
||||
): Required<TimeRangeBounds>;
|
|
@ -9,7 +9,7 @@ import { useMemo } from 'react';
|
|||
import type { IUiSettingsClient } from '@kbn/core/public';
|
||||
import { UI_SETTINGS } from '@kbn/data-plugin/public';
|
||||
import moment from 'moment';
|
||||
import { type TimeRangeBounds, type TimeBucketsInterval, TimeBuckets } from './time_buckets';
|
||||
import { type TimeRangeBounds, type TimeBucketsInterval, TimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import { useMlKibana } from '../contexts/kibana';
|
||||
|
||||
// TODO Consolidate with legacy code in `ml/public/application/util/time_buckets.js`.
|
||||
|
|
|
@ -16,15 +16,14 @@ import { forkJoin, of } from 'rxjs';
|
|||
import { each, get } from 'lodash';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { type MlAnomalyRecordDoc } from '@kbn/ml-anomaly-utils';
|
||||
import type { TimeRangeBounds, TimeBucketsInterval } from '@kbn/ml-time-buckets';
|
||||
import { parseInterval } from '../../../common/util/parse_interval';
|
||||
import type { GetAnnotationsResponse } from '../../../common/types/annotations';
|
||||
import { mlFunctionToESAggregation } from '../../../common/util/job_utils';
|
||||
import { ANNOTATIONS_TABLE_DEFAULT_QUERY_SIZE } from '../../../common/constants/search';
|
||||
import { CHARTS_POINT_TARGET } from '../timeseriesexplorer/timeseriesexplorer_constants';
|
||||
import { timeBucketsServiceFactory } from './time_buckets_service';
|
||||
import type { TimeRangeBounds } from './time_buckets';
|
||||
import type { Job } from '../../../common/types/anomaly_detection_jobs';
|
||||
import type { TimeBucketsInterval } from './time_buckets';
|
||||
import type { CriteriaField } from '../services/results_service';
|
||||
import {
|
||||
MAX_SCHEDULED_EVENTS,
|
||||
|
|
|
@ -22,6 +22,7 @@ Object {
|
|||
"showCharts": true,
|
||||
"showSelectedInterval": false,
|
||||
"timeBuckets": TimeBuckets {
|
||||
"_fieldFormats": undefined,
|
||||
"_timeBucketsConfig": Object {
|
||||
"dateFormat": undefined,
|
||||
"dateFormat:scaled": undefined,
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
type MlEntityFieldOperation,
|
||||
ML_ANOMALY_THRESHOLD,
|
||||
} from '@kbn/ml-anomaly-utils';
|
||||
import { TimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import { useEmbeddableExecutionContext } from '../common/use_embeddable_execution_context';
|
||||
import { useAnomalyChartsInputResolver } from './use_anomaly_charts_input_resolver';
|
||||
import type { IAnomalyChartsEmbeddable } from './anomaly_charts_embeddable';
|
||||
|
@ -30,7 +31,6 @@ import type {
|
|||
import { ExplorerAnomaliesContainer } from '../../application/explorer/explorer_charts/explorer_anomalies_container';
|
||||
import { ML_APP_LOCATOR } from '../../../common/constants/locator';
|
||||
import { optionValueToThreshold } from '../../application/components/controls/select_severity/select_severity';
|
||||
import { TimeBuckets } from '../../application/util/time_buckets';
|
||||
import { EXPLORER_ENTITY_FIELD_SELECTION_TRIGGER } from '../../ui_actions/triggers';
|
||||
import type { MlLocatorParams } from '../../../common/types/locator';
|
||||
import { ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE } from '..';
|
||||
|
|
|
@ -23,7 +23,7 @@ import {
|
|||
import type { CoreStart } from '@kbn/core/public';
|
||||
import { UI_SETTINGS } from '@kbn/data-plugin/public';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { TimeBuckets } from '../../application/util/time_buckets';
|
||||
import { TimeBuckets } from '@kbn/ml-time-buckets';
|
||||
import type { MlStartDependencies } from '../../plugin';
|
||||
import type { SwimlaneType } from '../../application/explorer/explorer_constants';
|
||||
import {
|
||||
|
|
|
@ -22,8 +22,8 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { MlJob } from '@elastic/elasticsearch/lib/api/types';
|
||||
import type { TimeRangeBounds } from '@kbn/ml-time-buckets';
|
||||
import type { SingleMetricViewerServices } from '..';
|
||||
import type { TimeRangeBounds } from '../../application/util/time_buckets';
|
||||
import { SeriesControls } from '../../application/timeseriesexplorer/components/series_controls';
|
||||
import {
|
||||
APP_STATE_ACTION,
|
||||
|
|
|
@ -10,8 +10,8 @@ import type { Observable } from 'rxjs';
|
|||
import { combineLatest } from 'rxjs';
|
||||
import { startWith } from 'rxjs/operators';
|
||||
import type { TimefilterContract } from '@kbn/data-plugin/public';
|
||||
import type { TimeRangeBounds } from '@kbn/ml-time-buckets';
|
||||
import type { SingleMetricViewerEmbeddableInput } from '..';
|
||||
import type { TimeRangeBounds } from '../../application/util/time_buckets';
|
||||
|
||||
export function useSingleMetricViewerInputResolver(
|
||||
embeddableInput: Observable<SingleMetricViewerEmbeddableInput>,
|
||||
|
|
|
@ -118,5 +118,6 @@
|
|||
"@kbn/presentation-publishing",
|
||||
"@kbn/core-elasticsearch-server",
|
||||
"@kbn/core-elasticsearch-client-server-mocks",
|
||||
"@kbn/ml-time-buckets",
|
||||
],
|
||||
}
|
||||
|
|
|
@ -5291,6 +5291,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/ml-time-buckets@link:x-pack/packages/ml/time_buckets":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/ml-trained-models-utils@link:x-pack/packages/ml/trained_models_utils":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue