[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:
Walter Rafelsberger 2024-03-20 13:39:07 +01:00 committed by GitHub
parent 495d7b5b68
commit be9ad681ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
67 changed files with 358 additions and 1311 deletions

1
.github/CODEOWNERS vendored
View file

@ -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

View file

@ -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",

View file

@ -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"],

View 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.

View 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';

View 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'],
};

View file

@ -0,0 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/ml-time-buckets",
"owner": "@elastic/ml-ui"
}

View file

@ -0,0 +1,6 @@
{
"name": "@kbn/ml-time-buckets",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}

View 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>;

View file

@ -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,
};
}

View 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",
]
}

View file

@ -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),

View file

@ -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>;

View file

@ -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',
};
}

View file

@ -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();

View file

@ -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,

View file

@ -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());

View file

@ -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",
],

View file

@ -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>;

View file

@ -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',
};
}

View file

@ -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';

View file

@ -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,

View file

@ -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]);
};

View file

@ -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]);

View file

@ -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<

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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/**/*",

View file

@ -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';

View file

@ -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]);
};

View file

@ -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.

View file

@ -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[];

View file

@ -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;

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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 {

View file

@ -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';

View file

@ -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';

View file

@ -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();

View file

@ -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';

View file

@ -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(
() =>

View file

@ -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';

View file

@ -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>> = [
{

View file

@ -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);

View file

@ -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';

View file

@ -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';

View file

@ -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,

View file

@ -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';

View file

@ -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;

View file

@ -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,

View file

@ -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'),
});
}

View file

@ -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>;

View file

@ -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`.

View file

@ -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,

View file

@ -22,6 +22,7 @@ Object {
"showCharts": true,
"showSelectedInterval": false,
"timeBuckets": TimeBuckets {
"_fieldFormats": undefined,
"_timeBucketsConfig": Object {
"dateFormat": undefined,
"dateFormat:scaled": undefined,

View file

@ -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 '..';

View file

@ -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 {

View file

@ -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,

View file

@ -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>,

View file

@ -118,5 +118,6 @@
"@kbn/presentation-publishing",
"@kbn/core-elasticsearch-server",
"@kbn/core-elasticsearch-client-server-mocks",
"@kbn/ml-time-buckets",
],
}

View file

@ -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 ""