Explore: Show a drawer with tabs for the library and query history (#86279)

* 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
pull/86812/head
Piotr Jamróz 1 year ago committed by GitHub
parent de589b98c7
commit f6e472f879
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
  2. 1
      packages/grafana-data/src/types/featureToggles.gen.ts
  3. 8
      pkg/services/featuremgmt/registry.go
  4. 1
      pkg/services/featuremgmt/toggles_gen.csv
  5. 4
      pkg/services/featuremgmt/toggles_gen.go
  6. 15
      pkg/services/featuremgmt/toggles_gen.json
  7. 4
      public/app/features/explore/Explore.test.tsx
  8. 11
      public/app/features/explore/Explore.tsx
  9. 7
      public/app/features/explore/ExploreDrawer.tsx
  10. 23
      public/app/features/explore/ExplorePage.tsx
  11. 2
      public/app/features/explore/ExploreToolbar.tsx
  12. 63
      public/app/features/explore/QueriesDrawer/QueriesDrawerContext.tsx
  13. 66
      public/app/features/explore/QueriesDrawer/QueriesDrawerDropdown.tsx
  14. 30
      public/app/features/explore/QueriesDrawer/mocks.tsx
  15. 6
      public/app/features/explore/QueriesDrawer/utils.ts
  16. 1
      public/app/features/explore/QueryRows.test.tsx
  17. 4
      public/app/features/explore/RichHistory/RichHistory.test.tsx
  18. 23
      public/app/features/explore/RichHistory/RichHistory.tsx
  19. 2
      public/app/features/explore/RichHistory/RichHistoryContainer.test.tsx
  20. 24
      public/app/features/explore/RichHistory/RichHistoryContainer.tsx
  21. 31
      public/app/features/explore/SecondaryActions.test.tsx
  22. 15
      public/app/features/explore/SecondaryActions.tsx
  23. 10
      public/app/features/explore/state/main.ts
  24. 4
      public/app/features/explore/state/selectors.ts
  25. 5
      public/app/types/explore.ts
  26. 1
      public/locales/en-US/grafana.json
  27. 1
      public/locales/pseudo-LOCALE/grafana.json

@ -176,6 +176,7 @@ Experimental features might be changed or removed without prior notice.
| `expressionParser` | Enable new expression parser |
| `accessActionSets` | Introduces action sets for resource permissions |
| `disableNumericMetricsSortingInExpressions` | In server-side expressions, disable the sorting of numeric-kind metrics by their metric name or labels. |
| `queryLibrary` | Enables Query Library feature in Explore |
## Development feature toggles

@ -180,4 +180,5 @@ export interface FeatureToggles {
accessActionSets?: boolean;
disableNumericMetricsSortingInExpressions?: boolean;
grafanaManagedRecordingRules?: boolean;
queryLibrary?: boolean;
}

@ -1211,6 +1211,14 @@ var (
HideFromDocs: true,
HideFromAdminPage: true,
},
{
Name: "queryLibrary",
Description: "Enables Query Library feature in Explore",
Stage: FeatureStageExperimental,
Owner: grafanaExploreSquad,
FrontendOnly: false,
AllowSelfServe: false,
},
}
)

@ -161,3 +161,4 @@ cloudWatchNewLabelParsing,GA,@grafana/aws-datasources,false,false,false
accessActionSets,experimental,@grafana/identity-access-team,false,false,false
disableNumericMetricsSortingInExpressions,experimental,@grafana/observability-metrics,false,true,false
grafanaManagedRecordingRules,experimental,@grafana/alerting-squad,false,false,false
queryLibrary,experimental,@grafana/explore-squad,false,false,false

1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
161 accessActionSets experimental @grafana/identity-access-team false false false
162 disableNumericMetricsSortingInExpressions experimental @grafana/observability-metrics false true false
163 grafanaManagedRecordingRules experimental @grafana/alerting-squad false false false
164 queryLibrary experimental @grafana/explore-squad false false false

