import { css, cx } from '@emotion/css'; import { pick } from 'lodash'; import { useMemo } from 'react'; import { shallowEqual } from 'react-redux'; import { DataSourceInstanceSettings, RawTimeRange, GrafanaTheme2 } from '@grafana/data'; import { Components } from '@grafana/e2e-selectors'; import { reportInteraction } from '@grafana/runtime'; import { defaultIntervals, PageToolbar, RefreshPicker, SetInterval, ToolbarButton, ButtonGroup, useStyles2, } from '@grafana/ui'; import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; import { t, Trans } from 'app/core/internationalization'; import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker'; import { CORRELATION_EDITOR_POST_CONFIRM_ACTION } from 'app/types/explore'; import { StoreState, useDispatch, useSelector } from 'app/types/store'; import { contextSrv } from '../../core/core'; import { updateFiscalYearStartMonthForSession, updateTimeZoneForSession } from '../profile/state/reducers'; import { getFiscalYearStartMonth, getTimeZone } from '../profile/state/selectors'; import { ExploreTimeControls } from './ExploreTimeControls'; import { LiveTailButton } from './LiveTailButton'; import { useQueriesDrawerContext } from './QueriesDrawer/QueriesDrawerContext'; import { QueriesDrawerDropdown } from './QueriesDrawer/QueriesDrawerDropdown'; import { ShortLinkButtonMenu } from './ShortLinkButtonMenu'; import { ToolbarExtensionPoint } from './extensions/ToolbarExtensionPoint'; import { changeDatasource } from './state/datasource'; import { changeCorrelationHelperData } from './state/explorePane'; import { splitClose, splitOpen, maximizePaneAction, evenPaneResizeAction, changeCorrelationEditorDetails, } from './state/main'; import { cancelQueries, runQueries, selectIsWaitingForData } from './state/query'; import { isLeftPaneSelector, isSplit, selectCorrelationDetails, selectPanesEntries } from './state/selectors'; import { syncTimes, changeRefreshInterval } from './state/time'; import { LiveTailControls } from './useLiveTailControls'; const getStyles = (theme: GrafanaTheme2, splitted: Boolean) => ({ rotateIcon: css({ '> div > svg': { transform: 'rotate(180deg)', }, }), toolbarButton: css({ display: 'flex', justifyContent: 'center', marginRight: theme.spacing(0.5), width: splitted && theme.spacing(6), }), }); interface Props { exploreId: string; onChangeTime: (range: RawTimeRange, changedByScanner?: boolean) => void; onContentOutlineToogle: () => void; isContentOutlineOpen: boolean; } export function ExploreToolbar({ exploreId, onChangeTime, onContentOutlineToogle, isContentOutlineOpen }: Props) { const dispatch = useDispatch(); const splitted = useSelector(isSplit); const styles = useStyles2(getStyles, splitted); const timeZone = useSelector((state: StoreState) => getTimeZone(state.user)); const fiscalYearStartMonth = useSelector((state: StoreState) => getFiscalYearStartMonth(state.user)); const { refreshInterval, datasourceInstance, range, isLive, isPaused, syncedTimes } = useSelector( (state: StoreState) => ({ ...pick(state.explore.panes[exploreId]!, 'refreshInterval', 'datasourceInstance', 'range', 'isLive', 'isPaused'), syncedTimes: state.explore.syncedTimes, }), shallowEqual ); const loading = useSelector(selectIsWaitingForData(exploreId)); const isLargerPane = useSelector((state: StoreState) => state.explore.largerExploreId === exploreId); const showSmallTimePicker = useSelector((state) => splitted || state.explore.panes[exploreId]!.containerWidth < 1210); const showSmallDataSourcePicker = useSelector( (state) => state.explore.panes[exploreId]!.containerWidth < (splitted ? 700 : 800) ); const panes = useSelector(selectPanesEntries); const correlationDetails = useSelector(selectCorrelationDetails); const isCorrelationsEditorMode = correlationDetails?.editorMode || false; const isLeftPane = useSelector(isLeftPaneSelector(exploreId)); const { drawerOpened, setDrawerOpened, queryLibraryAvailable } = useQueriesDrawerContext(); const shouldRotateSplitIcon = useMemo( () => (isLeftPane && isLargerPane) || (!isLeftPane && !isLargerPane), [isLeftPane, isLargerPane] ); const refreshPickerLabel = loading ? t('explore.toolbar.refresh-picker-cancel', 'Cancel') : t('explore.toolbar.refresh-picker-run', 'Run query'); const onChangeDatasource = async (dsSettings: DataSourceInstanceSettings) => { if (!isCorrelationsEditorMode) { dispatch(changeDatasource({ exploreId, datasource: dsSettings.uid, options: { importQueries: true } })); } else { if (correlationDetails?.correlationDirty || correlationDetails?.queryEditorDirty) { // prompt will handle datasource change if needed dispatch( changeCorrelationEditorDetails({ isExiting: true, postConfirmAction: { exploreId: exploreId, action: CORRELATION_EDITOR_POST_CONFIRM_ACTION.CHANGE_DATASOURCE, changeDatasourceUid: dsSettings.uid, isActionLeft: isLeftPane, }, }) ); } else { // if the left pane is changing, clear helper data for right pane if (isLeftPane) { panes.forEach((pane) => { dispatch( changeCorrelationHelperData({ exploreId: pane[0], correlationEditorHelperData: undefined, }) ); }); } dispatch(changeDatasource({ exploreId, datasource: dsSettings.uid, options: { 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' }); }; const onCloseSplitView = () => { if (isCorrelationsEditorMode) { if (correlationDetails?.correlationDirty || correlationDetails?.queryEditorDirty) { // if dirty, prompt dispatch( changeCorrelationEditorDetails({ isExiting: true, postConfirmAction: { exploreId: exploreId, action: CORRELATION_EDITOR_POST_CONFIRM_ACTION.CLOSE_PANE, isActionLeft: isLeftPane, }, }) ); } else { // otherwise, clear helper data and close panes.forEach((pane) => { dispatch( changeCorrelationHelperData({ exploreId: pane[0], correlationEditorHelperData: undefined, }) ); }); dispatch(splitClose(exploreId)); reportInteraction('grafana_explore_split_view_closed'); } } else { dispatch(splitClose(exploreId)); reportInteraction('grafana_explore_split_view_closed'); } }; const onClickResize = () => { if (isLargerPane) { dispatch(evenPaneResizeAction()); } else { dispatch(maximizePaneAction({ exploreId })); } }; const onChangeTimeSync = () => { dispatch(syncTimes(exploreId)); }; const onChangeFiscalYearStartMonth = (fiscalyearStartMonth: number) => dispatch(updateFiscalYearStartMonthForSession(fiscalyearStartMonth)); const onChangeRefreshInterval = (refreshInterval: string) => { dispatch(changeRefreshInterval({ exploreId, refreshInterval })); }; const navBarActions = []; if (queryLibraryAvailable) { navBarActions.unshift(); } else { navBarActions.unshift( setDrawerOpened(!drawerOpened)} data-testid={Components.QueryTab.queryHistoryButton} icon="history" > Query history ); } return (
{refreshInterval && } Outline , , , ].filter(Boolean)} forceShowLeftItems > {[ !splitted ? ( Split ) : ( Close ), , !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)}
); }