diff --git a/public/app/features/explore/ExploreToolbar.tsx b/public/app/features/explore/ExploreToolbar.tsx index 317d7452275..73abdae0d93 100644 --- a/public/app/features/explore/ExploreToolbar.tsx +++ b/public/app/features/explore/ExploreToolbar.tsx @@ -1,6 +1,7 @@ -import { css } from '@emotion/css'; -import React, { lazy, PureComponent, RefObject, Suspense } from 'react'; -import { connect, ConnectedProps } from 'react-redux'; +import { css, cx } from '@emotion/css'; +import { pick } from 'lodash'; +import React, { lazy, RefObject, Suspense, useMemo } from 'react'; +import { shallowEqual } from 'react-redux'; import { DataSourceInstanceSettings, RawTimeRange } from '@grafana/data'; import { config, DataSourcePicker, reportInteraction } from '@grafana/runtime'; @@ -10,7 +11,7 @@ import { contextSrv } from 'app/core/core'; import { createAndCopyShortLink } from 'app/core/utils/shortLinks'; import { AccessControlAction } from 'app/types'; import { ExploreId } from 'app/types/explore'; -import { StoreState } from 'app/types/store'; +import { StoreState, useDispatch, useSelector } from 'app/types/store'; import { DashNavButton } from '../dashboard/components/DashNav/DashNavButton'; import { getTimeSrv } from '../dashboard/services/TimeSrv'; @@ -30,299 +31,226 @@ const AddToDashboard = lazy(() => import('./AddToDashboard').then(({ AddToDashboard }) => ({ default: AddToDashboard })) ); -const getStyles = (exploreId: ExploreId, isLargerExploreId: boolean) => { - return { - rotateIcon: css({ - '> div > svg': { - transform: - (exploreId === 'left' && isLargerExploreId) || (exploreId === 'right' && !isLargerExploreId) - ? 'rotate(180deg)' - : 'none', - }, - }), - }; -}; +const rotateIcon = css({ + '> div > svg': { + transform: 'rotate(180deg)', + }, +}); -interface OwnProps { +interface Props { exploreId: ExploreId; onChangeTime: (range: RawTimeRange, changedByScanner?: boolean) => void; topOfViewRef: RefObject; } -type Props = OwnProps & ConnectedProps; +export function ExploreToolbar({ exploreId, topOfViewRef, onChangeTime }: Props) { + const dispatch = useDispatch(); -class UnConnectedExploreToolbar extends PureComponent { - onChangeDatasource = async (dsSettings: DataSourceInstanceSettings) => { - const { changeDatasource, exploreId } = this.props; - changeDatasource(exploreId, dsSettings.uid, { importQueries: true }); - }; + const splitted = useSelector(isSplit); + const timeZone = useSelector((state: StoreState) => getTimeZone(state.user)); + const fiscalYearStartMonth = useSelector((state: StoreState) => getFiscalYearStartMonth(state.user)); + const { refreshInterval, loading, datasourceInstance, range, isLive, isPaused, syncedTimes } = useSelector( + (state: StoreState) => ({ + ...pick( + state.explore[exploreId]!, + 'refreshInterval', + 'loading', + 'datasourceInstance', + 'range', + 'isLive', + 'isPaused' + ), + syncedTimes: state.explore.syncedTimes, + }), + shallowEqual + ); + const isLargerPane = useSelector((state: StoreState) => state.explore.largerExploreId === exploreId); + const showSmallTimePicker = useSelector((state) => splitted || state.explore[exploreId]!.containerWidth < 1210); + const showSmallDataSourcePicker = useSelector( + (state) => state.explore[exploreId]!.containerWidth < (splitted ? 700 : 800) + ); - onRunQuery = (loading = false) => { - const { runQueries, cancelQueries, exploreId } = this.props; - if (loading) { - return cancelQueries(exploreId); - } else { - return runQueries(exploreId); - } - }; + const shouldRotateSplitIcon = useMemo( + () => (exploreId === 'left' && isLargerPane) || (exploreId === 'right' && !isLargerPane), + [isLargerPane, exploreId] + ); - onChangeRefreshInterval = (item: string) => { - const { changeRefreshInterval, exploreId } = this.props; - changeRefreshInterval(exploreId, item); - }; - - onChangeTimeSync = () => { - const { syncTimes, exploreId } = this.props; - syncTimes(exploreId); - }; - - onCopyShortLink = async () => { - await createAndCopyShortLink(window.location.href); + const onCopyShortLink = () => { + createAndCopyShortLink(global.location.href); reportInteraction('grafana_explore_shortened_link_clicked'); }; - onOpenSplitView = () => { - const { split } = this.props; - split(); + const onChangeDatasource = async (dsSettings: DataSourceInstanceSettings) => { + dispatch(changeDatasource(exploreId, dsSettings.uid, { importQueries: true })); + }; + + const onRunQuery = (loading = false) => { + if (loading) { + return dispatch(cancelQueries(exploreId)); + } else { + return dispatch(runQueries(exploreId)); + } + }; + + const onChangeTimeZone = (timezone: string) => dispatch(updateTimeZoneForSession(timezone)); + + const onOpenSplitView = () => { + dispatch(splitOpen()); reportInteraction('grafana_explore_split_view_opened', { origin: 'menu' }); }; - onCloseSplitView = () => { - const { closeSplit, exploreId } = this.props; - closeSplit(exploreId); + const onCloseSplitView = () => { + dispatch(splitClose(exploreId)); reportInteraction('grafana_explore_split_view_closed'); }; - renderRefreshPicker = (showSmallTimePicker: boolean) => { - const { loading, refreshInterval, isLive } = this.props; - - let refreshPickerText: string | undefined = loading ? 'Cancel' : 'Run query'; - let refreshPickerTooltip = undefined; - let refreshPickerWidth = '108px'; - if (showSmallTimePicker) { - refreshPickerTooltip = refreshPickerText; - refreshPickerText = undefined; - refreshPickerWidth = '35px'; + const onClickResize = () => { + if (isLargerPane) { + dispatch(evenPaneResizeAction()); + } else { + dispatch(maximizePaneAction({ exploreId })); } - - return ( - this.onRunQuery(loading)} - noIntervalPicker={isLive} - primary={true} - width={refreshPickerWidth} - /> - ); }; - renderActions = () => { - const { - splitted, - isLive, - exploreId, - range, - timeZone, - fiscalYearStartMonth, - onChangeTime, - syncedTimes, - onChangeTimeZone, - onChangeFiscalYearStartMonth, - isPaused, - hasLiveOption, - containerWidth, - largerExploreId, - } = this.props; - const showSmallTimePicker = splitted || containerWidth < 1210; - const isLargerExploreId = largerExploreId === exploreId; - const styles = getStyles(exploreId, isLargerExploreId); + const onChangeTimeSync = () => { + dispatch(syncTimes(exploreId)); + }; - const showExploreToDashboard = + const onChangeFiscalYearStartMonth = (fiscalyearStartMonth: number) => + dispatch(updateFiscalYearStartMonthForSession(fiscalyearStartMonth)); + + const onChangeRefreshInterval = (item: string) => { + dispatch(changeRefreshInterval(exploreId, item)); + }; + + const showExploreToDashboard = useMemo( + () => contextSrv.hasAccess(AccessControlAction.DashboardsCreate, contextSrv.isEditor) || - contextSrv.hasAccess(AccessControlAction.DashboardsWrite, contextSrv.isEditor); + contextSrv.hasAccess(AccessControlAction.DashboardsWrite, contextSrv.isEditor), + [] + ); - const onClickResize = () => { - if (isLargerExploreId) { - this.props.evenPaneResizeAction(); - } else { - this.props.maximizePaneAction({ exploreId: exploreId }); - } - }; - - return [ - !splitted ? ( - - Split - - ) : ( - - - - Close - - - ), - - showExploreToDashboard && ( - - - - ), - - !isLive && ( - - ), - - this.renderRefreshPicker(showSmallTimePicker), - - hasLiveOption && ( - - {(c) => { - const controls = { - ...c, - start: () => { - reportInteraction('grafana_explore_logs_live_tailing_clicked', { - datasourceType: this.props.datasourceType, - }); - c.start(); - }, - }; - return ( - - ); - }} - - ), - ].filter(Boolean); - }; - - render() { - const { datasourceMissing, exploreId, splitted, containerWidth, topOfViewRef, refreshInterval, loading } = - this.props; - - const showSmallDataSourcePicker = (splitted ? containerWidth < 700 : containerWidth < 800) || false; - - const shareButton = ( - - ); - - const getDataSourcePicker = () => - !datasourceMissing && ( - - ); - - const toolbarLeftItems = [getDataSourcePicker()].filter(Boolean); - - return ( + return ( +
+ {refreshInterval && }
- {refreshInterval && } -
- ]} /> -
- - {this.renderActions()} - + , +
, + ]} + />
- ); - } + , + ]} + forceShowLeftItems + > + {[ + !splitted ? ( + + Split + + ) : ( + + + + Close + + + ), + showExploreToDashboard && ( + + + + ), + !isLive && ( + + ), + onRunQuery(loading)} + noIntervalPicker={isLive} + primary={true} + width={(showSmallTimePicker ? 35 : 108) + 'px'} + />, + datasourceInstance?.meta.streaming && ( + + {(c) => { + const controls = { + ...c, + start: () => { + reportInteraction('grafana_explore_logs_live_tailing_clicked', { + datasourceType: datasourceInstance?.type, + }); + c.start(); + }, + }; + return ( + + ); + }} + + ), + ].filter(Boolean)} + +
+ ); } - -const mapStateToProps = (state: StoreState, { exploreId }: OwnProps) => { - const { syncedTimes, largerExploreId } = state.explore; - const exploreItem = state.explore[exploreId]!; - const { datasourceInstance, datasourceMissing, range, refreshInterval, loading, isLive, isPaused, containerWidth } = - exploreItem; - - const hasLiveOption = !!datasourceInstance?.meta?.streaming; - - return { - datasourceMissing, - datasourceRef: datasourceInstance?.getRef(), - datasourceType: datasourceInstance?.type, - loading, - range, - timeZone: getTimeZone(state.user), - fiscalYearStartMonth: getFiscalYearStartMonth(state.user), - splitted: isSplit(state), - refreshInterval, - hasLiveOption, - isLive, - isPaused, - syncedTimes, - containerWidth, - largerExploreId, - }; -}; - -const mapDispatchToProps = { - changeDatasource, - changeRefreshInterval, - cancelQueries, - runQueries, - closeSplit: splitClose, - split: splitOpen, - syncTimes, - onChangeTimeZone: updateTimeZoneForSession, - onChangeFiscalYearStartMonth: updateFiscalYearStartMonthForSession, - maximizePaneAction, - evenPaneResizeAction, -}; - -const connector = connect(mapStateToProps, mapDispatchToProps); - -export const ExploreToolbar = connector(UnConnectedExploreToolbar);