mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
* Create basic feature toggle * Rename context to reflect it contains query history and query library * Update icons and variants * Rename hooks * Update tests * Fix mock * Add tracking * Turn button into a toggle * Make dropdown active as well This is required to have better UI and an indication of selected state in split view * Update Query Library icon This is to make it consistent with the toolbar button * Hide query history button when query library is available This is to avoid confusing UX with 2 button triggering the drawer but with slightly different behavior * Make the drawer bigger for query library To avoid confusion for current users and test it internally a bit more it's behind a feature toggle. Bigger drawer may obstruct the view and add more friction in the UX. * Fix tests The test was failing because queryLibraryAvailable was set to true for tests. This change makes it more explicit what use case is being tested * Remove active state underline from the dropdown * Allow closing Query Library drawer from the toolbar * Simplify dropdown design
340 lines
12 KiB
TypeScript
340 lines
12 KiB
TypeScript
import { css, cx } from '@emotion/css';
|
|
import { pick } from 'lodash';
|
|
import React, { useMemo } from 'react';
|
|
import { shallowEqual } from 'react-redux';
|
|
|
|
import { DataSourceInstanceSettings, RawTimeRange, GrafanaTheme2 } from '@grafana/data';
|
|
import { reportInteraction, config } 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 { 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 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 = [<ShortLinkButtonMenu key="share" />, <div style={{ flex: 1 }} key="spacer0" />];
|
|
|
|
return (
|
|
<div>
|
|
{refreshInterval && <SetInterval func={onRunQuery} interval={refreshInterval} loading={loading} />}
|
|
<div>
|
|
<AppChromeUpdate actions={navBarActions} />
|
|
</div>
|
|
<PageToolbar
|
|
aria-label={t('explore.toolbar.aria-label', 'Explore toolbar')}
|
|
leftItems={[
|
|
config.featureToggles.exploreContentOutline && (
|
|
<ToolbarButton
|
|
key="content-outline"
|
|
variant="canvas"
|
|
tooltip="Content outline"
|
|
icon="list-ui-alt"
|
|
iconOnly={splitted}
|
|
onClick={onContentOutlineToogle}
|
|
aria-expanded={isContentOutlineOpen}
|
|
aria-controls={isContentOutlineOpen ? 'content-outline-container' : undefined}
|
|
className={styles.toolbarButton}
|
|
>
|
|
Outline
|
|
</ToolbarButton>
|
|
),
|
|
<DataSourcePicker
|
|
key={`${exploreId}-ds-picker`}
|
|
mixed={!isCorrelationsEditorMode}
|
|
onChange={onChangeDatasource}
|
|
current={datasourceInstance?.getRef()}
|
|
hideTextValue={showSmallDataSourcePicker}
|
|
width={showSmallDataSourcePicker ? 8 : undefined}
|
|
/>,
|
|
].filter(Boolean)}
|
|
forceShowLeftItems
|
|
>
|
|
{[
|
|
<QueriesDrawerDropdown key="queryLibrary" variant={splitted ? 'compact' : 'full'} />,
|
|
!splitted ? (
|
|
<ToolbarButton
|
|
variant="canvas"
|
|
key="split"
|
|
tooltip={t('explore.toolbar.split-tooltip', 'Split the pane')}
|
|
onClick={onOpenSplitView}
|
|
icon="columns"
|
|
disabled={isLive}
|
|
>
|
|
<Trans i18nKey="explore.toolbar.split-title">Split</Trans>
|
|
</ToolbarButton>
|
|
) : (
|
|
<ButtonGroup key="split-controls">
|
|
<ToolbarButton
|
|
variant="canvas"
|
|
tooltip={
|
|
isLargerPane
|
|
? t('explore.toolbar.split-narrow', 'Narrow pane')
|
|
: t('explore.toolbar.split-widen', 'Widen pane')
|
|
}
|
|
onClick={onClickResize}
|
|
icon={isLargerPane ? 'gf-movepane-left' : 'gf-movepane-right'}
|
|
iconOnly={true}
|
|
className={cx(shouldRotateSplitIcon && styles.rotateIcon)}
|
|
/>
|
|
<ToolbarButton
|
|
tooltip={t('explore.toolbar.split-close-tooltip', 'Close split pane')}
|
|
onClick={onCloseSplitView}
|
|
icon="times"
|
|
variant="canvas"
|
|
>
|
|
<Trans i18nKey="explore.toolbar.split-close"> Close </Trans>
|
|
</ToolbarButton>
|
|
</ButtonGroup>
|
|
),
|
|
<ToolbarExtensionPoint key="toolbar-extension-point" exploreId={exploreId} timeZone={timeZone} />,
|
|
!isLive && (
|
|
<ExploreTimeControls
|
|
key="timeControls"
|
|
exploreId={exploreId}
|
|
range={range}
|
|
timeZone={timeZone}
|
|
fiscalYearStartMonth={fiscalYearStartMonth}
|
|
onChangeTime={onChangeTime}
|
|
splitted={splitted}
|
|
syncedTimes={syncedTimes}
|
|
onChangeTimeSync={onChangeTimeSync}
|
|
hideText={showSmallTimePicker}
|
|
onChangeTimeZone={onChangeTimeZone}
|
|
onChangeFiscalYearStartMonth={onChangeFiscalYearStartMonth}
|
|
/>
|
|
),
|
|
<RefreshPicker
|
|
key="refreshPicker"
|
|
onIntervalChanged={onChangeRefreshInterval}
|
|
value={refreshInterval}
|
|
isLoading={loading}
|
|
text={showSmallTimePicker ? undefined : refreshPickerLabel}
|
|
tooltip={showSmallTimePicker ? refreshPickerLabel : undefined}
|
|
intervals={contextSrv.getValidIntervals(defaultIntervals)}
|
|
isLive={isLive}
|
|
onRefresh={() => onRunQuery(loading)}
|
|
noIntervalPicker={isLive}
|
|
primary={true}
|
|
width={(showSmallTimePicker ? 35 : 108) + 'px'}
|
|
/>,
|
|
datasourceInstance?.meta.streaming && (
|
|
<LiveTailControls key="liveControls" exploreId={exploreId}>
|
|
{(c) => {
|
|
const controls = {
|
|
...c,
|
|
start: () => {
|
|
reportInteraction('grafana_explore_logs_live_tailing_clicked', {
|
|
datasourceType: datasourceInstance?.type,
|
|
});
|
|
c.start();
|
|
},
|
|
};
|
|
return (
|
|
<LiveTailButton
|
|
splitted={splitted}
|
|
isLive={isLive}
|
|
isPaused={isPaused}
|
|
start={controls.start}
|
|
pause={controls.pause}
|
|
resume={controls.resume}
|
|
stop={controls.stop}
|
|
/>
|
|
);
|
|
}}
|
|
</LiveTailControls>
|
|
),
|
|
].filter(Boolean)}
|
|
</PageToolbar>
|
|
</div>
|
|
);
|
|
}
|