Chore: Convert Wrapper to function component (#56172)

* WIP

* remove comment
This commit is contained in:
Giordano Ricci 2022-10-04 09:39:22 +01:00 committed by GitHub
parent cb99b94b01
commit 3381629d3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 104 additions and 178 deletions

View File

@ -4416,8 +4416,7 @@ exports[`better eslint`] = {
"public/app/features/explore/state/main.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"]
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
],
"public/app/features/explore/state/query.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],

View File

@ -26,7 +26,7 @@ import { getFiscalYearStartMonth, getTimeZone } from '../profile/state/selectors
import Explore from './Explore';
import { initializeExplore, refreshExplore } from './state/explorePane';
import { lastSavedUrl, cleanupPaneAction, stateSave } from './state/main';
import { lastSavedUrl, stateSave } from './state/main';
import { importQueries } from './state/query';
import { loadAndInitDatasource } from './state/utils';
@ -178,7 +178,6 @@ function mapStateToProps(state: StoreState, props: OwnProps) {
const mapDispatchToProps = {
initializeExplore,
refreshExplore,
cleanupPaneAction,
importQueries,
stateSave,
};

View File

@ -1,28 +1,23 @@
import { css } from '@emotion/css';
import { useResizeObserver } from '@react-aria/utils';
import { debounce, inRange } from 'lodash';
import React, { PureComponent } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { locationService } from '@grafana/runtime';
import { ErrorBoundaryAlert } from '@grafana/ui';
import { SplitView } from 'app/core/components/SplitPaneWrapper/SplitView';
import { GrafanaContext } from 'app/core/context/GrafanaContext';
import { useGrafana } from 'app/core/context/GrafanaContext';
import { useNavModel } from 'app/core/hooks/useNavModel';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { StoreState } from 'app/types';
import { isTruthy } from 'app/core/utils/types';
import { useSelector, useDispatch } from 'app/types';
import { ExploreId, ExploreQueryParams } from 'app/types/explore';
import { Branding } from '../../core/components/Branding/Branding';
import { getNavModel } from '../../core/selectors/navModel';
import { ExploreActions } from './ExploreActions';
import { ExplorePaneContainer } from './ExplorePaneContainer';
import {
lastSavedUrl,
resetExploreAction,
richHistoryUpdatedAction,
cleanupPaneAction,
splitSizeUpdateAction,
} from './state/main';
import { lastSavedUrl, resetExploreAction, splitSizeUpdateAction } from './state/main';
const styles = {
pageScrollbarWrapper: css`
@ -36,64 +31,29 @@ const styles = {
`,
};
interface RouteProps extends GrafanaRouteComponentProps<{}, ExploreQueryParams> {}
interface OwnProps {}
const MIN_PANE_WIDTH = 200;
function Wrapper(props: GrafanaRouteComponentProps<{}, ExploreQueryParams>) {
useExplorePageTitle();
const { maxedExploreId, evenSplitPanes } = useSelector((state) => state.explore);
const [rightPaneWidth, setRightPaneWidth] = useState<number>();
const [prevWindowWidth, setWindowWidth] = useState<number>();
const dispatch = useDispatch();
const containerRef = useRef<HTMLDivElement>(null);
const queryParams = props.queryParams;
const { keybindings, chrome } = useGrafana();
const navModel = useNavModel('explore');
interface WrapperState {
rightPaneWidth?: number;
windowWidth?: number;
}
const mapStateToProps = (state: StoreState) => {
return {
navModel: getNavModel(state.navIndex, 'explore'),
exploreState: state.explore,
};
};
const mapDispatchToProps = {
resetExploreAction,
richHistoryUpdatedAction,
cleanupPaneAction,
splitSizeUpdateAction,
};
const connector = connect(mapStateToProps, mapDispatchToProps);
type Props = OwnProps & RouteProps & ConnectedProps<typeof connector>;
class WrapperUnconnected extends PureComponent<Props, WrapperState> {
minWidth = 200;
static contextType = GrafanaContext;
constructor(props: Props) {
super(props);
this.state = {
rightPaneWidth: undefined,
windowWidth: undefined,
};
}
componentWillUnmount() {
const { left, right } = this.props.queryParams;
this.props.resetExploreAction({});
if (Boolean(left)) {
this.props.cleanupPaneAction({ exploreId: ExploreId.left });
}
if (Boolean(right)) {
this.props.cleanupPaneAction({ exploreId: ExploreId.right });
}
window.removeEventListener('resize', this.windowResizeListener);
}
componentDidMount() {
useEffect(() => {
//This is needed for breadcrumbs and topnav.
//We should probably abstract this out at some point
this.context.chrome.update({ sectionNav: this.props.navModel.node });
this.context.keybindings.setupTimeRangeBindings(false);
chrome.update({ sectionNav: navModel.node });
}, [chrome, navModel]);
useEffect(() => {
keybindings.setupTimeRangeBindings(false);
}, [keybindings]);
useEffect(() => {
lastSavedUrl.left = undefined;
lastSavedUrl.right = undefined;
@ -111,90 +71,101 @@ class WrapperUnconnected extends PureComponent<Props, WrapperState> {
locationService.partial({ from: undefined, to: undefined }, true);
}
window.addEventListener('resize', this.windowResizeListener);
}
return () => {
// Cleaning up Explore state so that when navigating back to Explore it starts from a blank state
dispatch(resetExploreAction());
};
// eslint-disable-next-line react-hooks/exhaustive-deps -- dispatch is stable, doesn't need to be in the deps array
}, []);
componentDidUpdate() {
const { left, right } = this.props.queryParams;
const hasSplit = Boolean(left) && Boolean(right);
const datasourceTitle = hasSplit
? `${this.props.exploreState.left.datasourceInstance?.name} | ${this.props.exploreState.right?.datasourceInstance?.name}`
: `${this.props.exploreState.left.datasourceInstance?.name}`;
const documentTitle = `${this.props.navModel.main.text} - ${datasourceTitle} - ${Branding.AppTitle}`;
document.title = documentTitle;
}
windowResizeListener = debounce(() => {
const debouncedFunctionRef = useRef((prevWindowWidth?: number, rightPaneWidth?: number) => {
let rightPaneRatio = 0.5;
const windowWidth = window.innerWidth;
if (!containerRef.current) {
return;
}
const windowWidth = containerRef.current.clientWidth;
// get the ratio of the previous rightPane to the window width
if (this.state.rightPaneWidth && this.state.windowWidth) {
rightPaneRatio = this.state.rightPaneWidth / this.state.windowWidth;
if (rightPaneWidth && prevWindowWidth) {
rightPaneRatio = rightPaneWidth / prevWindowWidth;
}
let newRightPaneWidth = Math.floor(windowWidth * rightPaneRatio);
if (newRightPaneWidth < this.minWidth) {
if (newRightPaneWidth < MIN_PANE_WIDTH) {
// if right pane is too narrow, make min width
newRightPaneWidth = this.minWidth;
} else if (windowWidth - newRightPaneWidth < this.minWidth) {
newRightPaneWidth = MIN_PANE_WIDTH;
} else if (windowWidth - newRightPaneWidth < MIN_PANE_WIDTH) {
// if left pane is too narrow, make right pane = window - minWidth
newRightPaneWidth = windowWidth - this.minWidth;
newRightPaneWidth = windowWidth - MIN_PANE_WIDTH;
}
this.setState({ windowWidth, rightPaneWidth: newRightPaneWidth });
}, 500);
setRightPaneWidth(newRightPaneWidth);
setWindowWidth(windowWidth);
});
updateSplitSize = (rightPaneWidth: number) => {
// eslint needs the callback to be inline to analyze the dependencies, but we need to use debounce from lodash
// eslint-disable-next-line react-hooks/exhaustive-deps
const onResize = useCallback(
debounce(() => debouncedFunctionRef.current(prevWindowWidth, rightPaneWidth), 500),
[prevWindowWidth, rightPaneWidth]
);
const updateSplitSize = (rightPaneWidth: number) => {
const evenSplitWidth = window.innerWidth / 2;
const areBothSimilar = inRange(rightPaneWidth, evenSplitWidth - 100, evenSplitWidth + 100);
if (areBothSimilar) {
this.props.splitSizeUpdateAction({ largerExploreId: undefined });
dispatch(splitSizeUpdateAction({ largerExploreId: undefined }));
} else {
this.props.splitSizeUpdateAction({
largerExploreId: rightPaneWidth > evenSplitWidth ? ExploreId.right : ExploreId.left,
});
dispatch(
splitSizeUpdateAction({
largerExploreId: rightPaneWidth > evenSplitWidth ? ExploreId.right : ExploreId.left,
})
);
}
this.setState({ ...this.state, rightPaneWidth });
setRightPaneWidth(rightPaneWidth);
};
render() {
const { left, right } = this.props.queryParams;
const { maxedExploreId, evenSplitPanes } = this.props.exploreState;
const hasSplit = Boolean(left) && Boolean(right);
let widthCalc = 0;
useResizeObserver({ onResize, ref: containerRef });
const hasSplit = Boolean(queryParams.left) && Boolean(queryParams.right);
let widthCalc = 0;
if (hasSplit) {
if (!evenSplitPanes && maxedExploreId) {
widthCalc = maxedExploreId === ExploreId.right ? window.innerWidth - this.minWidth : this.minWidth;
} else if (evenSplitPanes) {
widthCalc = Math.floor(window.innerWidth / 2);
} else if (this.state.rightPaneWidth !== undefined) {
widthCalc = this.state.rightPaneWidth;
}
if (hasSplit) {
if (!evenSplitPanes && maxedExploreId) {
widthCalc = maxedExploreId === ExploreId.right ? window.innerWidth - MIN_PANE_WIDTH : MIN_PANE_WIDTH;
} else if (evenSplitPanes) {
widthCalc = Math.floor(window.innerWidth / 2);
} else if (rightPaneWidth !== undefined) {
widthCalc = rightPaneWidth;
}
const splitSizeObj = { rightPaneSize: widthCalc };
return (
<div className={styles.pageScrollbarWrapper}>
<ExploreActions exploreIdLeft={ExploreId.left} exploreIdRight={ExploreId.right} />
<div className={styles.exploreWrapper}>
<SplitView uiState={splitSizeObj} minSize={this.minWidth} onResize={this.updateSplitSize}>
<ErrorBoundaryAlert style="page" key="LeftPane">
<ExplorePaneContainer split={hasSplit} exploreId={ExploreId.left} urlQuery={left} />
</ErrorBoundaryAlert>
{hasSplit && (
<ErrorBoundaryAlert style="page" key="RightPane">
<ExplorePaneContainer split={hasSplit} exploreId={ExploreId.right} urlQuery={right} />
</ErrorBoundaryAlert>
)}
</SplitView>
</div>
</div>
);
}
const splitSizeObj = { rightPaneSize: widthCalc };
return (
<div className={styles.pageScrollbarWrapper} ref={containerRef}>
<ExploreActions exploreIdLeft={ExploreId.left} exploreIdRight={ExploreId.right} />
<div className={styles.exploreWrapper}>
<SplitView uiState={splitSizeObj} minSize={MIN_PANE_WIDTH} onResize={updateSplitSize}>
<ErrorBoundaryAlert style="page" key="LeftPane">
<ExplorePaneContainer split={hasSplit} exploreId={ExploreId.left} urlQuery={queryParams.left} />
</ErrorBoundaryAlert>
{hasSplit && (
<ErrorBoundaryAlert style="page" key="RightPane">
<ExplorePaneContainer split={hasSplit} exploreId={ExploreId.right} urlQuery={queryParams.right} />
</ErrorBoundaryAlert>
)}
</SplitView>
</div>
</div>
);
}
const Wrapper = connector(WrapperUnconnected);
const useExplorePageTitle = () => {
const navModel = useNavModel('explore');
const datasources = useSelector((state) =>
[state.explore.left.datasourceInstance?.name, state.explore.right?.datasourceInstance?.name].filter(isTruthy)
);
const documentTitle = `${navModel.main.text} - ${datasources.join(' | ')} - ${Branding.AppTitle}`;
document.title = documentTitle;
};
export default Wrapper;

View File

@ -192,8 +192,8 @@ export function initializeExplore(
*/
export function refreshExplore(exploreId: ExploreId, newUrlQuery: string): ThunkResult<void> {
return async (dispatch, getState) => {
const itemState = getState().explore[exploreId]!;
if (!itemState.initialized) {
const itemState = getState().explore[exploreId];
if (!itemState?.initialized) {
return;
}

View File

@ -50,10 +50,7 @@ export const evenPaneResizeAction = createAction('explore/evenPaneResizeAction')
/**
* Resets state for explore.
*/
export interface ResetExplorePayload {
force?: boolean;
}
export const resetExploreAction = createAction<ResetExplorePayload>('explore/resetExplore');
export const resetExploreAction = createAction('explore/resetExplore');
/**
* Close the split view and save URL state.
@ -63,17 +60,6 @@ export interface SplitCloseActionPayload {
}
export const splitCloseAction = createAction<SplitCloseActionPayload>('explore/splitClose');
/**
* Cleans up a pane state. Could seem like this should be in explorePane.ts actions but in case we area closing
* left pane we need to move right state to the left.
* Also this may seem redundant as we have splitClose actions which clears up state but that action is not called on
* URL change.
*/
export interface CleanupPanePayload {
exploreId: ExploreId;
}
export const cleanupPaneAction = createAction<CleanupPanePayload>('explore/cleanupPane');
//
// Action creators
//
@ -237,30 +223,6 @@ export const exploreReducer = (state = initialExploreState, action: AnyAction):
};
}
if (cleanupPaneAction.match(action)) {
const { exploreId } = action.payload as CleanupPanePayload;
// We want to do this only when we remove single pane not when we are unmounting whole explore.
// It needs to be checked like this because in component we don't get new path (which would tell us if we are
// navigating out of explore) before the unmount.
if (!state[exploreId]?.initialized) {
return state;
}
if (exploreId === ExploreId.left) {
return {
...state,
[ExploreId.left]: state[ExploreId.right]!,
[ExploreId.right]: undefined,
};
} else {
return {
...state,
[ExploreId.right]: undefined,
};
}
}
if (syncTimesAction.match(action)) {
return { ...state, syncedTimes: action.payload.syncedTimes };
}
@ -287,7 +249,6 @@ export const exploreReducer = (state = initialExploreState, action: AnyAction):
}
if (resetExploreAction.match(action)) {
const payload: ResetExplorePayload = action.payload;
const leftState = state[ExploreId.left];
const rightState = state[ExploreId.right];
stopQueryState(leftState.querySubscription);
@ -295,10 +256,6 @@ export const exploreReducer = (state = initialExploreState, action: AnyAction):
stopQueryState(rightState.querySubscription);
}
if (payload.force) {
return initialExploreState;
}
return {
...initialExploreState,
left: {