add suport for queryLibraryRef in explore

This commit is contained in:
alexandra vargas
2025-07-15 16:57:52 +02:00
parent db7087fdfc
commit 579d1cb6e4
12 changed files with 93 additions and 12 deletions
@@ -24,6 +24,7 @@ export interface ExploreUrlState<T extends DataQuery = AnyQuery> {
queries: T[];
range: URLRange;
panelsState?: ExplorePanelsState;
queryRef?: string;
}
export interface ExplorePanelsState extends Partial<Record<PreferredVisualisationType, {}>> {
+8 -2
View File
@@ -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<string | undefined> {
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<strin
.map((q) => 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 }));
}
+5 -4
View File
@@ -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) => (
<ContentOutlineItem
title={refId}
@@ -13,6 +13,7 @@ export function getUrlStateFromPaneState(pane: ExploreItemState): ExploreUrlStat
range: toURLRange(pane.range.raw),
// don't include panelsState in the url unless a piece of state is actually set
panelsState: pruneObject(pane.panelsState),
queryRef: pane.queryRef || undefined,
};
}
@@ -23,17 +23,20 @@ export const urlDiff = (
queries: boolean;
range: boolean;
panelsState: boolean;
queryRef: boolean;
} => {
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,
};
};
@@ -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 }),
};
}
@@ -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,
})
);
}
@@ -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();
})
@@ -83,10 +83,18 @@ interface ChangeCorrelationHelperData {
exploreId: string;
correlationEditorHelperData?: ExploreCorrelationHelperData;
}
export const changeCorrelationHelperData = createAction<ChangeCorrelationHelperData>(
'explore/changeCorrelationHelperData'
);
export interface UpdateQueryRefPayload {
exploreId: string;
queryRef?: string;
}
export const updateQueryRefAction = createAction<UpdateQueryRefPayload>('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<InitializeExplorePayload>('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,
};
}
@@ -67,6 +67,7 @@ export interface Props<TQuery extends DataQuery> {
onQueryReplacedFromLibrary?: () => void;
collapsable?: boolean;
hideRefId?: boolean;
queryRef?: string;
}
interface State<TQuery extends DataQuery> {
@@ -324,6 +325,26 @@ export class QueryEditorRow<TQuery extends DataQuery> extends PureComponent<Prop
);
};
renderQueryLibraryBadge = () => {
const { queryRef } = this.props;
if (!queryRef) {
return null;
}
return (
<Badge
key="query-library-badge"
color="blue"
icon="book"
text={t('query-operation.query-library.from-library', 'Editing From Query Library')}
tooltip={t('query-operation.query-library.editing-tooltip', 'Editing query from library ({{queryRef}})', {
queryRef,
})}
/>
);
};
renderExtraActions = () => {
const { query, queries, data, onAddQuery, dataSource, app } = this.props;
@@ -351,6 +372,7 @@ export class QueryEditorRow<TQuery extends DataQuery> extends PureComponent<Prop
extraActions.push(this.renderWarnings('info'));
extraActions.push(this.renderWarnings('warning'));
extraActions.push(<AdaptiveTelemetryQueryActions key="adaptive-telemetry-actions" query={query} />);
extraActions.push(this.renderQueryLibraryBadge());
return extraActions;
};
@@ -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<Props> {
@@ -175,6 +176,7 @@ export class QueryEditorRows extends PureComponent<Props> {
onQueryToggled,
onQueryReplacedFromLibrary,
queryRowWrapper,
queryRef,
} = this.props;
return (
@@ -212,6 +214,7 @@ export class QueryEditorRows extends PureComponent<Props> {
range={getTimeSrv().timeRange()}
history={history}
eventBus={eventBus}
queryRef={queryRef}
/>
);
+5
View File
@@ -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.
*/