@ -654,4 +654,8 @@ const (
// FlagGrafanaManagedRecordingRules
// Enables Grafana-managed recording rules.
FlagGrafanaManagedRecordingRules = "grafanaManagedRecordingRules"
// FlagQueryLibrary
// Enables Query Library feature in Explore
FlagQueryLibrary = "queryLibrary"
)

@ -2088,6 +2088,21 @@
"hideFromAdminPage": true,
"hideFromDocs": true
}
},
{
"metadata": {
"name": "queryLibrary",
"resourceVersion": "1713260947272",
"creationTimestamp": "2024-04-16T07:18:28Z",
"annotations": {
"grafana.app/updatedTimestamp": "2024-04-16 09:49:07.272595 +0000 UTC"
}
},
"spec": {
"description": "Enables Query Library feature in Explore",
"stage": "experimental",
"codeowner": "@grafana/explore-squad"
}
}
]
}

@ -10,7 +10,7 @@ import { configureStore } from 'app/store/configureStore';
import { ContentOutlineContextProvider } from './ContentOutline/ContentOutlineContext';
import { Explore, Props } from './Explore';
import { changeShowQueryHistory, initialExploreState } from './state/main';
import { initialExploreState } from './state/main';
import { scanStopAction } from './state/query';
import { createEmptyQueryResponse, makeExplorePaneState } from './state/utils';
@ -100,8 +100,6 @@ const dummyProps: Props = {
setSupplementaryQueryEnabled: jest.fn(),
correlationEditorDetails: undefined,
correlationEditorHelperData: undefined,
showQueryHistory: false,
changeShowQueryHistory: changeShowQueryHistory,
};
jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => {

@ -56,7 +56,7 @@ import { SecondaryActions } from './SecondaryActions';
import TableContainer from './Table/TableContainer';
import { TraceViewContainer } from './TraceView/TraceViewContainer';
import { changeSize } from './state/explorePane';
import { changeShowQueryHistory, splitOpen } from './state/main';
import { splitOpen } from './state/main';
import {
addQueryRow,
modifyQueries,
@ -304,10 +304,6 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
updateTimeRange({ exploreId, absoluteRange });
};
toggleShowQueryHistory = () => {
this.props.changeShowQueryHistory(!this.props.showQueryHistory);
};
onSplitOpen = (panelType: string) => {
return async (options?: SplitOpenOptions) => {
this.props.splitOpen(options);
@ -535,7 +531,6 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
showLogsSample,
correlationEditorDetails,
correlationEditorHelperData,
showQueryHistory,
showQueryInspector,
setShowQueryInspector,
} = this.props;
@ -603,10 +598,8 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
//TODO:unification
addQueryRowButtonHidden={false}
richHistoryRowButtonHidden={richHistoryRowButtonHidden}
richHistoryButtonActive={showQueryHistory}
queryInspectorButtonActive={showQueryInspector}
onClickAddQueryRowButton={this.onClickAddQueryRowButton}
onClickRichHistoryButton={this.toggleShowQueryHistory}
onClickQueryInspectorButton={() => setShowQueryInspector(!showQueryInspector)}
/>
<ResponseErrorContainer exploreId={exploreId} />
@ -721,7 +714,6 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) {
showLogsSample,
correlationEditorHelperData,
correlationEditorDetails: explore.correlationEditorDetails,
showQueryHistory: explore.showQueryHistory,
};
}
@ -735,7 +727,6 @@ const mapDispatchToProps = {
addQueryRow,
splitOpen,
setSupplementaryQueryEnabled,
changeShowQueryHistory,
};
const connector = connect(mapStateToProps, mapDispatchToProps);

