Skip to content

Commit

Permalink
[ML] Consolidate redundant time_buckets into @kbn/ml-time-buckets. (
Browse files Browse the repository at this point in the history
#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)
  • Loading branch information
walterra authored Mar 20, 2024
1 parent 495d7b5 commit be9ad68
Show file tree
Hide file tree
Showing 67 changed files with 358 additions and 1,311 deletions.
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

0 comments on commit be9ad68

Please sign in to comment.