diff --git a/packages/grafana-data/src/datetime/rangeutil.test.ts b/packages/grafana-data/src/datetime/rangeutil.test.ts new file mode 100644 index 00000000000..63931563880 --- /dev/null +++ b/packages/grafana-data/src/datetime/rangeutil.test.ts @@ -0,0 +1,43 @@ +import { rangeUtil } from './index'; + +describe('Range Utils', () => { + describe('relative time', () => { + it('should identify absolute vs relative', () => { + expect( + rangeUtil.isRelativeTimeRange({ + from: '1234', + to: '4567', + }) + ).toBe(false); + expect( + rangeUtil.isRelativeTimeRange({ + from: 'now-5', + to: 'now', + }) + ).toBe(true); + }); + }); + + describe('describe_interval', () => { + it('falls back to seconds if input is a number', () => { + expect(rangeUtil.describeInterval('123')).toEqual({ + sec: 1, + type: 's', + count: 123, + }); + }); + + it('parses a valid time unt string correctly', () => { + expect(rangeUtil.describeInterval('123h')).toEqual({ + sec: 3600, + type: 'h', + count: 123, + }); + }); + + it('fails if input is invalid', () => { + expect(() => rangeUtil.describeInterval('123xyz')).toThrow(); + expect(() => rangeUtil.describeInterval('xyz')).toThrow(); + }); + }); +}); diff --git a/packages/grafana-data/src/datetime/rangeutil.ts b/packages/grafana-data/src/datetime/rangeutil.ts index c7cc0d84159..1360e991287 100644 --- a/packages/grafana-data/src/datetime/rangeutil.ts +++ b/packages/grafana-data/src/datetime/rangeutil.ts @@ -1,10 +1,11 @@ import each from 'lodash/each'; import groupBy from 'lodash/groupBy'; +import has from 'lodash/has'; -import { RawTimeRange, TimeRange, TimeZone } from '../types/time'; +import { RawTimeRange, TimeRange, TimeZone, IntervalValues } from '../types/time'; import * as dateMath from './datemath'; -import { isDateTime } from './moment_wrapper'; +import { isDateTime, DateTime } from './moment_wrapper'; import { timeZoneAbbrevation, dateTimeFormat, dateTimeFormatTimeAgo } from './formatter'; import { dateTimeParse } from './parser'; @@ -221,3 +222,198 @@ export const convertRawToRange = (raw: RawTimeRange, timeZone?: TimeZone): TimeR return { from, to, raw: { from, to } }; }; + +function isRelativeTime(v: DateTime | string) { + if (typeof v === 'string') { + return (v as string).indexOf('now') >= 0; + } + return false; +} + +export function isRelativeTimeRange(raw: RawTimeRange): boolean { + return isRelativeTime(raw.from) || isRelativeTime(raw.to); +} + +export function secondsToHms(seconds: number): string { + const numYears = Math.floor(seconds / 31536000); + if (numYears) { + return numYears + 'y'; + } + const numDays = Math.floor((seconds % 31536000) / 86400); + if (numDays) { + return numDays + 'd'; + } + const numHours = Math.floor(((seconds % 31536000) % 86400) / 3600); + if (numHours) { + return numHours + 'h'; + } + const numMinutes = Math.floor((((seconds % 31536000) % 86400) % 3600) / 60); + if (numMinutes) { + return numMinutes + 'm'; + } + const numSeconds = Math.floor((((seconds % 31536000) % 86400) % 3600) % 60); + if (numSeconds) { + return numSeconds + 's'; + } + const numMilliseconds = Math.floor(seconds * 1000.0); + if (numMilliseconds) { + return numMilliseconds + 'ms'; + } + + return 'less than a millisecond'; //'just now' //or other string you like; +} + +export function calculateInterval(range: TimeRange, resolution: number, lowLimitInterval?: string): IntervalValues { + let lowLimitMs = 1; // 1 millisecond default low limit + if (lowLimitInterval) { + lowLimitMs = intervalToMs(lowLimitInterval); + } + + let intervalMs = roundInterval((range.to.valueOf() - range.from.valueOf()) / resolution); + if (lowLimitMs > intervalMs) { + intervalMs = lowLimitMs; + } + return { + intervalMs: intervalMs, + interval: secondsToHms(intervalMs / 1000), + }; +} + +const interval_regex = /(\d+(?:\.\d+)?)(ms|[Mwdhmsy])/; +// histogram & trends +const intervals_in_seconds = { + y: 31536000, + M: 2592000, + w: 604800, + d: 86400, + h: 3600, + m: 60, + s: 1, + ms: 0.001, +}; + +export function describeInterval(str: string) { + // Default to seconds if no unit is provided + if (Number(str)) { + return { + sec: intervals_in_seconds.s, + type: 's', + count: parseInt(str, 10), + }; + } + + const matches = str.match(interval_regex); + if (!matches || !has(intervals_in_seconds, matches[2])) { + throw new Error( + `Invalid interval string, has to be either unit-less or end with one of the following units: "${Object.keys( + intervals_in_seconds + ).join(', ')}"` + ); + } + return { + sec: (intervals_in_seconds as any)[matches[2]] as number, + type: matches[2], + count: parseInt(matches[1], 10), + }; +} + +export function intervalToSeconds(str: string): number { + const info = describeInterval(str); + return info.sec * info.count; +} + +export function intervalToMs(str: string): number { + const info = describeInterval(str); + return info.sec * 1000 * info.count; +} + +export function roundInterval(interval: number) { + switch (true) { + // 0.015s + case interval < 15: + return 10; // 0.01s + // 0.035s + case interval < 35: + return 20; // 0.02s + // 0.075s + case interval < 75: + return 50; // 0.05s + // 0.15s + case interval < 150: + return 100; // 0.1s + // 0.35s + case interval < 350: + return 200; // 0.2s + // 0.75s + case interval < 750: + return 500; // 0.5s + // 1.5s + case interval < 1500: + return 1000; // 1s + // 3.5s + case interval < 3500: + return 2000; // 2s + // 7.5s + case interval < 7500: + return 5000; // 5s + // 12.5s + case interval < 12500: + return 10000; // 10s + // 17.5s + case interval < 17500: + return 15000; // 15s + // 25s + case interval < 25000: + return 20000; // 20s + // 45s + case interval < 45000: + return 30000; // 30s + // 1.5m + case interval < 90000: + return 60000; // 1m + // 3.5m + case interval < 210000: + return 120000; // 2m + // 7.5m + case interval < 450000: + return 300000; // 5m + // 12.5m + case interval < 750000: + return 600000; // 10m + // 12.5m + case interval < 1050000: + return 900000; // 15m + // 25m + case interval < 1500000: + return 1200000; // 20m + // 45m + case interval < 2700000: + return 1800000; // 30m + // 1.5h + case interval < 5400000: + return 3600000; // 1h + // 2.5h + case interval < 9000000: + return 7200000; // 2h + // 4.5h + case interval < 16200000: + return 10800000; // 3h + // 9h + case interval < 32400000: + return 21600000; // 6h + // 1d + case interval < 86400000: + return 43200000; // 12h + // 1w + case interval < 604800000: + return 86400000; // 1d + // 3w + case interval < 1814400000: + return 604800000; // 1w + // 6w + case interval < 3628800000: + return 2592000000; // 30d + default: + return 31536000000; // 1y + } +} diff --git a/public/app/core/services/context_srv.ts b/public/app/core/services/context_srv.ts index 0b2d5ec4a98..16f566ffdd5 100644 --- a/public/app/core/services/context_srv.ts +++ b/public/app/core/services/context_srv.ts @@ -1,7 +1,7 @@ import config from '../../core/config'; import _ from 'lodash'; import coreModule from 'app/core/core_module'; -import kbn from '../utils/kbn'; +import { rangeUtil } from '@grafana/data'; export class User { id: number; @@ -61,7 +61,7 @@ export class ContextSrv { if (!config.minRefreshInterval) { return true; } - return kbn.intervalToMs(interval) >= kbn.intervalToMs(config.minRefreshInterval); + return rangeUtil.intervalToMs(interval) >= rangeUtil.intervalToMs(config.minRefreshInterval); } getValidInterval(interval: string) { diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index 15961d62ffd..f5d3eed981b 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -22,9 +22,9 @@ import { toUtc, urlUtil, ExploreUrlState, + rangeUtil, } from '@grafana/data'; import store from 'app/core/store'; -import kbn from 'app/core/utils/kbn'; import { v4 as uuidv4 } from 'uuid'; import { getNextRefIdChar } from './query'; // Types @@ -488,7 +488,7 @@ export function getIntervals(range: TimeRange, lowLimit?: string, resolution?: n return { interval: '1s', intervalMs: 1000 }; } - return kbn.calculateInterval(range, resolution, lowLimit); + return rangeUtil.calculateInterval(range, resolution, lowLimit); } export function deduplicateLogRowsById(rows: LogRowModel[]) { diff --git a/public/app/core/utils/kbn.ts b/public/app/core/utils/kbn.ts index cc991b12fd5..255282c3184 100644 --- a/public/app/core/utils/kbn.ts +++ b/public/app/core/utils/kbn.ts @@ -8,8 +8,8 @@ import { stringToJsRegex, TimeRange, ValueFormatterIndex, + rangeUtil, } from '@grafana/data'; -import { has } from 'lodash'; const kbn = { valueFormats: {} as ValueFormatterIndex, @@ -25,123 +25,16 @@ const kbn = { ms: 0.001, } as { [index: string]: number }, regexEscape: (value: string) => value.replace(/[\\^$*+?.()|[\]{}\/]/g, '\\$&'), - roundInterval: (interval: number) => { - switch (true) { - // 0.015s - case interval < 15: - return 10; // 0.01s - // 0.035s - case interval < 35: - return 20; // 0.02s - // 0.075s - case interval < 75: - return 50; // 0.05s - // 0.15s - case interval < 150: - return 100; // 0.1s - // 0.35s - case interval < 350: - return 200; // 0.2s - // 0.75s - case interval < 750: - return 500; // 0.5s - // 1.5s - case interval < 1500: - return 1000; // 1s - // 3.5s - case interval < 3500: - return 2000; // 2s - // 7.5s - case interval < 7500: - return 5000; // 5s - // 12.5s - case interval < 12500: - return 10000; // 10s - // 17.5s - case interval < 17500: - return 15000; // 15s - // 25s - case interval < 25000: - return 20000; // 20s - // 45s - case interval < 45000: - return 30000; // 30s - // 1.5m - case interval < 90000: - return 60000; // 1m - // 3.5m - case interval < 210000: - return 120000; // 2m - // 7.5m - case interval < 450000: - return 300000; // 5m - // 12.5m - case interval < 750000: - return 600000; // 10m - // 12.5m - case interval < 1050000: - return 900000; // 15m - // 25m - case interval < 1500000: - return 1200000; // 20m - // 45m - case interval < 2700000: - return 1800000; // 30m - // 1.5h - case interval < 5400000: - return 3600000; // 1h - // 2.5h - case interval < 9000000: - return 7200000; // 2h - // 4.5h - case interval < 16200000: - return 10800000; // 3h - // 9h - case interval < 32400000: - return 21600000; // 6h - // 1d - case interval < 86400000: - return 43200000; // 12h - // 1w - case interval < 604800000: - return 86400000; // 1d - // 3w - case interval < 1814400000: - return 604800000; // 1w - // 6w - case interval < 3628800000: - return 2592000000; // 30d - default: - return 31536000000; // 1y - } - }, - secondsToHms: (seconds: number) => { - const numYears = Math.floor(seconds / 31536000); - if (numYears) { - return numYears + 'y'; - } - const numDays = Math.floor((seconds % 31536000) / 86400); - if (numDays) { - return numDays + 'd'; - } - const numHours = Math.floor(((seconds % 31536000) % 86400) / 3600); - if (numHours) { - return numHours + 'h'; - } - const numMinutes = Math.floor((((seconds % 31536000) % 86400) % 3600) / 60); - if (numMinutes) { - return numMinutes + 'm'; - } - const numSeconds = Math.floor((((seconds % 31536000) % 86400) % 3600) % 60); - if (numSeconds) { - return numSeconds + 's'; - } - const numMilliseconds = Math.floor(seconds * 1000.0); - if (numMilliseconds) { - return numMilliseconds + 'ms'; - } - return 'less than a millisecond'; //'just now' //or other string you like; + /** @deprecated since 7.2, use grafana/data */ + roundInterval: (interval: number) => { + deprecationWarning('kbn.ts', 'kbn.roundInterval()', '@grafana/data'); + return rangeUtil.roundInterval(interval); + }, + /** @deprecated since 7.2, use grafana/data */ + secondsToHms: (s: number) => { + deprecationWarning('kbn.ts', 'kbn.secondsToHms()', '@grafana/data'); + return rangeUtil.secondsToHms(s); }, secondsToHhmmss: (seconds: number) => { const strings: string[] = []; @@ -161,59 +54,25 @@ const kbn = { str = str.replace(/\0/g, '\\0'); return str; }, + /** @deprecated since 7.2, use grafana/data */ describeInterval: (str: string) => { - // Default to seconds if no unit is provided - if (Number(str)) { - return { - sec: kbn.intervalsInSeconds.s, - type: 's', - count: parseInt(str, 10), - }; - } - - const matches = str.match(kbn.intervalRegex); - if (!matches || !has(kbn.intervalsInSeconds, matches[2])) { - throw new Error( - `Invalid interval string, has to be either unit-less or end with one of the following units: "${Object.keys( - kbn.intervalsInSeconds - ).join(', ')}"` - ); - } else { - return { - sec: kbn.intervalsInSeconds[matches[2]], - type: matches[2], - count: parseInt(matches[1], 10), - }; - } + deprecationWarning('kbn.ts', 'kbn.stringToJsRegex()', '@grafana/data'); + return rangeUtil.describeInterval(str); }, - intervalToSeconds: (str: string): number => { - const info = kbn.describeInterval(str); - return info.sec * info.count; + /** @deprecated since 7.2, use grafana/data */ + intervalToSeconds: (str: string) => { + deprecationWarning('kbn.ts', 'rangeUtil.intervalToSeconds()', '@grafana/data'); + return rangeUtil.intervalToSeconds(str); }, + /** @deprecated since 7.2, use grafana/data */ intervalToMs: (str: string) => { - const info = kbn.describeInterval(str); - return info.sec * 1000 * info.count; + deprecationWarning('kbn.ts', 'rangeUtil.intervalToMs()', '@grafana/data'); + return rangeUtil.intervalToMs(str); }, + /** @deprecated since 7.2, use grafana/data */ calculateInterval: (range: TimeRange, resolution: number, lowLimitInterval?: string) => { - let lowLimitMs = 1; // 1 millisecond default low limit - let intervalMs; - - if (lowLimitInterval) { - if (lowLimitInterval[0] === '>') { - lowLimitInterval = lowLimitInterval.slice(1); - } - lowLimitMs = kbn.intervalToMs(lowLimitInterval); - } - - intervalMs = kbn.roundInterval((range.to.valueOf() - range.from.valueOf()) / resolution); - if (lowLimitMs > intervalMs) { - intervalMs = lowLimitMs; - } - - return { - intervalMs: intervalMs, - interval: kbn.secondsToHms(intervalMs / 1000), - }; + deprecationWarning('kbn.ts', 'kbn.calculateInterval()', '@grafana/data'); + return rangeUtil.calculateInterval(range, resolution, lowLimitInterval); }, queryColorDot: (color: string, diameter: string) => { return ( @@ -228,7 +87,7 @@ const kbn = { .replace(/[^\w ]+/g, '') .replace(/ +/g, '-'); }, - /** deprecated since 6.1, use grafana/data */ + /** @deprecated since 6.1, use grafana/data */ stringToJsRegex: (str: string) => { deprecationWarning('kbn.ts', 'kbn.stringToJsRegex()', '@grafana/data'); return stringToJsRegex(str); diff --git a/public/app/features/alerting/AlertTabCtrl.ts b/public/app/features/alerting/AlertTabCtrl.ts index f38ce575031..775cc665348 100644 --- a/public/app/features/alerting/AlertTabCtrl.ts +++ b/public/app/features/alerting/AlertTabCtrl.ts @@ -8,11 +8,10 @@ import appEvents from 'app/core/app_events'; import { getBackendSrv } from '@grafana/runtime'; import { DashboardSrv } from '../dashboard/services/DashboardSrv'; import DatasourceSrv from '../plugins/datasource_srv'; -import { DataQuery, DataSourceApi } from '@grafana/data'; +import { DataQuery, DataSourceApi, rangeUtil } from '@grafana/data'; import { PanelModel } from 'app/features/dashboard/state'; import { getDefaultCondition } from './getAlertingValidationMessage'; import { CoreEvents } from 'app/types'; -import kbn from 'app/core/utils/kbn'; import { promiseToDigest } from 'app/core/utils/promiseToDigest'; export class AlertTabCtrl { @@ -56,7 +55,7 @@ export class AlertTabCtrl { this.appSubUrl = config.appSubUrl; this.panelCtrl._enableAlert = this.enable; this.alertingMinIntervalSecs = config.alertingMinInterval; - this.alertingMinInterval = kbn.secondsToHms(config.alertingMinInterval); + this.alertingMinInterval = rangeUtil.secondsToHms(config.alertingMinInterval); } $onInit() { @@ -253,7 +252,7 @@ export class AlertTabCtrl { this.frequencyWarning = ''; try { - const frequencySecs = kbn.intervalToSeconds(this.alert.frequency); + const frequencySecs = rangeUtil.intervalToSeconds(this.alert.frequency); if (frequencySecs < this.alertingMinIntervalSecs) { this.frequencyWarning = 'A minimum evaluation interval of ' + diff --git a/public/app/features/api-keys/ApiKeysPage.tsx b/public/app/features/api-keys/ApiKeysPage.tsx index 37a85290326..faa954b1754 100644 --- a/public/app/features/api-keys/ApiKeysPage.tsx +++ b/public/app/features/api-keys/ApiKeysPage.tsx @@ -22,10 +22,9 @@ import { IconButton, } from '@grafana/ui'; const { Input, Switch } = LegacyForms; -import { NavModel, dateTimeFormat } from '@grafana/data'; +import { NavModel, dateTimeFormat, rangeUtil } from '@grafana/data'; import { FilterInput } from 'app/core/components/FilterInput/FilterInput'; import { store } from 'app/store/store'; -import kbn from 'app/core/utils/kbn'; import { getTimeZone } from 'app/features/profile/state/selectors'; import { setSearchQuery } from './state/reducers'; @@ -37,7 +36,7 @@ const timeRangeValidationEvents: ValidationEvents = { return true; } try { - kbn.intervalToSeconds(value); + rangeUtil.intervalToSeconds(value); return true; } catch { return false; @@ -125,7 +124,7 @@ export class ApiKeysPage extends PureComponent { // make sure that secondsToLive is number or null const secondsToLive = this.state.newApiKey['secondsToLive']; - this.state.newApiKey['secondsToLive'] = secondsToLive ? kbn.intervalToSeconds(secondsToLive) : null; + this.state.newApiKey['secondsToLive'] = secondsToLive ? rangeUtil.intervalToSeconds(secondsToLive) : null; this.props.addApiKey(this.state.newApiKey, openModal, this.props.includeExpired); this.setState((prevState: State) => { return { diff --git a/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.tsx index d62042e722a..ce0c300e735 100644 --- a/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.tsx @@ -3,7 +3,6 @@ import { TimeZonePicker, Input, Tooltip, LegacyForms } from '@grafana/ui'; import { DashboardModel } from '../../state/DashboardModel'; import { TimeZone, rangeUtil } from '@grafana/data'; import { config } from '@grafana/runtime'; -import kbn from 'app/core/utils/kbn'; import isEmpty from 'lodash/isEmpty'; import { selectors } from '@grafana/e2e-selectors'; @@ -39,7 +38,7 @@ export class TimePickerSettings extends PureComponent { if (config.minRefreshInterval) { intervals = intervals.filter(rate => { - return kbn.intervalToMs(rate) >= kbn.intervalToMs(config.minRefreshInterval); + return rangeUtil.intervalToMs(rate) >= rangeUtil.intervalToMs(config.minRefreshInterval); }); } diff --git a/public/app/features/dashboard/services/TimeSrv.ts b/public/app/features/dashboard/services/TimeSrv.ts index 44a9c58603e..4c0bd4a133e 100644 --- a/public/app/features/dashboard/services/TimeSrv.ts +++ b/public/app/features/dashboard/services/TimeSrv.ts @@ -1,10 +1,18 @@ // Libraries import _ from 'lodash'; // Utils -import kbn from 'app/core/utils/kbn'; import coreModule from 'app/core/core_module'; // Types -import { dateMath, DefaultTimeRange, TimeRange, RawTimeRange, toUtc, dateTime, isDateTime } from '@grafana/data'; +import { + dateMath, + DefaultTimeRange, + TimeRange, + RawTimeRange, + toUtc, + dateTime, + isDateTime, + rangeUtil, +} from '@grafana/data'; import { ITimeoutService, ILocationService } from 'angular'; import { ContextSrv } from 'app/core/services/context_srv'; import { DashboardModel } from '../state/DashboardModel'; @@ -115,7 +123,7 @@ export class TimeSrv { // when time window specified in ms timeWindowMs = parseInt(timeWindow, 10); } else { - timeWindowMs = kbn.intervalToMs(timeWindow); + timeWindowMs = rangeUtil.intervalToMs(timeWindow); } return { @@ -181,7 +189,7 @@ export class TimeSrv { if (interval) { const validInterval = this.contextSrv.getValidInterval(interval); - const intervalMs = kbn.intervalToMs(validInterval); + const intervalMs = rangeUtil.intervalToMs(validInterval); this.refreshTimer = this.timer.register( this.$timeout(() => { diff --git a/public/app/features/dashboard/state/PanelQueryRunner.ts b/public/app/features/dashboard/state/PanelQueryRunner.ts index f0f8ea62bb1..30d3d935986 100644 --- a/public/app/features/dashboard/state/PanelQueryRunner.ts +++ b/public/app/features/dashboard/state/PanelQueryRunner.ts @@ -5,7 +5,6 @@ import { map } from 'rxjs/operators'; // Services & Utils import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; -import kbn from 'app/core/utils/kbn'; import templateSrv from 'app/features/templating/template_srv'; import { runRequest, preProcessPanelData } from './runRequest'; import { runSharedRequest, isSharedDashboardQuery } from '../../../plugins/datasource/dashboard'; @@ -26,6 +25,7 @@ import { DataConfigSource, TimeZone, LoadingState, + rangeUtil, } from '@grafana/data'; export interface QueryRunnerOptions< @@ -163,7 +163,7 @@ export class PanelQueryRunner { }); const lowerIntervalLimit = minInterval ? templateSrv.replace(minInterval, request.scopedVars) : ds.interval; - const norm = kbn.calculateInterval(timeRange, maxDataPoints, lowerIntervalLimit); + const norm = rangeUtil.calculateInterval(timeRange, maxDataPoints, lowerIntervalLimit); // make shallow copy of scoped vars, // and add built in variables interval and interval_ms diff --git a/public/app/features/playlist/playlist_srv.ts b/public/app/features/playlist/playlist_srv.ts index 55c92eb22a2..2f006a05405 100644 --- a/public/app/features/playlist/playlist_srv.ts +++ b/public/app/features/playlist/playlist_srv.ts @@ -5,11 +5,10 @@ import _ from 'lodash'; import coreModule from '../../core/core_module'; import appEvents from 'app/core/app_events'; -import kbn from 'app/core/utils/kbn'; import { store } from 'app/store/store'; import { CoreEvents } from 'app/types'; import { getBackendSrv } from '@grafana/runtime'; -import { locationUtil, urlUtil } from '@grafana/data'; +import { locationUtil, urlUtil, rangeUtil } from '@grafana/data'; export const queryParamsToPreserve: { [key: string]: boolean } = { kiosk: true, @@ -97,7 +96,7 @@ export class PlaylistSrv { .get(`/api/playlists/${playlistId}/dashboards`) .then((dashboards: any) => { this.dashboards = dashboards; - this.interval = kbn.intervalToMs(playlist.interval); + this.interval = rangeUtil.intervalToMs(playlist.interval); this.next(); }); }); diff --git a/public/app/features/variables/interval/actions.test.ts b/public/app/features/variables/interval/actions.test.ts index 8914648c3f5..ca9782020c6 100644 --- a/public/app/features/variables/interval/actions.test.ts +++ b/public/app/features/variables/interval/actions.test.ts @@ -97,9 +97,7 @@ describe('interval actions', () => { .build(); const dependencies: UpdateAutoValueDependencies = { - kbn: { - calculateInterval: jest.fn(), - } as any, + calculateInterval: jest.fn(), getTimeSrv: () => { return ({ timeRange: jest.fn().mockReturnValue({ @@ -124,7 +122,7 @@ describe('interval actions', () => { ) .whenAsyncActionIsDispatched(updateAutoValue(toVariableIdentifier(interval), dependencies), true); - expect(dependencies.kbn.calculateInterval).toHaveBeenCalledTimes(0); + expect(dependencies.calculateInterval).toHaveBeenCalledTimes(0); expect(dependencies.getTimeSrv().timeRange).toHaveBeenCalledTimes(0); expect(dependencies.templateSrv.setGrafanaVariable).toHaveBeenCalledTimes(0); }); @@ -150,9 +148,7 @@ describe('interval actions', () => { }); const setGrafanaVariableMock = jest.fn(); const dependencies: UpdateAutoValueDependencies = { - kbn: { - calculateInterval: jest.fn().mockReturnValue({ interval: '10s' }), - } as any, + calculateInterval: jest.fn().mockReturnValue({ interval: '10s' }), getTimeSrv: () => { return ({ timeRange: timeRangeMock, @@ -170,8 +166,8 @@ describe('interval actions', () => { ) .whenAsyncActionIsDispatched(updateAutoValue(toVariableIdentifier(interval), dependencies), true); - expect(dependencies.kbn.calculateInterval).toHaveBeenCalledTimes(1); - expect(dependencies.kbn.calculateInterval).toHaveBeenCalledWith( + expect(dependencies.calculateInterval).toHaveBeenCalledTimes(1); + expect(dependencies.calculateInterval).toHaveBeenCalledWith( { from: '2001-01-01', to: '2001-01-02', diff --git a/public/app/features/variables/interval/actions.ts b/public/app/features/variables/interval/actions.ts index 3c804290b08..c8adfac38a2 100644 --- a/public/app/features/variables/interval/actions.ts +++ b/public/app/features/variables/interval/actions.ts @@ -1,4 +1,4 @@ -import { AppEvents } from '@grafana/data'; +import { AppEvents, rangeUtil } from '@grafana/data'; import { toVariablePayload, VariableIdentifier } from '../state/types'; import { ThunkResult } from '../../../types'; @@ -6,7 +6,6 @@ import { createIntervalOptions } from './reducer'; import { validateVariableSelectionState } from '../state/actions'; import { getVariable } from '../state/selectors'; import { IntervalVariableModel } from '../types'; -import kbn from '../../../core/utils/kbn'; import { getTimeSrv } from '../../dashboard/services/TimeSrv'; import templateSrv from '../../templating/template_srv'; import appEvents from '../../../core/app_events'; @@ -29,7 +28,7 @@ export const updateIntervalVariableOptions = ( }; export interface UpdateAutoValueDependencies { - kbn: typeof kbn; + calculateInterval: typeof rangeUtil.calculateInterval; getTimeSrv: typeof getTimeSrv; templateSrv: typeof templateSrv; } @@ -37,14 +36,14 @@ export interface UpdateAutoValueDependencies { export const updateAutoValue = ( identifier: VariableIdentifier, dependencies: UpdateAutoValueDependencies = { - kbn: kbn, + calculateInterval: rangeUtil.calculateInterval, getTimeSrv: getTimeSrv, templateSrv: templateSrv, } ): ThunkResult => (dispatch, getState) => { const variableInState = getVariable(identifier.id, getState()); if (variableInState.auto) { - const res = dependencies.kbn.calculateInterval( + const res = dependencies.calculateInterval( dependencies.getTimeSrv().timeRange(), variableInState.auto_count, variableInState.auto_min diff --git a/public/app/plugins/datasource/cloud-monitoring/components/AlignmentPeriods.tsx b/public/app/plugins/datasource/cloud-monitoring/components/AlignmentPeriods.tsx index 49279a38efa..fcff3a1d135 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/AlignmentPeriods.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/AlignmentPeriods.tsx @@ -2,8 +2,7 @@ import React, { FC } from 'react'; import _ from 'lodash'; import { TemplateSrv } from 'app/features/templating/template_srv'; -import kbn from 'app/core/utils/kbn'; -import { SelectableValue } from '@grafana/data'; +import { SelectableValue, rangeUtil } from '@grafana/data'; import { Segment } from '@grafana/ui'; import { alignmentPeriods, alignOptions } from '../constants'; @@ -26,7 +25,7 @@ export const AlignmentPeriods: FC = ({ }) => { const alignment = alignOptions.find(ap => ap.value === templateSrv.replace(perSeriesAligner)); const formatAlignmentText = usedAlignmentPeriod - ? `${kbn.secondsToHms(usedAlignmentPeriod)} interval (${alignment ? alignment.text : ''})` + ? `${rangeUtil.secondsToHms(usedAlignmentPeriod)} interval (${alignment ? alignment.text : ''})` : ''; const options = alignmentPeriods.map(ap => ({ ...ap, diff --git a/public/app/plugins/datasource/cloudwatch/datasource.ts b/public/app/plugins/datasource/cloudwatch/datasource.ts index ebd66f0bc17..3f3eb208b3d 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.ts @@ -5,7 +5,6 @@ import { notifyApp } from 'app/core/actions'; import { createErrorNotification } from 'app/core/copy/appNotification'; import { AppNotificationTimeout } from 'app/types'; import { store } from 'app/store/store'; -import kbn from 'app/core/utils/kbn'; import { DataFrame, DataQueryRequest, @@ -18,6 +17,7 @@ import { ScopedVars, TimeRange, toDataFrame, + rangeUtil, } from '@grafana/data'; import { getBackendSrv, toDataQueryResponse } from '@grafana/runtime'; import { TemplateSrv } from 'app/features/templating/template_srv'; @@ -420,7 +420,7 @@ export class CloudWatchDatasource extends DataSourceApi { if (tg.value !== 'auto') { - allowedTimeGrainsMs.push(kbn.intervalToMs(TimegrainConverter.createKbnUnitFromISO8601Duration(tg.value))); + allowedTimeGrainsMs.push(rangeUtil.intervalToMs(TimegrainConverter.createKbnUnitFromISO8601Duration(tg.value))); } }); return allowedTimeGrainsMs; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/time_grain_converter.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/time_grain_converter.ts index 80f4265e1c2..a63c87d5b86 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/time_grain_converter.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/time_grain_converter.ts @@ -1,5 +1,5 @@ import _ from 'lodash'; -import kbn from 'app/core/utils/kbn'; +import { rangeUtil } from '@grafana/data'; export default class TimeGrainConverter { static createISO8601Duration(timeGrain: string | number, timeGrainUnit: any) { @@ -36,11 +36,11 @@ export default class TimeGrainConverter { const timeGrains = _.filter(allowedTimeGrains, o => o !== 'auto'); let closest = timeGrains[0]; - const intervalMs = kbn.intervalToMs(interval); + const intervalMs = rangeUtil.intervalToMs(interval); for (let i = 0; i < timeGrains.length; i++) { // abs (num - val) < abs (num - curr): - if (intervalMs > kbn.intervalToMs(timeGrains[i])) { + if (intervalMs > rangeUtil.intervalToMs(timeGrains[i])) { if (i + 1 < timeGrains.length) { closest = timeGrains[i + 1]; } else { diff --git a/public/app/plugins/datasource/graphite/MetricTankMetaInspector.tsx b/public/app/plugins/datasource/graphite/MetricTankMetaInspector.tsx index e768b9d7576..946211365f9 100644 --- a/public/app/plugins/datasource/graphite/MetricTankMetaInspector.tsx +++ b/public/app/plugins/datasource/graphite/MetricTankMetaInspector.tsx @@ -1,12 +1,11 @@ import { css, cx } from 'emotion'; import React, { PureComponent } from 'react'; -import { MetadataInspectorProps } from '@grafana/data'; +import { MetadataInspectorProps, rangeUtil } from '@grafana/data'; import { GraphiteDatasource } from './datasource'; import { GraphiteQuery, GraphiteOptions, MetricTankSeriesMeta } from './types'; import { parseSchemaRetentions, getRollupNotice, getRuntimeConsolidationNotice } from './meta'; import { stylesFactory } from '@grafana/ui'; import { config } from 'app/core/config'; -import kbn from 'app/core/utils/kbn'; export type Props = MetadataInspectorProps; @@ -23,7 +22,7 @@ export class MetricTankMetaInspector extends PureComponent { const normFunc = (meta['consolidator-normfetch'] ?? '').replace('Consolidator', ''); const totalSeconds = buckets.reduce( - (acc, bucket) => acc + (bucket.retention ? kbn.intervalToSeconds(bucket.retention) : 0), + (acc, bucket) => acc + (bucket.retention ? rangeUtil.intervalToSeconds(bucket.retention) : 0), 0 ); @@ -45,7 +44,7 @@ export class MetricTankMetaInspector extends PureComponent {
{buckets.map((bucket, index) => { - const bucketLength = bucket.retention ? kbn.intervalToSeconds(bucket.retention) : 0; + const bucketLength = bucket.retention ? rangeUtil.intervalToSeconds(bucket.retention) : 0; const lengthPercent = (bucketLength / totalSeconds) * 100; const isActive = index === meta['archive-read']; diff --git a/public/app/plugins/datasource/opentsdb/query_ctrl.ts b/public/app/plugins/datasource/opentsdb/query_ctrl.ts index 3954e564a1c..9f2284d966a 100644 --- a/public/app/plugins/datasource/opentsdb/query_ctrl.ts +++ b/public/app/plugins/datasource/opentsdb/query_ctrl.ts @@ -1,8 +1,7 @@ import _ from 'lodash'; -import kbn from 'app/core/utils/kbn'; import { QueryCtrl } from 'app/plugins/sdk'; import { auto } from 'angular'; -import { textUtil } from '@grafana/data'; +import { textUtil, rangeUtil } from '@grafana/data'; export class OpenTsQueryCtrl extends QueryCtrl { static templateUrl = 'partials/query.editor.html'; @@ -205,7 +204,7 @@ export class OpenTsQueryCtrl extends QueryCtrl { if (this.target.shouldDownsample) { try { if (this.target.downsampleInterval) { - kbn.describeInterval(this.target.downsampleInterval); + rangeUtil.describeInterval(this.target.downsampleInterval); } else { errs.downsampleInterval = "You must supply a downsample interval (e.g. '1m' or '1h')."; } diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 0e3cdc4d65f..83679c18616 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -1,7 +1,6 @@ // Libraries import cloneDeep from 'lodash/cloneDeep'; // Services & Utils -import kbn from 'app/core/utils/kbn'; import { AnnotationEvent, CoreApp, @@ -17,6 +16,7 @@ import { ScopedVars, TimeRange, TimeSeries, + rangeUtil, } from '@grafana/data'; import { forkJoin, merge, Observable, of, throwError } from 'rxjs'; import { catchError, filter, map, tap } from 'rxjs/operators'; @@ -345,13 +345,13 @@ export class PrometheusDatasource extends DataSourceApi const range = Math.ceil(end - start); // options.interval is the dynamically calculated interval - let interval: number = kbn.intervalToSeconds(options.interval); + let interval: number = rangeUtil.intervalToSeconds(options.interval); // Minimum interval ("Min step"), if specified for the query, or same as interval otherwise. - const minInterval = kbn.intervalToSeconds( + const minInterval = rangeUtil.intervalToSeconds( templateSrv.replace(target.interval || options.interval, options.scopedVars) ); // Scrape interval as specified for the query ("Min step") or otherwise taken from the datasource. - const scrapeInterval = kbn.intervalToSeconds(target.interval || this.interval); + const scrapeInterval = rangeUtil.intervalToSeconds(target.interval || this.interval); const intervalFactor = target.intervalFactor || 1; // Adjust the interval to take into account any specified minimum and interval factor plus Prometheus limits const adjustedInterval = this.adjustInterval(interval, minInterval, range, intervalFactor); @@ -533,7 +533,7 @@ export class PrometheusDatasource extends DataSourceApi const scopedVars = { __interval: { text: this.interval, value: this.interval }, - __interval_ms: { text: kbn.intervalToMs(this.interval), value: kbn.intervalToMs(this.interval) }, + __interval_ms: { text: rangeUtil.intervalToMs(this.interval), value: rangeUtil.intervalToMs(this.interval) }, ...this.getRangeScopedVars(getTimeSrv().timeRange()), }; const interpolated = templateSrv.replace(query, scopedVars, this.interpolateQueryExpr); diff --git a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts index 3dbe32abc41..6038f905106 100644 --- a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts +++ b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts @@ -15,7 +15,7 @@ import { import { auto } from 'angular'; import { getProcessedDataFrames } from 'app/features/dashboard/state/runRequest'; import { DataProcessor } from '../graph/data_processor'; -import { LegacyResponseData, PanelEvents, DataFrame } from '@grafana/data'; +import { LegacyResponseData, PanelEvents, DataFrame, rangeUtil } from '@grafana/data'; import { CoreEvents } from 'app/types'; const X_BUCKET_NUMBER_DEFAULT = 30; @@ -182,7 +182,7 @@ export class HeatmapCtrl extends MetricsPanelCtrl { // Parse X bucket size (number or interval) const isIntervalString = kbn.intervalRegex.test(this.panel.xBucketSize); if (isIntervalString) { - xBucketSize = kbn.intervalToMs(this.panel.xBucketSize); + xBucketSize = rangeUtil.intervalToMs(this.panel.xBucketSize); } else if ( isNaN(Number(this.panel.xBucketSize)) || this.panel.xBucketSize === '' ||