@ -10,18 +10,21 @@ import { getDragStyles, useStyles2, useTheme2 } from '@grafana/ui';
export interface Props {
children: React.ReactNode;
onResize?: ResizeCallback;
initialHeight?: string;
}
export function ExploreDrawer(props: Props) {
const { children, onResize } = props;
const { children, onResize, initialHeight } = props;
const theme = useTheme2();
const styles = useStyles2(getStyles);
const dragStyles = getDragStyles(theme);
const height = initialHeight || `${theme.components.horizontalDrawer.defaultHeight}px`;
return (
<Resizable
className={cx(styles.fixed, styles.container, styles.drawerActive)}
defaultSize={{ width: '100%', height: `${theme.components.horizontalDrawer.defaultHeight}px` }}
defaultSize={{ width: '100%', height }}
handleClasses={{ top: dragStyles.dragHandleHorizontal }}
enable={{
top: true,

@ -9,25 +9,33 @@ import { useGrafana } from 'app/core/context/GrafanaContext';
import { useNavModel } from 'app/core/hooks/useNavModel';
import { Trans } from 'app/core/internationalization';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { useDispatch, useSelector } from 'app/types';
import { useSelector } from 'app/types';
import { ExploreQueryParams } from 'app/types/explore';
import { CorrelationEditorModeBar } from './CorrelationEditorModeBar';
import { ExploreActions } from './ExploreActions';
import { ExploreDrawer } from './ExploreDrawer';
import { ExplorePaneContainer } from './ExplorePaneContainer';
import { QueriesDrawerContextProvider, useQueriesDrawerContext } from './QueriesDrawer/QueriesDrawerContext';
import RichHistoryContainer from './RichHistory/RichHistoryContainer';
import { useExplorePageTitle } from './hooks/useExplorePageTitle';
import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts';
import { useSplitSizeUpdater } from './hooks/useSplitSizeUpdater';
import { useStateSync } from './hooks/useStateSync';
import { useTimeSrvFix } from './hooks/useTimeSrvFix';
import { changeShowQueryHistory } from './state/main';
import { isSplit, selectCorrelationDetails, selectPanesEntries, selectShowQueryHistory } from './state/selectors';
import { isSplit, selectCorrelationDetails, selectPanesEntries } from './state/selectors';
const MIN_PANE_WIDTH = 200;
export default function ExplorePage(props: GrafanaRouteComponentProps<{}, ExploreQueryParams>) {
return (
<QueriesDrawerContextProvider>
<ExplorePageContent {...props} />
</QueriesDrawerContextProvider>
);
}
function ExplorePageContent(props: GrafanaRouteComponentProps<{}, ExploreQueryParams>) {
const styles = useStyles2(getStyles);
const theme = useTheme2();
useTimeSrvFix();
@ -40,13 +48,12 @@ export default function ExplorePage(props: GrafanaRouteComponentProps<{}, Explor
useExplorePageTitle(props.queryParams);
const { chrome } = useGrafana();
const navModel = useNavModel('explore');
const dispatch = useDispatch();
const { updateSplitSize, widthCalc } = useSplitSizeUpdater(MIN_PANE_WIDTH);
const panes = useSelector(selectPanesEntries);
const hasSplit = useSelector(isSplit);
const correlationDetails = useSelector(selectCorrelationDetails);
const showQueryHistory = useSelector(selectShowQueryHistory);
const { drawerOpened, setDrawerOpened, queryLibraryAvailable } = useQueriesDrawerContext();
const showCorrelationEditorBar = config.featureToggles.correlations && (correlationDetails?.editorMode || false);
useEffect(() => {
@ -89,11 +96,11 @@ export default function ExplorePage(props: GrafanaRouteComponentProps<{}, Explor
);
})}
</SplitPaneWrapper>
{showQueryHistory && (
<ExploreDrawer>
{drawerOpened && (
<ExploreDrawer initialHeight={queryLibraryAvailable ? '75vh' : undefined}>
<RichHistoryContainer
onClose={() => {
dispatch(changeShowQueryHistory(false));
setDrawerOpened(false);
}}
/>
</ExploreDrawer>

@ -26,6 +26,7 @@ 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';
@ -238,6 +239,7 @@ export function ExploreToolbar({ exploreId, onChangeTime, onContentOutlineToogle
forceShowLeftItems
>
{[
<QueriesDrawerDropdown key="queryLibrary" variant={splitted ? 'compact' : 'full'} />,
!splitted ? (
<ToolbarButton
variant="canvas"

@ -0,0 +1,63 @@
import React, { PropsWithChildren, useState, createContext, useContext, useEffect } from 'react';
import { config } from '@grafana/runtime';
import { useSelector } from 'app/types';
import { selectRichHistorySettings } from '../state/selectors';
export enum Tabs {
QueryLibrary = 'Query library',
RichHistory = 'Query history',
Starred = 'Starred',
Settings = 'Settings',
}
type QueryLibraryContextType = {
selectedTab?: Tabs;
setSelectedTab: (tab: Tabs) => void;
queryLibraryAvailable: boolean;
drawerOpened: boolean;
setDrawerOpened: (value: boolean) => void;
};
export const QueriesDrawerContext = createContext<QueryLibraryContextType>({
selectedTab: undefined,
setSelectedTab: () => {},
queryLibraryAvailable: false,
drawerOpened: false,
setDrawerOpened: () => {},
});
export function useQueriesDrawerContext() {
return useContext(QueriesDrawerContext);
}
export function QueriesDrawerContextProvider({ children }: PropsWithChildren) {
const queryLibraryAvailable = config.featureToggles.queryLibrary === true;
const [selectedTab, setSelectedTab] = useState<Tabs | undefined>(
queryLibraryAvailable ? Tabs.QueryLibrary : undefined
);
const [drawerOpened, setDrawerOpened] = useState<boolean>(false);
const settings = useSelector(selectRichHistorySettings);
useEffect(() => {
if (settings && !queryLibraryAvailable) {
setSelectedTab(settings.starredTabAsFirstTab ? Tabs.Starred : Tabs.RichHistory);
}
}, [settings, setSelectedTab, queryLibraryAvailable]);
return (
<QueriesDrawerContext.Provider
value={{
queryLibraryAvailable,
selectedTab,
setSelectedTab,
drawerOpened,
setDrawerOpened,
}}
>
{children}
</QueriesDrawerContext.Provider>
);
}

@ -0,0 +1,66 @@
import { css } from '@emotion/css';
import React from 'react';
import { Button, ButtonGroup, Dropdown, Menu, ToolbarButton } from '@grafana/ui';
import { useStyles2 } from '@grafana/ui/';
import { Tabs, useQueriesDrawerContext } from './QueriesDrawerContext';
import { i18n } from './utils';
type Props = {
variant: 'compact' | 'full';
};
export function QueriesDrawerDropdown({ variant }: Props) {
const { selectedTab, setSelectedTab, queryLibraryAvailable, drawerOpened, setDrawerOpened } =
useQueriesDrawerContext();
const styles = useStyles2(getStyles);
if (!queryLibraryAvailable) {
return undefined;
}
function toggle(tab: Tabs) {
setSelectedTab(tab);
setDrawerOpened(false);
setDrawerOpened(true);
}
const menu = (
<Menu>
<Menu.Item label={i18n.queryLibrary} onClick={() => toggle(Tabs.QueryLibrary)} />
<Menu.Item label={i18n.queryHistory} onClick={() => toggle(Tabs.RichHistory)} />
</Menu>
);
return (
<ButtonGroup>
<ToolbarButton
icon="book"
variant={drawerOpened ? 'active' : 'canvas'}
onClick={() => setDrawerOpened(!drawerOpened)}
>
{variant === 'full' ? selectedTab : undefined}
</ToolbarButton>
{drawerOpened ? (
<Button
className={styles.close}
variant="secondary"
icon="times"
onClick={() => setDrawerOpened(false)}
></Button>
) : (
<Dropdown overlay={menu}>
<ToolbarButton className={styles.toggle} variant="canvas" icon="angle-down" />
</Dropdown>
)}
</ButtonGroup>
);
}
const getStyles = () => ({
toggle: css({ width: '36px' }),
// tweaking icon position so it's nicely aligned when dropdown turns into a close button
close: css({ width: '36px', '> svg': { position: 'relative', left: 2 } }),
});

@ -0,0 +1,30 @@
import React, { PropsWithChildren, useState } from 'react';
import { QueriesDrawerContext, Tabs } from './QueriesDrawerContext';
type Props = {
setDrawerOpened?: (value: boolean) => {};
queryLibraryAvailable?: boolean;
} & PropsWithChildren;
export function QueriesDrawerContextProviderMock(props: Props) {
const [selectedTab, setSelectedTab] = useState<Tabs>(Tabs.QueryLibrary);
const [drawerOpened, setDrawerOpened] = useState<boolean>(false);
return (
<QueriesDrawerContext.Provider
value={{
queryLibraryAvailable: props.queryLibraryAvailable || false,
selectedTab,
setSelectedTab,
drawerOpened,
setDrawerOpened: (value) => {
props.setDrawerOpened?.(value);
setDrawerOpened(value);
},
}}
>
{props.children}
</QueriesDrawerContext.Provider>
);
}

@ -0,0 +1,6 @@
import { t } from 'app//core/internationalization';
export const i18n = {
queryLibrary: t('explore.rich-history.query-library', 'Query library'),
queryHistory: t('explore.rich-history.query-history', 'Query history'),
};

@ -52,7 +52,6 @@ function setup(queries: DataQuery[]) {
const leftState = makeExplorePaneState();
const initialState: ExploreState = {
richHistory: [],
showQueryHistory: false,
panes: {
left: {
...leftState,

@ -4,7 +4,9 @@ import { TestProvider } from 'test/helpers/TestProvider';
import { SortOrder } from 'app/core/utils/richHistory';
import { RichHistory, RichHistoryProps, Tabs } from './RichHistory';
import { Tabs } from '../QueriesDrawer/QueriesDrawerContext';
import { RichHistory, RichHistoryProps } from './RichHistory';
jest.mock('../state/selectors', () => ({ selectExploreDSMaps: jest.fn().mockReturnValue({ dsToExplore: [] }) }));

@ -3,23 +3,19 @@ import React, { useState, useEffect } from 'react';
import { SelectableValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { TabbedContainer, TabConfig } from '@grafana/ui';
import { EmptyState, TabbedContainer, TabConfig } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { SortOrder, RichHistorySearchFilters, RichHistorySettings } from 'app/core/utils/richHistory';
import { RichHistoryQuery } from 'app/types/explore';
import { supportedFeatures } from '../../../core/history/richHistoryStorageProvider';
import { Tabs, useQueriesDrawerContext } from '../QueriesDrawer/QueriesDrawerContext';
import { i18n } from '../QueriesDrawer/utils';
import { RichHistoryQueriesTab } from './RichHistoryQueriesTab';
import { RichHistorySettingsTab } from './RichHistorySettingsTab';
import { RichHistoryStarredTab } from './RichHistoryStarredTab';
export enum Tabs {
RichHistory = 'Query history',
Starred = 'Starred',
Settings = 'Settings',
}
export const getSortOrderOptions = () =>
[
{ label: t('explore.rich-history.newest-first', 'Newest first'), value: SortOrder.Descending },
@ -49,6 +45,8 @@ export function RichHistory(props: RichHistoryProps) {
const [loading, setLoading] = useState(false);
const { queryLibraryAvailable } = useQueriesDrawerContext();
const updateSettings = (settingsToUpdate: Partial<RichHistorySettings>) => {
props.updateHistorySettings({ ...props.richHistorySettings, ...settingsToUpdate });
};
@ -84,8 +82,15 @@ export function RichHistory(props: RichHistoryProps) {
setLoading(false);
}, [richHistory]);
const QueryLibraryTab: TabConfig = {
label: i18n.queryLibrary,
value: Tabs.QueryLibrary,
content: <EmptyState message="Coming soon!" variant="not-found" />,
icon: 'book',
};
const QueriesTab: TabConfig = {
label: t('explore.rich-history.query-history', 'Query history'),
label: i18n.queryHistory,
value: Tabs.RichHistory,
content: (
<RichHistoryQueriesTab
@ -138,7 +143,7 @@ export function RichHistory(props: RichHistoryProps) {
icon: 'sliders-v-alt',
};
let tabs = [QueriesTab, StarredTab, SettingsTab];
let tabs = (queryLibraryAvailable ? [QueryLibraryTab] : []).concat([QueriesTab, StarredTab, SettingsTab]);
return (
<TabbedContainer
tabs={tabs}

@ -4,7 +4,6 @@ import { TestProvider } from 'test/helpers/TestProvider';
import { SortOrder } from 'app/core/utils/richHistory';
import { Tabs } from './RichHistory';
import { RichHistoryContainer, Props } from './RichHistoryContainer';
jest.mock('@grafana/runtime', () => ({
@ -27,7 +26,6 @@ jest.mock('../state/selectors', () => ({ selectExploreDSMaps: jest.fn().mockRetu
const setup = (propOverrides?: Partial<Props>) => {
const props: Props = {
richHistory: [],
firstTab: Tabs.RichHistory,
deleteRichHistory: jest.fn(),
initRichHistory: jest.fn(),
loadRichHistory: jest.fn(),

@ -1,5 +1,5 @@
// Libraries
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { config, reportInteraction } from '@grafana/runtime';
@ -9,6 +9,7 @@ import { Trans } from 'app/core/internationalization';
import { StoreState } from 'app/types';
// Components, enums
import { useQueriesDrawerContext } from '../QueriesDrawer/QueriesDrawerContext';
import {
deleteRichHistory,
initRichHistory,
@ -19,7 +20,7 @@ import {
updateHistorySearchFilters,
} from '../state/history';
import { RichHistory, Tabs } from './RichHistory';
import { RichHistory } from './RichHistory';
//Actions
@ -28,11 +29,9 @@ function mapStateToProps(state: StoreState) {
const richHistorySearchFilters = explore.richHistorySearchFilters;
const { richHistorySettings, richHistory, richHistoryTotal } = explore;
const firstTab = richHistorySettings?.starredTabAsFirstTab ? Tabs.Starred : Tabs.RichHistory;
return {
richHistory,
richHistoryTotal,
firstTab,
richHistorySettings,
richHistorySearchFilters,
};
@ -61,7 +60,6 @@ export function RichHistoryContainer(props: Props) {
const {
richHistory,
richHistoryTotal,
firstTab,
deleteRichHistory,
initRichHistory,
loadRichHistory,
@ -76,12 +74,22 @@ export function RichHistoryContainer(props: Props) {
useEffect(() => {
initRichHistory();
}, [initRichHistory]);
const { selectedTab } = useQueriesDrawerContext();
const [tracked, setTracked] = useState(false);
useEffect(() => {
if (!tracked) {
setTracked(true);
reportInteraction('grafana_explore_query_history_opened', {
queryHistoryEnabled: config.queryHistoryEnabled,
selectedTab,
});
}, [initRichHistory]);
}
}, [tracked, selectedTab]);
if (!richHistorySettings) {
if (!richHistorySettings || !selectedTab) {
return (
<span>
<Trans i18nKey="explore.rich-history-container.loading">Loading...</Trans>
@ -93,7 +101,7 @@ export function RichHistoryContainer(props: Props) {
<RichHistory
richHistory={richHistory}
richHistoryTotal={richHistoryTotal}
firstTab={firstTab}
firstTab={selectedTab}
onClose={onClose}
height={theme.components.horizontalDrawer.defaultHeight}
deleteRichHistory={deleteRichHistory}

@ -3,17 +3,12 @@ import userEvent from '@testing-library/user-event';
import { noop } from 'lodash';
import React from 'react';
import { QueriesDrawerContextProviderMock } from './QueriesDrawer/mocks';
import { SecondaryActions } from './SecondaryActions';
describe('SecondaryActions', () => {
it('should render component with three buttons', () => {
render(
<SecondaryActions
onClickAddQueryRowButton={noop}
onClickRichHistoryButton={noop}
onClickQueryInspectorButton={noop}
/>
);
render(<SecondaryActions onClickAddQueryRowButton={noop} onClickQueryInspectorButton={noop} />);
expect(screen.getByRole('button', { name: /Add query/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /Query history/i })).toBeInTheDocument();
@ -22,13 +17,14 @@ describe('SecondaryActions', () => {
it('should not render hidden elements', () => {
render(
<QueriesDrawerContextProviderMock queryLibraryAvailable={false}>
<SecondaryActions
addQueryRowButtonHidden={true}
richHistoryRowButtonHidden={true}
onClickAddQueryRowButton={noop}
onClickRichHistoryButton={noop}
onClickQueryInspectorButton={noop}
/>
</QueriesDrawerContextProviderMock>
);
expect(screen.queryByRole('button', { name: /Add query/i })).not.toBeInTheDocument();
@ -36,12 +32,25 @@ describe('SecondaryActions', () => {
expect(screen.getByRole('button', { name: /Query inspector/i })).toBeInTheDocument();
});
it('should not render query history button when query library is available', () => {
render(
<QueriesDrawerContextProviderMock queryLibraryAvailable={true}>
<SecondaryActions
richHistoryRowButtonHidden={false}
onClickAddQueryRowButton={noop}
onClickQueryInspectorButton={noop}
/>
</QueriesDrawerContextProviderMock>
);
expect(screen.queryByRole('button', { name: /Query history/i })).not.toBeInTheDocument();
});
it('should disable add row button if addQueryRowButtonDisabled=true', () => {
render(
<SecondaryActions
addQueryRowButtonDisabled={true}
onClickAddQueryRowButton={noop}
onClickRichHistoryButton={noop}
onClickQueryInspectorButton={noop}
/>
);
@ -59,11 +68,12 @@ describe('SecondaryActions', () => {
const onClickQueryInspector = jest.fn();
render(
<QueriesDrawerContextProviderMock setDrawerOpened={onClickHistory}>
<SecondaryActions
onClickAddQueryRowButton={onClickAddRow}
onClickRichHistoryButton={onClickHistory}
onClickQueryInspectorButton={onClickQueryInspector}
/>
</QueriesDrawerContextProviderMock>
);
await user.click(screen.getByRole('button', { name: /Add query/i }));
@ -71,6 +81,7 @@ describe('SecondaryActions', () => {
await user.click(screen.getByRole('button', { name: /Query history/i }));
expect(onClickHistory).toBeCalledTimes(1);
expect(onClickHistory).toBeCalledWith(true);
await user.click(screen.getByRole('button', { name: /Query inspector/i }));
expect(onClickQueryInspector).toBeCalledTimes(1);

@ -6,15 +6,15 @@ import { Components } from '@grafana/e2e-selectors';
import { ToolbarButton, useTheme2 } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization';
import { useQueriesDrawerContext } from './QueriesDrawer/QueriesDrawerContext';
type Props = {
addQueryRowButtonDisabled?: boolean;
addQueryRowButtonHidden?: boolean;
richHistoryRowButtonHidden?: boolean;
richHistoryButtonActive?: boolean;
queryInspectorButtonActive?: boolean;
onClickAddQueryRowButton: () => void;
onClickRichHistoryButton: () => void;
onClickQueryInspectorButton: () => void;
};
@ -32,6 +32,11 @@ const getStyles = (theme: GrafanaTheme2) => {
export function SecondaryActions(props: Props) {
const theme = useTheme2();
const styles = getStyles(theme);
const { drawerOpened, setDrawerOpened, queryLibraryAvailable } = useQueriesDrawerContext();
// When queryLibraryAvailable=true we show the button in the toolbar (see QueriesDrawerDropdown)
const showHistoryButton = !props.richHistoryRowButtonHidden && !queryLibraryAvailable;
return (
<div className={styles.containerMargin}>
{!props.addQueryRowButtonHidden && (
@ -45,11 +50,11 @@ export function SecondaryActions(props: Props) {
<Trans i18nKey="explore.secondary-actions.query-add-button">Add query</Trans>
</ToolbarButton>
)}
{!props.richHistoryRowButtonHidden && (
{showHistoryButton && (
<ToolbarButton
variant={props.richHistoryButtonActive ? 'active' : 'canvas'}
variant={drawerOpened ? 'active' : 'canvas'}
aria-label={t('explore.secondary-actions.query-history-button-aria-label', 'Query history')}
onClick={props.onClickRichHistoryButton}
onClick={() => setDrawerOpened(!drawerOpened)}
data-testid={Components.QueryTab.queryHistoryButton}
icon="history"
>

@ -124,8 +124,6 @@ export const changeCorrelationEditorDetails = createAction<CorrelationEditorDeta
'explore/changeCorrelationEditorDetails'
);
export const changeShowQueryHistory = createAction<boolean>('explore/changeShowQueryHistory');
export interface NavigateToExploreDependencies {
timeRange: TimeRange;
getExploreUrl: (args: GetExploreUrlArguments) => Promise<string | undefined>;
@ -169,7 +167,6 @@ export const initialExploreState: ExploreState = {
largerExploreId: undefined,
maxedExploreId: undefined,
evenSplitPanes: true,
showQueryHistory: false,
richHistory: [],
};
@ -323,13 +320,6 @@ export const exploreReducer = (state = initialExploreState, action: AnyAction):
};
}
if (changeShowQueryHistory.match(action)) {
return {
...state,
showQueryHistory: action.payload,
};
}
const exploreId: string | undefined = action.payload?.exploreId;
if (typeof exploreId === 'string') {
return {

@ -7,6 +7,8 @@ import { ExploreItemState, StoreState } from 'app/types';
export const selectPanes = (state: Pick<StoreState, 'explore'>) => state.explore.panes;
export const selectExploreRoot = (state: Pick<StoreState, 'explore'>) => state.explore;
export const selectRichHistorySettings = (state: Pick<StoreState, 'explore'>) => state.explore.richHistorySettings;
export const selectPanesEntries = createSelector<
[(state: Pick<StoreState, 'explore'>) => Record<string, ExploreItemState | undefined>],
Array<[string, ExploreItemState]>
@ -26,8 +28,6 @@ export const getExploreItemSelector = (exploreId: string) => createSelector(sele
export const selectCorrelationDetails = createSelector(selectExploreRoot, (state) => state.correlationEditorDetails);
export const selectShowQueryHistory = createSelector(selectExploreRoot, (state) => state.showQueryHistory);
export const selectExploreDSMaps = createSelector(selectPanesEntries, (panes) => {
const exploreDSMap = panes
.map(([exploreId, pane]) => {

@ -63,11 +63,6 @@ export interface ExploreState {
panes: Record<string, ExploreItemState | undefined>;
/**
* Is the drawer for query history showing
*/
showQueryHistory: boolean;
/**
* History of all queries
*/

@ -472,6 +472,7 @@
"newest-first": "Newest first",
"oldest-first": "Oldest first",
"query-history": "Query history",
"query-library": "Query library",
"settings": "Settings",
"starred": "Starred"
},

@ -472,6 +472,7 @@
"newest-first": "Ńęŵęşŧ ƒįřşŧ",
"oldest-first": "Øľđęşŧ ƒįřşŧ",
"query-history": "Qūęřy ĥįşŧőřy",
"query-library": "Qūęřy ľįþřäřy",
"settings": "Ŝęŧŧįʼnģş",
"starred": "Ŝŧäřřęđ"
},

Loading…
Cancel
Save