grafana/public/app/features/explore/ExplorePaneContainer.tsx
Andrej Ocenas b0bd242eda
Explore/Refactor: Simplify URL handling (#29173)
* Inline datasource actions into initialisation

* Simplify url handling

* Add comments

* Remove split property from state and split Explore.tsx to 2 components

* Add comments

* Simplify and fix splitOpen and splitClose actions

* Update public/app/features/explore/ExplorePaneContainer.tsx

Co-authored-by: Giordano Ricci <gio.ricci@grafana.com>

* Update public/app/features/explore/state/explorePane.test.ts

Co-authored-by: Giordano Ricci <gio.ricci@grafana.com>

* Update public/app/features/explore/Wrapper.tsx

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>

* Fix test

* Fix lint

Co-authored-by: Giordano Ricci <gio.ricci@grafana.com>
Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
2021-02-12 21:33:26 +01:00

131 lines
4.2 KiB
TypeScript

import React from 'react';
import { hot } from 'react-hot-loader';
import { compose } from 'redux';
import { connect, ConnectedProps } from 'react-redux';
import memoizeOne from 'memoize-one';
import { withTheme } from '@grafana/ui';
import { DataQuery, ExploreUrlState, EventBusExtended, EventBusSrv } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import store from 'app/core/store';
import { lastSavedUrl, cleanupPaneAction } from './state/main';
import { initializeExplore, refreshExplore } from './state/explorePane';
import { ExploreId } from 'app/types/explore';
import { StoreState } from 'app/types';
import {
DEFAULT_RANGE,
ensureQueries,
getTimeRange,
getTimeRangeFromUrl,
lastUsedDatasourceKeyForOrgId,
parseUrlState,
} from 'app/core/utils/explore';
import { getTimeZone } from '../profile/state/selectors';
import Explore from './Explore';
type PropsFromRedux = ConnectedProps<typeof connector>;
interface Props extends PropsFromRedux {
exploreId: ExploreId;
split: boolean;
}
/**
* This component is responsible for handling initialization of an Explore pane and triggering synchronization
* of state based on URL changes and preventing any infinite loops.
*/
export class ExplorePaneContainerUnconnected extends React.PureComponent<Props & ConnectedProps<typeof connector>> {
el: any;
exploreEvents: EventBusExtended;
constructor(props: Props) {
super(props);
this.exploreEvents = new EventBusSrv();
this.state = {
openDrawer: undefined,
};
}
componentDidMount() {
const { initialized, exploreId, initialDatasource, initialQueries, initialRange, originPanelId } = this.props;
const width = this.el?.offsetWidth ?? 0;
// initialize the whole explore first time we mount and if browser history contains a change in datasource
if (!initialized) {
this.props.initializeExplore(
exploreId,
initialDatasource,
initialQueries,
initialRange,
width,
this.exploreEvents,
originPanelId
);
}
}
componentWillUnmount() {
this.exploreEvents.removeAllListeners();
this.props.cleanupPaneAction({ exploreId: this.props.exploreId });
}
componentDidUpdate(prevProps: Props) {
this.refreshExplore(prevProps.urlQuery);
}
refreshExplore = (prevUrlQuery: string) => {
const { exploreId, urlQuery } = this.props;
// Update state from url only if it changed and only if the change wasn't initialised by redux to prevent any loops
if (urlQuery !== prevUrlQuery && urlQuery !== lastSavedUrl[exploreId]) {
this.props.refreshExplore(exploreId, urlQuery);
}
};
getRef = (el: any) => {
this.el = el;
};
render() {
const exploreClass = this.props.split ? 'explore explore-split' : 'explore';
return (
<div className={exploreClass} ref={this.getRef} aria-label={selectors.pages.Explore.General.container}>
{this.props.initialized && <Explore exploreId={this.props.exploreId} />}
</div>
);
}
}
const ensureQueriesMemoized = memoizeOne(ensureQueries);
const getTimeRangeFromUrlMemoized = memoizeOne(getTimeRangeFromUrl);
function mapStateToProps(state: StoreState, { exploreId }: { exploreId: ExploreId }) {
const urlQuery = state.location.query[exploreId] as string;
const urlState = parseUrlState(urlQuery);
const timeZone = getTimeZone(state.user);
const { datasource, queries, range: urlRange, originPanelId } = (urlState || {}) as ExploreUrlState;
const initialDatasource = datasource || store.get(lastUsedDatasourceKeyForOrgId(state.user.orgId));
const initialQueries: DataQuery[] = ensureQueriesMemoized(queries);
const initialRange = urlRange
? getTimeRangeFromUrlMemoized(urlRange, timeZone)
: getTimeRange(timeZone, DEFAULT_RANGE);
return {
initialized: state.explore[exploreId]?.initialized,
initialDatasource,
initialQueries,
initialRange,
originPanelId,
urlQuery,
};
}
const mapDispatchToProps = {
initializeExplore,
refreshExplore,
cleanupPaneAction,
};
const connector = connect(mapStateToProps, mapDispatchToProps);
export const ExplorePaneContainer = compose(hot(module), connector, withTheme)(ExplorePaneContainerUnconnected);