add suport for queryLibraryRef in explore
This commit is contained in:
@@ -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, {}>> {
|
||||
|
||||
@@ -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 }));
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user