diff --git a/packages/grafana-data/src/types/explore.ts b/packages/grafana-data/src/types/explore.ts index f95d90ac409..362d6a19f37 100644 --- a/packages/grafana-data/src/types/explore.ts +++ b/packages/grafana-data/src/types/explore.ts @@ -24,6 +24,7 @@ export interface ExploreUrlState { queries: T[]; range: URLRange; panelsState?: ExplorePanelsState; + queryRef?: string; } export interface ExplorePanelsState extends Partial> { diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index cb22e191a77..92f07dce7b9 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -49,6 +49,7 @@ export interface GetExploreUrlArguments { timeRange: TimeRange; scopedVars: ScopedVars | undefined; adhocFilters?: AdHocVariableFilter[]; + queryRef?: string; } export function generateExploreId() { @@ -65,7 +66,7 @@ export function generateExploreId() { * Returns an Explore-URL that contains a panel's queries and the dashboard time range. */ export async function getExploreUrl(args: GetExploreUrlArguments): Promise { - const { queries, dsRef, timeRange, scopedVars, adhocFilters } = args; + const { queries, dsRef, timeRange, scopedVars, adhocFilters, queryRef } = args; const interpolatedQueries = ( await Promise.allSettled( queries @@ -93,7 +94,12 @@ export async function getExploreUrl(args: GetExploreUrlArguments): Promise q.value); const exploreState = JSON.stringify({ - [generateExploreId()]: { range: toURLRange(timeRange.raw), queries: interpolatedQueries, datasource: dsRef?.uid }, + [generateExploreId()]: { + range: toURLRange(timeRange.raw), + queries: interpolatedQueries, + datasource: dsRef?.uid, + queryRef: queryRef, + }, }); return locationUtil.assureBaseUrl(urlUtil.renderUrl('/explore', { panes: exploreState, schemaVersion: 1 })); } diff --git a/public/app/features/explore/QueryRows.tsx b/public/app/features/explore/QueryRows.tsx index 084c180544f..2355b8bbc07 100644 --- a/public/app/features/explore/QueryRows.tsx +++ b/public/app/features/explore/QueryRows.tsx @@ -29,21 +29,21 @@ const makeSelectors = (exploreId: string) => { exploreItemSelector, (s) => getDatasourceSrv().getInstanceSettings(s!.datasourceInstance?.uid)! ), + getQueryRef: createSelector(exploreItemSelector, (s) => s!.queryRef), }; }; export const QueryRows = ({ exploreId }: Props) => { const dispatch = useDispatch(); - const { getQueries, getDatasourceInstanceSettings, getQueryResponse, getHistory, getEventBridge } = useMemo( - () => makeSelectors(exploreId), - [exploreId] - ); + const { getQueries, getDatasourceInstanceSettings, getQueryResponse, getHistory, getEventBridge, getQueryRef } = + useMemo(() => makeSelectors(exploreId), [exploreId]); const queries = useSelector(getQueries); const dsSettings = useSelector(getDatasourceInstanceSettings); const queryResponse = useSelector(getQueryResponse); const history = useSelector(getHistory); const eventBridge = useSelector(getEventBridge); + const queryRef = useSelector(getQueryRef); const onRunQueries = useCallback(() => { dispatch(runQueries({ exploreId })); @@ -102,6 +102,7 @@ export const QueryRows = ({ exploreId }: Props) => { app={CoreApp.Explore} history={history} eventBus={eventBridge} + queryRef={queryRef} queryRowWrapper={(children, refId) => ( { const datasource = !isEqual(currentUrlState?.datasource, oldUrlState?.datasource); const queries = !isEqual(currentUrlState?.queries, oldUrlState?.queries); const range = !isEqual(currentUrlState?.range || DEFAULT_RANGE, oldUrlState?.range || DEFAULT_RANGE); const panelsState = !isEqual(currentUrlState?.panelsState, oldUrlState?.panelsState); + const queryRef = !isEqual(currentUrlState?.queryRef, oldUrlState?.queryRef); return { datasource, queries, range, panelsState, + queryRef, }; }; diff --git a/public/app/features/explore/hooks/useStateSync/migrators/v1.ts b/public/app/features/explore/hooks/useStateSync/migrators/v1.ts index 58fe595e49a..0c3d36911ed 100644 --- a/public/app/features/explore/hooks/useStateSync/migrators/v1.ts +++ b/public/app/features/explore/hooks/useStateSync/migrators/v1.ts @@ -118,5 +118,7 @@ function applyDefaults(input: unknown): ExploreUrlState { hasKey('to', input.range) && typeof input.range.from === 'string' && typeof input.range.to === 'string' && { range: { from: input.range.from, to: input.range.to } }), + //queryRef + ...(hasKey('queryRef', input) && typeof input.queryRef === 'string' && { queryRef: input.queryRef }), }; } diff --git a/public/app/features/explore/hooks/useStateSync/synchronizer/fromURL.ts b/public/app/features/explore/hooks/useStateSync/synchronizer/fromURL.ts index c196320a859..fa001b94f23 100644 --- a/public/app/features/explore/hooks/useStateSync/synchronizer/fromURL.ts +++ b/public/app/features/explore/hooks/useStateSync/synchronizer/fromURL.ts @@ -2,7 +2,11 @@ import { isEqual } from 'lodash'; import { EventBusSrv } from '@grafana/data'; import { changeDatasource } from 'app/features/explore/state/datasource'; -import { changePanelsStateAction, initializeExplore } from 'app/features/explore/state/explorePane'; +import { + changePanelsStateAction, + initializeExplore, + updateQueryRefAction, +} from 'app/features/explore/state/explorePane'; import { splitClose, syncTimesAction } from 'app/features/explore/state/main'; import { cancelQueries, runQueries, setQueriesAction } from 'app/features/explore/state/query'; import { updateTime } from 'app/features/explore/state/time'; @@ -30,7 +34,7 @@ export function syncFromURL( } Object.entries(urlState.panes).forEach(async ([exploreId, urlPane], i) => { - const { datasource, queries, range, panelsState } = urlPane; + const { datasource, queries, range, panelsState, queryRef } = urlPane; const paneState = panesState[exploreId]; @@ -61,6 +65,10 @@ export function syncFromURL( if (update.panelsState && panelsState) { dispatch(changePanelsStateAction({ exploreId, panelsState })); } + + if (update.queryRef !== undefined) { + dispatch(updateQueryRefAction({ exploreId, queryRef })); + } }); } else { // This happens when browser history is used to navigate. @@ -76,6 +84,7 @@ export function syncFromURL( panelsState, position: i, eventBridge: new EventBusSrv(), + queryRef, }) ); } diff --git a/public/app/features/explore/hooks/useStateSync/synchronizer/init.ts b/public/app/features/explore/hooks/useStateSync/synchronizer/init.ts index f4dec0dcecb..1489e79e53e 100644 --- a/public/app/features/explore/hooks/useStateSync/synchronizer/init.ts +++ b/public/app/features/explore/hooks/useStateSync/synchronizer/init.ts @@ -33,7 +33,7 @@ export function initializeFromURL( dispatch(clearPanes()); Promise.all( - Object.entries(urlState.panes).map(([exploreId, { datasource, queries, range, panelsState }]) => { + Object.entries(urlState.panes).map(([exploreId, { datasource, queries, range, panelsState, queryRef }]) => { return getPaneDatasource(datasource, queries, orgId).then((paneDatasource) => { return Promise.resolve( // Given the Grafana datasource will always be present, this should always be defined. @@ -64,13 +64,20 @@ export function initializeFromURL( ]; } - return { exploreId, range, panelsState, queries: validQueries, datasource: paneDatasource }; + return { + exploreId, + range, + panelsState, + queries: validQueries, + datasource: paneDatasource, + ...(queryRef && { queryRef }), + }; }); }); }) ).then(async (panes) => { const initializedPanes = await Promise.all( - panes.map(({ exploreId, range, panelsState, queries, datasource }) => { + panes.map(({ exploreId, range, panelsState, queries, datasource, queryRef }) => { return dispatch( initializeExplore({ exploreId, @@ -79,6 +86,7 @@ export function initializeFromURL( range: fromURLRange(range), panelsState, eventBridge: new EventBusSrv(), + queryRef, }) ).unwrap(); }) diff --git a/public/app/features/explore/state/explorePane.ts b/public/app/features/explore/state/explorePane.ts index 9835b9f3e09..fd2d35a3469 100644 --- a/public/app/features/explore/state/explorePane.ts +++ b/public/app/features/explore/state/explorePane.ts @@ -83,10 +83,18 @@ interface ChangeCorrelationHelperData { exploreId: string; correlationEditorHelperData?: ExploreCorrelationHelperData; } + export const changeCorrelationHelperData = createAction( 'explore/changeCorrelationHelperData' ); +export interface UpdateQueryRefPayload { + exploreId: string; + queryRef?: string; +} + +export const updateQueryRefAction = createAction('explore/updateQueryRef'); + /** * Initialize Explore state with state from the URL and the React component. * Call this only on components for with the Explore state has not been initialized. @@ -98,6 +106,7 @@ interface InitializeExplorePayload { history: HistoryItem[]; datasourceInstance?: DataSourceApi; eventBridge: EventBusExtended; + queryRef?: string; } const initializeExploreAction = createAction('explore/initializeExploreAction'); @@ -129,6 +138,7 @@ export interface InitializeExploreOptions { correlationHelperData?: ExploreCorrelationHelperData; position?: number; eventBridge: EventBusExtended; + queryRef?: string; } /** * Initialize Explore state with state from the URL and the React component. @@ -149,6 +159,7 @@ export const initializeExplore = createAsyncThunk( panelsState, correlationHelperData, eventBridge, + queryRef, }: InitializeExploreOptions, { dispatch, getState, fulfillWithValue } ) => { @@ -170,6 +181,7 @@ export const initializeExplore = createAsyncThunk( datasourceInstance: instance, history, eventBridge, + queryRef, }) ); if (panelsState !== undefined) { @@ -235,8 +247,15 @@ export const paneReducer = (state: ExploreItemState = makeExplorePaneState(), ac }; } + if (updateQueryRefAction.match(action)) { + return { + ...state, + queryRef: action.payload.queryRef, + }; + } + if (initializeExploreAction.match(action)) { - const { queries, range, datasourceInstance, history, eventBridge } = action.payload; + const { queries, range, datasourceInstance, history, eventBridge, queryRef } = action.payload; return { ...state, @@ -250,6 +269,7 @@ export const paneReducer = (state: ExploreItemState = makeExplorePaneState(), ac queryResponse: createEmptyQueryResponse(), cache: [], correlations: [], + queryRef, }; } diff --git a/public/app/features/query/components/QueryEditorRow.tsx b/public/app/features/query/components/QueryEditorRow.tsx index 5e1a6c845f6..3006dbb4ae1 100644 --- a/public/app/features/query/components/QueryEditorRow.tsx +++ b/public/app/features/query/components/QueryEditorRow.tsx @@ -67,6 +67,7 @@ export interface Props { onQueryReplacedFromLibrary?: () => void; collapsable?: boolean; hideRefId?: boolean; + queryRef?: string; } interface State { @@ -324,6 +325,26 @@ export class QueryEditorRow extends PureComponent { + const { queryRef } = this.props; + + if (!queryRef) { + return null; + } + + return ( + + ); + }; + renderExtraActions = () => { const { query, queries, data, onAddQuery, dataSource, app } = this.props; @@ -351,6 +372,7 @@ export class QueryEditorRow extends PureComponent); + extraActions.push(this.renderQueryLibraryBadge()); return extraActions; }; diff --git a/public/app/features/query/components/QueryEditorRows.tsx b/public/app/features/query/components/QueryEditorRows.tsx index a9b9d9c0186..2d969abbb7a 100644 --- a/public/app/features/query/components/QueryEditorRows.tsx +++ b/public/app/features/query/components/QueryEditorRows.tsx @@ -40,6 +40,7 @@ export interface Props { onUpdateDatasources?: (datasource: DataSourceRef) => void; onQueryReplacedFromLibrary?: () => void; queryRowWrapper?: (children: ReactNode, refId: string) => ReactNode; + queryRef?: string; } export class QueryEditorRows extends PureComponent { @@ -175,6 +176,7 @@ export class QueryEditorRows extends PureComponent { onQueryToggled, onQueryReplacedFromLibrary, queryRowWrapper, + queryRef, } = this.props; return ( @@ -212,6 +214,7 @@ export class QueryEditorRows extends PureComponent { range={getTimeSrv().timeRange()} history={history} eventBus={eventBus} + queryRef={queryRef} /> ); diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 11ac0ad84d0..c798fd96b1a 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -140,6 +140,11 @@ export interface ExploreItemState { * Used to distinguish URL state injection versus split view state injection. */ initialized: boolean; + /** + * Query library reference identifier when editing a query from the query library + * + */ + queryRef?: string; /** * Log query result to be displayed in the logs result viewer. */