From 1308e191972aaf2df65d4bd66a0c66c07d6c246e Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 16 Feb 2022 07:48:47 -0500 Subject: [PATCH] Explore: avoid locking timepicker when range is inverted (#44790) (#44841) * Explore: avoid locking timepicker when range is inverted * Explore: prevent time picker to lock if from & to search parameters are present (cherry picked from commit 6415b9a54dec6e9763f2faee076ce6578564b072) Co-authored-by: Giordano Ricci --- public/app/core/utils/explore.test.ts | 14 ++++++++++++++ public/app/core/utils/explore.ts | 12 +++++++----- public/app/features/explore/Wrapper.test.tsx | 10 +++++++++- public/app/features/explore/Wrapper.tsx | 15 +++++++++++++++ 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts index a25e8f75a54..97fde9b8820 100644 --- a/public/app/core/utils/explore.test.ts +++ b/public/app/core/utils/explore.test.ts @@ -11,6 +11,7 @@ import { getExploreUrl, GetExploreUrlArguments, getTimeRangeFromUrl, + getTimeRange, } from './explore'; import store from 'app/core/store'; import { dateTime, ExploreUrlState, LogsSortOrder } from '@grafana/data'; @@ -361,6 +362,19 @@ describe('getTimeRangeFromUrl', () => { }); }); +describe('getTimeRange', () => { + describe('should flip from and to when from is after to', () => { + const rawRange = { + from: 'now', + to: 'now-6h', + }; + + const range = getTimeRange('utc', rawRange, 0); + + expect(range.from.isBefore(range.to)).toBe(true); + }); +}); + describe('getRefIds', () => { describe('when called with a null value', () => { it('then it should return empty array', () => { diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index 77a29040e18..f10af2037f1 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -358,11 +358,13 @@ export const getQueryKeys = (queries: DataQuery[], datasourceInstance?: DataSour }; export const getTimeRange = (timeZone: TimeZone, rawRange: RawTimeRange, fiscalYearStartMonth: number): TimeRange => { - return { - from: dateMath.parse(rawRange.from, false, timeZone as any, fiscalYearStartMonth)!, - to: dateMath.parse(rawRange.to, true, timeZone as any, fiscalYearStartMonth)!, - raw: rawRange, - }; + let range = rangeUtil.convertRawToRange(rawRange, timeZone, fiscalYearStartMonth); + + if (range.to.isBefore(range.from)) { + range = rangeUtil.convertRawToRange({ from: range.raw.to, to: range.raw.from }, timeZone, fiscalYearStartMonth); + } + + return range; }; const parseRawTime = (value: string | DateTime): TimeFragment | null => { diff --git a/public/app/features/explore/Wrapper.test.tsx b/public/app/features/explore/Wrapper.test.tsx index 89518be6e2d..1c964abef98 100644 --- a/public/app/features/explore/Wrapper.test.tsx +++ b/public/app/features/explore/Wrapper.test.tsx @@ -314,12 +314,20 @@ describe('Wrapper', () => { store.dispatch(splitOpen({ datasourceUid: 'elastic', query: { expr: 'error' } }) as any); await waitFor(() => expect(document.title).toEqual('Explore - loki | elastic - Grafana')); }); + + it('removes `from` and `to` parameters from url when first mounted', () => { + setup({ searchParams: 'from=1&to=2&orgId=1' }); + + expect(locationService.getSearchObject()).toEqual(expect.not.objectContaining({ from: '1', to: '2' })); + expect(locationService.getSearchObject()).toEqual(expect.objectContaining({ orgId: '1' })); + }); }); type DatasourceSetup = { settings: DataSourceInstanceSettings; api: DataSourceApi }; type SetupOptions = { datasources?: DatasourceSetup[]; query?: any; + searchParams?: string; }; function setup(options?: SetupOptions): { datasources: { [name: string]: DataSourceApi }; store: EnhancedStore } { @@ -368,7 +376,7 @@ function setup(options?: SetupOptions): { datasources: { [name: string]: DataSou }, }; - locationService.push({ pathname: '/explore' }); + locationService.push({ pathname: '/explore', search: options?.searchParams }); if (options?.query) { locationService.partial(options.query); diff --git a/public/app/features/explore/Wrapper.tsx b/public/app/features/explore/Wrapper.tsx index abc725c59fb..f38a2e6616e 100644 --- a/public/app/features/explore/Wrapper.tsx +++ b/public/app/features/explore/Wrapper.tsx @@ -10,6 +10,7 @@ import { Branding } from '../../core/components/Branding/Branding'; import { getNavModel } from '../../core/selectors/navModel'; import { StoreState } from 'app/types'; +import { locationService } from '@grafana/runtime'; interface RouteProps extends GrafanaRouteComponentProps<{}, ExploreQueryParams> {} interface OwnProps {} @@ -38,6 +39,20 @@ class WrapperUnconnected extends PureComponent { lastSavedUrl.left = undefined; lastSavedUrl.right = undefined; + // timeSrv (which is used internally) on init reads `from` and `to` param from the URL and updates itself + // using those value regardless of what is passed to the init method. + // The updated value is then used by Explore to get the range for each pane. + // This means that if `from` and `to` parameters are present in the URL, + // it would be impossible to change the time range in Explore. + // We are only doing this on mount for 2 reasons: + // 1: Doing it on update means we'll enter a render loop. + // 2: when parsing time in Explore (before feeding it to timeSrv) we make sure `from` is before `to` inside + // each pane state in order to not trigger un URL update from timeSrv. + const searchParams = locationService.getSearchObject(); + if (searchParams.from || searchParams.to) { + locationService.partial({ from: undefined, to: undefined }, true); + } + const richHistory = getRichHistory(); this.props.richHistoryUpdatedAction({ richHistory }); }