Graphite: Fix date mutation (#107414)

* Fix date mutation

- Add unit test
- Improve types

* Add invalid test and minor cleanup

* Review
This commit is contained in:
Andreas Christou
2025-07-02 16:11:32 +02:00
committed by GitHub
parent bae8d98a9f
commit a01b1536f9
3 changed files with 61 additions and 18 deletions

View File

@ -3699,8 +3699,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "15"], [0, 0, 0, "Unexpected any. Specify a different type.", "15"],
[0, 0, 0, "Unexpected any. Specify a different type.", "16"], [0, 0, 0, "Unexpected any. Specify a different type.", "16"],
[0, 0, 0, "Unexpected any. Specify a different type.", "17"], [0, 0, 0, "Unexpected any. Specify a different type.", "17"],
[0, 0, 0, "Unexpected any. Specify a different type.", "18"], [0, 0, 0, "Unexpected any. Specify a different type.", "18"]
[0, 0, 0, "Unexpected any. Specify a different type.", "19"]
], ],
"public/app/plugins/datasource/graphite/gfunc.ts:5381": [ "public/app/plugins/datasource/graphite/gfunc.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "0"],

View File

@ -1,4 +1,5 @@
import { isArray } from 'lodash'; import { isArray } from 'lodash';
import moment from 'moment';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { createFetchResponse } from 'test/helpers/createFetchResponse'; import { createFetchResponse } from 'test/helpers/createFetchResponse';
@ -6,6 +7,7 @@ import {
AbstractLabelMatcher, AbstractLabelMatcher,
AbstractLabelOperator, AbstractLabelOperator,
DataQueryRequest, DataQueryRequest,
dateMath,
dateTime, dateTime,
getFrameDisplayName, getFrameDisplayName,
MetricFindValue, MetricFindValue,
@ -956,6 +958,31 @@ describe('graphiteDatasource', () => {
await assertQueryExport('interpolate(alias(servers.west.001))', []); await assertQueryExport('interpolate(alias(servers.west.001))', []);
}); });
}); });
describe('translateTime', () => {
it('does not mutate passed in date', async () => {
const date = new Date('2025-06-30T00:00:59.000Z');
const functionDate = moment(date);
const updatedDate = ctx.ds.translateTime(
dateMath.toDateTime(functionDate.toDate(), { roundUp: undefined, timezone: undefined })!,
true
);
expect(functionDate.toDate()).toEqual(date);
expect(updatedDate).not.toEqual(date.getTime());
});
it('does not mutate passed in relative date - string', async () => {
const date = 'now-1m';
const updatedDate = ctx.ds.translateTime(date, true);
expect(updatedDate).not.toEqual(date);
});
it('returns the input if the input is invalid', async () => {
const updatedDate = ctx.ds.translateTime('', true);
expect(updatedDate).toBe('');
});
});
}); });
function accessScenario(name: string, url: string, fn: ({ headers }: { headers: Record<string, unknown> }) => void) { function accessScenario(name: string, url: string, fn: ({ headers }: { headers: Record<string, unknown> }) => void) {

View File

@ -1,4 +1,5 @@
import { map as _map, each, indexOf, isArray, isString } from 'lodash'; import { map as _map, each, indexOf, isArray, isString } from 'lodash';
import moment from 'moment';
import { lastValueFrom, merge, Observable, of, OperatorFunction, pipe, throwError } from 'rxjs'; import { lastValueFrom, merge, Observable, of, OperatorFunction, pipe, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators'; import { catchError, map } from 'rxjs/operators';
@ -12,16 +13,17 @@ import {
DataSourceApi, DataSourceApi,
DataSourceWithQueryExportSupport, DataSourceWithQueryExportSupport,
dateMath, dateMath,
DateTime,
dateTime, dateTime,
getSearchFilterScopedVar, getSearchFilterScopedVar,
MetricFindValue, MetricFindValue,
QueryResultMetaStat, QueryResultMetaStat,
ScopedVars, ScopedVars,
TimeRange, TimeRange,
TimeZone,
toDataFrame, toDataFrame,
} from '@grafana/data'; } from '@grafana/data';
import { BackendSrvRequest, FetchResponse, getBackendSrv } from '@grafana/runtime'; import { BackendSrvRequest, FetchResponse, getBackendSrv } from '@grafana/runtime';
import { TimeZone } from '@grafana/schema';
import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version'; import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version';
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
import { getRollupNotice, getRuntimeConsolidationNotice } from 'app/plugins/datasource/graphite/meta'; import { getRollupNotice, getRuntimeConsolidationNotice } from 'app/plugins/datasource/graphite/meta';
@ -500,17 +502,32 @@ export class GraphiteDatasource
return this.templateSrv.containsTemplate(target.target ?? ''); return this.templateSrv.containsTemplate(target.target ?? '');
} }
translateTime(date: any, roundUp?: boolean, timezone?: TimeZone) { translateTime(date: DateTime | string, roundUp?: boolean, timezone?: TimeZone) {
if (isString(date)) { const parseDate = () => {
if (date === 'now') { if (isString(date)) {
return 'now'; if (date === 'now') {
} else if (date.indexOf('now-') >= 0 && date.indexOf('/') === -1) { return 'now';
date = date.substring(3); } else if (date.indexOf('now-') >= 0 && date.indexOf('/') === -1) {
date = date.replace('m', 'min'); return date.substring(3).replace('m', 'min').replace('M', 'mon');
date = date.replace('M', 'mon'); }
return date; const parsedDate = dateMath.toDateTime(date, { roundUp, timezone });
// If the date is invalid return the original string
// e.g. if an empty string is passed in or if the roundng is invalid e.g. now/2y
if (!parsedDate || parsedDate.isValid() === false) {
return date;
}
return moment(parsedDate.toDate());
} else {
return moment(date.toDate());
} }
date = dateMath.parse(date, roundUp, timezone); };
const parsedDate = parseDate();
if (typeof parsedDate === 'string') {
return parsedDate;
} }
// graphite' s from filter is exclusive // graphite' s from filter is exclusive
@ -518,16 +535,16 @@ export class GraphiteDatasource
// to guarantee that we get all the data that // to guarantee that we get all the data that
// exists for the specified range // exists for the specified range
if (roundUp) { if (roundUp) {
if (date.get('s')) { if (parsedDate.get('s')) {
date.add(1, 's'); parsedDate.add(1, 's');
} }
} else if (roundUp === false) { } else if (roundUp === false) {
if (date.get('s')) { if (parsedDate.get('s')) {
date.subtract(1, 's'); parsedDate.subtract(1, 's');
} }
} }
return date.unix(); return parsedDate.unix();
} }
metricFindQuery(findQuery: string | GraphiteQuery, optionalOptions?: any): Promise<MetricFindValue[]> { metricFindQuery(findQuery: string | GraphiteQuery, optionalOptions?: any): Promise<MetricFindValue[]> {