diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx index 668b8612851..0ed01ae59b7 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx @@ -394,8 +394,12 @@ export function PanelDataQueriesTabRendered({ model }: SceneComponentProps - openQueryLibraryDrawer(getDatasourceNames(datasource, queries), onSelectQueryFromLibrary, { - context: CoreApp.PanelEditor, + openQueryLibraryDrawer({ + datasourceFilters: getDatasourceNames(datasource, queries), + onSelectQuery: onSelectQueryFromLibrary, + options: { + context: CoreApp.PanelEditor, + }, }) } variant="secondary" diff --git a/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx b/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx index 411575cb961..1e0e571f8e4 100644 --- a/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx +++ b/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx @@ -3,7 +3,14 @@ import { createContext, ReactNode, useContext } from 'react'; import { CoreApp } from '@grafana/data'; import { DataQuery } from '@grafana/schema'; -import { OnSelectQueryType } from './types'; +import { OnSelectQueryType, QueryTemplate } from './types'; + +export type QueryLibraryDrawerOptions = { + datasourceFilters?: string[]; + onSelectQuery?: OnSelectQueryType; + options?: { isReplacingQuery?: boolean; onSave?: () => void; context?: string; highlightQuery?: string }; + query?: DataQuery; +}; /** * Context with state and action to interact with Query Library. The Query Library feature consists of a drawer @@ -16,36 +23,15 @@ export type QueryLibraryContextType = { /** * Opens a drawer with query library. * @param datasourceFilters Data source names that will be used for initial filter in the library. - * @param queryActionButton Action button will be shown in the library next to the query and can implement context - * specific actions with the library, like running the query or updating some query in the current app. + * @param onSelectQuery Callback to be called when a query is selected from the library. * @param options.context Used for QueryEditor. Should identify the context this is called from, like 'explore' or * 'dashboard'. + * @param newQuery New query to be added to the library. */ - openDrawer: ( - datasourceFilters: string[], - onSelectQuery: OnSelectQueryType, - options?: { - isReplacingQuery?: boolean; - context?: string; - highlightQuery?: string; - } - ) => void; + openDrawer: (options: QueryLibraryDrawerOptions) => void; closeDrawer: () => void; isDrawerOpen: boolean; - - /** - * Opens a modal for adding a query to the library. - * @param query Query to be saved - * @param options.onSave Callback that will be called after the query is saved. - * @param options.context Used for rendering QueryEditor. Should identify the context this is called from, like 'explore' or - * 'dashboard'. - * @param options.title Default title for the modal, can be overridden by the query title. - */ - openAddQueryModal: ( - query: DataQuery, - options?: { onSave?: () => void; context?: string; title?: string; isDuplicating?: boolean } - ) => void; - closeAddQueryModal: () => void; + onSave?: () => void; /** * Returns a predefined small button that can be used to save a query to the library. @@ -55,10 +41,12 @@ export type QueryLibraryContextType = { query: DataQuery, app?: CoreApp, queryLibraryRef?: string, - onUpdateSuccess?: () => void + onUpdateSuccess?: () => void, + onSelectQuery?: (query: DataQuery) => void ) => ReactNode; queryLibraryEnabled: boolean; context: string; + setNewQuery: (query?: QueryTemplate) => void; }; export const QueryLibraryContext = createContext({ @@ -66,8 +54,8 @@ export const QueryLibraryContext = createContext({ closeDrawer: () => {}, isDrawerOpen: false, - openAddQueryModal: () => {}, - closeAddQueryModal: () => {}, + setNewQuery: () => {}, + onSave: () => {}, renderSaveQueryButton: () => { return null; diff --git a/public/app/features/explore/QueryLibrary/mocks.tsx b/public/app/features/explore/QueryLibrary/mocks.tsx index e59732870a9..ccb6b1c6b99 100644 --- a/public/app/features/explore/QueryLibrary/mocks.tsx +++ b/public/app/features/explore/QueryLibrary/mocks.tsx @@ -13,11 +13,10 @@ export function QueryLibraryContextProviderMock(props: PropsWithChildren) openDrawer: jest.fn(), closeDrawer: jest.fn(), isDrawerOpen: false, - openAddQueryModal: jest.fn(), - closeAddQueryModal: jest.fn(), renderSaveQueryButton: jest.fn(), queryLibraryEnabled: Boolean(props.queryLibraryEnabled), context: 'explore', + setNewQuery: jest.fn(), }} > {props.children} diff --git a/public/app/features/explore/QueryLibrary/types.ts b/public/app/features/explore/QueryLibrary/types.ts index 50db96feeb0..1877b30c3df 100644 --- a/public/app/features/explore/QueryLibrary/types.ts +++ b/public/app/features/explore/QueryLibrary/types.ts @@ -1,3 +1,25 @@ -import { DataQuery } from '@grafana/schema'; +import { DataQuery, DataSourceRef } from '@grafana/schema'; + +export type User = { + uid: string; + displayName?: string; + avatarUrl?: string; +}; export type OnSelectQueryType = (query: DataQuery) => void; + +export type QueryTemplate = { + query: DataQuery; + datasourceName?: string; + title?: string; + description?: string; + tags?: string[]; + isLocked?: boolean; + isVisible?: boolean; + queryText?: string; + datasourceRef?: DataSourceRef | null; + datasourceType?: string; + createdAtTimestamp?: number; + user?: User; + uid?: string; +}; diff --git a/public/app/features/explore/QueryRows.tsx b/public/app/features/explore/QueryRows.tsx index 672d9cee950..16beb902c36 100644 --- a/public/app/features/explore/QueryRows.tsx +++ b/public/app/features/explore/QueryRows.tsx @@ -107,9 +107,12 @@ export const QueryRows = ({ exploreId, isOpen, changeCompactMode }: Props) => { // Open drawer with the original query highlighted if (originalQueryRef) { - openDrawer([], () => {}, { - context: 'explore', - highlightQuery: originalQueryRef, + openDrawer({ + datasourceFilters: [], + options: { + context: 'explore', + highlightQuery: originalQueryRef, + }, }); } }; diff --git a/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx b/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx index e99c0e6cebc..9082d02bfc2 100644 --- a/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx @@ -1,10 +1,14 @@ import { useState } from 'react'; import { t } from '@grafana/i18n'; +import { reportInteraction } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; import { Button } from '@grafana/ui'; +import { useDispatch, useSelector } from 'app/types/store'; import { useQueryLibraryContext } from '../QueryLibrary/QueryLibraryContext'; +import { changeQueries } from '../state/query'; +import { selectExploreDSMaps } from '../state/selectors'; type Props = { query: DataQuery; @@ -12,7 +16,17 @@ type Props = { export const RichHistoryAddToLibrary = ({ query }: Props) => { const [hasBeenSaved, setHasBeenSaved] = useState(false); - const { openAddQueryModal, queryLibraryEnabled } = useQueryLibraryContext(); + const { openDrawer, queryLibraryEnabled } = useQueryLibraryContext(); + const dispatch = useDispatch(); + const exploreActiveDS = useSelector(selectExploreDSMaps); + const exploreId = exploreActiveDS.exploreToDS[0]?.exploreId; + + const onSelectQuery = (newQuery: DataQuery) => { + reportInteraction('grafana_explore_query_replaced_from_library'); + if (exploreId) { + dispatch(changeQueries({ exploreId, queries: [newQuery] })); + } + }; const buttonLabel = t('explore.rich-history-card.add-to-library', 'Add to library'); @@ -22,7 +36,16 @@ export const RichHistoryAddToLibrary = ({ query }: Props) => { variant="secondary" aria-label={buttonLabel} onClick={() => { - openAddQueryModal(query, { onSave: () => setHasBeenSaved(true), context: 'rich-history' }); + openDrawer({ + query, + onSelectQuery, + options: { + onSave: () => { + setHasBeenSaved(true); + }, + context: 'rich-history', + }, + }); }} > {buttonLabel} diff --git a/public/app/features/explore/SecondaryActions.tsx b/public/app/features/explore/SecondaryActions.tsx index a54daf81694..d3672f6fc32 100644 --- a/public/app/features/explore/SecondaryActions.tsx +++ b/public/app/features/explore/SecondaryActions.tsx @@ -77,8 +77,10 @@ export function SecondaryActions({ aria-label={t('explore.secondary-actions.add-from-query-library', 'Add query from library')} variant="canvas" onClick={() => - openQueryLibraryDrawer(activeDatasources, onSelectQueryFromLibrary, { - context: CoreApp.Explore, + openQueryLibraryDrawer({ + datasourceFilters: activeDatasources, + onSelectQuery: onSelectQueryFromLibrary, + options: { context: CoreApp.Explore }, }) } icon="plus" diff --git a/public/app/features/explore/spec/helper/interactions.ts b/public/app/features/explore/spec/helper/interactions.ts index a83dfb2d9c8..840eccdee1a 100644 --- a/public/app/features/explore/spec/helper/interactions.ts +++ b/public/app/features/explore/spec/helper/interactions.ts @@ -56,7 +56,11 @@ export const addQueryHistoryToQueryLibrary = async () => { }; export const submitAddToQueryLibrary = async ({ title }: { title: string }) => { - const input = within(screen.getByRole('dialog')).getByLabelText('Title'); + const container = screen.getByRole('dialog', { + name: /Drawer title/i, + }); + + const input = within(container).getByRole('textbox', { name: /title/i }); await userEvent.type(input, title); const saveButton = screen.getByRole('button', { name: /^save$/i, diff --git a/public/app/features/query/components/QueryEditorRow.tsx b/public/app/features/query/components/QueryEditorRow.tsx index 65631c9b568..a8bda63f862 100644 --- a/public/app/features/query/components/QueryEditorRow.tsx +++ b/public/app/features/query/components/QueryEditorRow.tsx @@ -290,6 +290,11 @@ export class QueryEditorRow extends PureComponent { + this.props.onQueryReplacedFromLibrary?.(); + this.props.onReplace?.(query); + }; + renderCollapsedText(): string | null { const { datasource } = this.state; if (datasource?.getQueryDisplayText) { @@ -378,13 +383,7 @@ export class QueryEditorRow extends PureComponent { - const { - query, - hideHideQueryButton: hideHideQueryButton = false, - onReplace, - onQueryReplacedFromLibrary, - queryLibraryRef, - } = this.props; + const { query, hideHideQueryButton: hideHideQueryButton = false, queryLibraryRef } = this.props; const { datasource, showingHelp } = this.state; const isHidden = !!query.hide; @@ -398,16 +397,14 @@ export class QueryEditorRow extends PureComponent {!isEditingQueryLibrary && ( { - onQueryReplacedFromLibrary?.(); - onReplace?.(query); - }} + onSelectQuery={this.onSelectQueryFromLibrary} app={this.props.app} /> )} @@ -606,9 +603,16 @@ function MaybeQueryLibrarySaveButton(props: { app?: CoreApp; queryLibraryRef?: string; onUpdateSuccess?: () => void; + onSelectQuery: (query: DataQuery) => void; }) { const { renderSaveQueryButton } = useQueryLibraryContext(); - return renderSaveQueryButton(props.query, props.app, props.queryLibraryRef, props.onUpdateSuccess); + return renderSaveQueryButton( + props.query, + props.app, + props.queryLibraryRef, + props.onUpdateSuccess, + props.onSelectQuery + ); } interface ReplaceQueryFromLibraryProps { @@ -625,7 +629,7 @@ function ReplaceQueryFromLibrary({ const { openDrawer, queryLibraryEnabled } = useQueryLibraryContext(); const onReplaceQueryFromLibrary = () => { - openDrawer(datasourceFilters, onSelectQuery, { isReplacingQuery: true, context: app }); + openDrawer({ datasourceFilters, onSelectQuery, options: { isReplacingQuery: true, context: app } }); }; return queryLibraryEnabled ? (