mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
@@ -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
|
||||
|
||||
|
@@ -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 } }),
|
||||
});
|
||||
30
public/app/features/explore/QueriesDrawer/mocks.tsx
Normal file
30
public/app/features/explore/QueriesDrawer/mocks.tsx
Normal file
@@ -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>
|
||||
);
|
||||
}
|
||||
6
public/app/features/explore/QueriesDrawer/utils.ts
Normal file
6
public/app/features/explore/QueriesDrawer/utils.ts
Normal file
@@ -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();
|
||||
reportInteraction('grafana_explore_query_history_opened', {
|
||||
queryHistoryEnabled: config.queryHistoryEnabled,
|
||||
});
|
||||
}, [initRichHistory]);
|
||||
|
||||
if (!richHistorySettings) {
|
||||
const { selectedTab } = useQueriesDrawerContext();
|
||||
const [tracked, setTracked] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!tracked) {
|
||||
setTracked(true);
|
||||
reportInteraction('grafana_explore_query_history_opened', {
|
||||
queryHistoryEnabled: config.queryHistoryEnabled,
|
||||
selectedTab,
|
||||
});
|
||||
}
|
||||
}, [tracked, selectedTab]);
|
||||
|
||||
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(
|
||||
<SecondaryActions
|
||||
addQueryRowButtonHidden={true}
|
||||
richHistoryRowButtonHidden={true}
|
||||
onClickAddQueryRowButton={noop}
|
||||
onClickRichHistoryButton={noop}
|
||||
onClickQueryInspectorButton={noop}
|
||||
/>
|
||||
<QueriesDrawerContextProviderMock queryLibraryAvailable={false}>
|
||||
<SecondaryActions
|
||||
addQueryRowButtonHidden={true}
|
||||
richHistoryRowButtonHidden={true}
|
||||
onClickAddQueryRowButton={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(
|
||||
<SecondaryActions
|
||||
onClickAddQueryRowButton={onClickAddRow}
|
||||
onClickRichHistoryButton={onClickHistory}
|
||||
onClickQueryInspectorButton={onClickQueryInspector}
|
||||
/>
|
||||
<QueriesDrawerContextProviderMock setDrawerOpened={onClickHistory}>
|
||||
<SecondaryActions
|
||||
onClickAddQueryRowButton={onClickAddRow}
|
||||
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": "Ŝŧäřřęđ"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user