Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Consolidate redundant time_buckets into @kbn/ml-time-buckets. #178756

Merged
merged 16 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
7 changes: 7 additions & 0 deletions x-pack/packages/ml/time_buckets/README.md
Original file line number Diff line number Diff line change
@@ -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 changes: 10 additions & 0 deletions x-pack/packages/ml/time_buckets/index.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 12 additions & 0 deletions x-pack/packages/ml/time_buckets/jest.config.js
Original file line number Diff line number Diff line change
@@ -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 changes: 5 additions & 0 deletions x-pack/packages/ml/time_buckets/kibana.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/ml-time-buckets",
"owner": "@elastic/ml-ui"
}
6 changes: 6 additions & 0 deletions x-pack/packages/ml/time_buckets/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@kbn/ml-time-buckets",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}
135 changes: 135 additions & 0 deletions x-pack/packages/ml/time_buckets/time_buckets.d.ts
Original file line number Diff line number Diff line change
@@ -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>;
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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];
}
Expand Down Expand Up @@ -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.
Expand All @@ -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(
{
Expand Down Expand Up @@ -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
Expand All @@ -399,7 +420,7 @@ export function calcEsInterval(duration) {

return {
value: val,
unit: unit,
unit,
expression: val + unit,
};
}
Expand Down
23 changes: 23 additions & 0 deletions x-pack/packages/ml/time_buckets/tsconfig.json
Original file line number Diff line number Diff line change
@@ -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",
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Loading