mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: Moves PromContext from query level to DataQueryRequest level (#21260)
Closes #19598 Fixes bug introduced recently where the new PromQueryEditor did not preserve the PromContext.Explore set on the query model by PromQueryField which caused the table to be empty for Prometheus in explore.
This commit is contained in:
parent
545b72da33
commit
45b7de1910
@ -3,6 +3,11 @@ import { KeyValue } from './data';
|
|||||||
import { NavModel } from './navModel';
|
import { NavModel } from './navModel';
|
||||||
import { PluginMeta, GrafanaPlugin, PluginIncludeType } from './plugin';
|
import { PluginMeta, GrafanaPlugin, PluginIncludeType } from './plugin';
|
||||||
|
|
||||||
|
export enum CoreApp {
|
||||||
|
Dashboard = 'dashboard',
|
||||||
|
Explore = 'explore',
|
||||||
|
}
|
||||||
|
|
||||||
export interface AppRootProps<T = KeyValue> {
|
export interface AppRootProps<T = KeyValue> {
|
||||||
meta: AppPluginMeta<T>;
|
meta: AppPluginMeta<T>;
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import { AnnotationEvent, KeyValue, LoadingState, TableData, TimeSeries } from '
|
|||||||
import { DataFrame, DataFrameDTO } from './dataFrame';
|
import { DataFrame, DataFrameDTO } from './dataFrame';
|
||||||
import { RawTimeRange, TimeRange, AbsoluteTimeRange } from './time';
|
import { RawTimeRange, TimeRange, AbsoluteTimeRange } from './time';
|
||||||
import { ScopedVars } from './ScopedVars';
|
import { ScopedVars } from './ScopedVars';
|
||||||
|
import { CoreApp } from './app';
|
||||||
|
|
||||||
export interface DataSourcePluginOptionsEditorProps<JSONData = DataSourceJsonData, SecureJSONData = {}> {
|
export interface DataSourcePluginOptionsEditorProps<JSONData = DataSourceJsonData, SecureJSONData = {}> {
|
||||||
options: DataSourceSettings<JSONData, SecureJSONData>;
|
options: DataSourceSettings<JSONData, SecureJSONData>;
|
||||||
@ -449,6 +450,7 @@ export interface DataQueryRequest<TQuery extends DataQuery = DataQuery> {
|
|||||||
scopedVars: ScopedVars;
|
scopedVars: ScopedVars;
|
||||||
targets: TQuery[];
|
targets: TQuery[];
|
||||||
timezone: string;
|
timezone: string;
|
||||||
|
app: CoreApp | string;
|
||||||
|
|
||||||
cacheTimeout?: string;
|
cacheTimeout?: string;
|
||||||
exploreMode?: 'Logs' | 'Metrics';
|
exploreMode?: 'Logs' | 'Metrics';
|
||||||
|
@ -4,6 +4,7 @@ import { Unsubscribable } from 'rxjs';
|
|||||||
// Services & Utils
|
// Services & Utils
|
||||||
import {
|
import {
|
||||||
DataQuery,
|
DataQuery,
|
||||||
|
CoreApp,
|
||||||
DataQueryError,
|
DataQueryError,
|
||||||
DataQueryRequest,
|
DataQueryRequest,
|
||||||
DataSourceApi,
|
DataSourceApi,
|
||||||
@ -130,6 +131,7 @@ export function buildQueryTransaction(
|
|||||||
const panelId = `${key}`;
|
const panelId = `${key}`;
|
||||||
|
|
||||||
const request: DataQueryRequest = {
|
const request: DataQueryRequest = {
|
||||||
|
app: CoreApp.Explore,
|
||||||
dashboardId: 0,
|
dashboardId: 0,
|
||||||
// TODO probably should be taken from preferences but does not seem to be used anyway.
|
// TODO probably should be taken from preferences but does not seem to be used anyway.
|
||||||
timezone: DefaultTimeZone,
|
timezone: DefaultTimeZone,
|
||||||
|
@ -15,6 +15,7 @@ import { runSharedRequest, isSharedDashboardQuery } from '../../../plugins/datas
|
|||||||
import {
|
import {
|
||||||
PanelData,
|
PanelData,
|
||||||
DataQuery,
|
DataQuery,
|
||||||
|
CoreApp,
|
||||||
DataQueryRequest,
|
DataQueryRequest,
|
||||||
DataSourceApi,
|
DataSourceApi,
|
||||||
DataSourceJsonData,
|
DataSourceJsonData,
|
||||||
@ -106,6 +107,7 @@ export class PanelQueryRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const request: DataQueryRequest = {
|
const request: DataQueryRequest = {
|
||||||
|
app: CoreApp.Dashboard,
|
||||||
requestId: getNextRequestId(),
|
requestId: getNextRequestId(),
|
||||||
timezone,
|
timezone,
|
||||||
panelId,
|
panelId,
|
||||||
|
@ -6,6 +6,8 @@ import { Table, Collapse } from '@grafana/ui';
|
|||||||
import { ExploreId, ExploreItemState } from 'app/types/explore';
|
import { ExploreId, ExploreItemState } from 'app/types/explore';
|
||||||
import { StoreState } from 'app/types';
|
import { StoreState } from 'app/types';
|
||||||
import { toggleTable } from './state/actions';
|
import { toggleTable } from './state/actions';
|
||||||
|
import { config } from 'app/core/config';
|
||||||
|
import { PANEL_BORDER } from 'app/core/constants';
|
||||||
|
|
||||||
interface TableContainerProps {
|
interface TableContainerProps {
|
||||||
exploreId: ExploreId;
|
exploreId: ExploreId;
|
||||||
@ -37,8 +39,7 @@ export class TableContainer extends PureComponent<TableContainerProps> {
|
|||||||
const { loading, onClickCell, showingTable, tableResult, width } = this.props;
|
const { loading, onClickCell, showingTable, tableResult, width } = this.props;
|
||||||
|
|
||||||
const height = this.getTableHeight();
|
const height = this.getTableHeight();
|
||||||
const paddingWidth = 16;
|
const tableWidth = width - config.theme.panelPadding * 2 - PANEL_BORDER;
|
||||||
const tableWidth = width - paddingWidth;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapse label="Table" loading={loading} collapsible isOpen={showingTable} onToggle={this.onClickTableButton}>
|
<Collapse label="Table" loading={loading} collapsible isOpen={showingTable} onToggle={this.onClickTableButton}>
|
||||||
|
@ -262,7 +262,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
|
|||||||
return Promise.all(
|
return Promise.all(
|
||||||
queries.map(async query => {
|
queries.map(async query => {
|
||||||
const expr = await this.importPrometheusQuery(query.expr);
|
const expr = await this.importPrometheusQuery(query.expr);
|
||||||
const { context, ...rest } = query as PromQuery;
|
const { ...rest } = query as PromQuery;
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
expr,
|
expr,
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
import Prism from 'prismjs';
|
import Prism from 'prismjs';
|
||||||
|
|
||||||
// dom also includes Element polyfills
|
// dom also includes Element polyfills
|
||||||
import { PromQuery, PromContext, PromOptions, PromMetricsMetadata } from '../types';
|
import { PromQuery, PromOptions, PromMetricsMetadata } from '../types';
|
||||||
import { CancelablePromise, makePromiseCancelable } from 'app/core/utils/CancelablePromise';
|
import { CancelablePromise, makePromiseCancelable } from 'app/core/utils/CancelablePromise';
|
||||||
import { ExploreQueryFieldProps, QueryHint, isDataFrame, toLegacyResponseData, HistoryItem } from '@grafana/data';
|
import { ExploreQueryFieldProps, QueryHint, isDataFrame, toLegacyResponseData, HistoryItem } from '@grafana/data';
|
||||||
import { DOMUtil, SuggestionsState } from '@grafana/ui';
|
import { DOMUtil, SuggestionsState } from '@grafana/ui';
|
||||||
@ -220,7 +220,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
// Send text change to parent
|
// Send text change to parent
|
||||||
const { query, onChange, onRunQuery } = this.props;
|
const { query, onChange, onRunQuery } = this.props;
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
const nextQuery: PromQuery = { ...query, expr: value, context: PromContext.Explore };
|
const nextQuery: PromQuery = { ...query, expr: value };
|
||||||
onChange(nextQuery);
|
onChange(nextQuery);
|
||||||
|
|
||||||
if (override && onRunQuery) {
|
if (override && onRunQuery) {
|
||||||
|
@ -11,9 +11,10 @@ import {
|
|||||||
DataQueryResponseData,
|
DataQueryResponseData,
|
||||||
DataQueryRequest,
|
DataQueryRequest,
|
||||||
dateTime,
|
dateTime,
|
||||||
|
CoreApp,
|
||||||
LoadingState,
|
LoadingState,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { PromOptions, PromQuery, PromContext } from './types';
|
import { PromOptions, PromQuery } from './types';
|
||||||
import templateSrv from 'app/features/templating/template_srv';
|
import templateSrv from 'app/features/templating/template_srv';
|
||||||
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
import { CustomVariable } from 'app/features/templating/custom_variable';
|
import { CustomVariable } from 'app/features/templating/custom_variable';
|
||||||
@ -70,7 +71,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
it('returns empty array when no queries', done => {
|
it('returns empty array when no queries', done => {
|
||||||
expect.assertions(2);
|
expect.assertions(2);
|
||||||
|
|
||||||
ds.query(makeQuery([])).subscribe({
|
ds.query(createDataRequest([])).subscribe({
|
||||||
next(next) {
|
next(next) {
|
||||||
expect(next.data).toEqual([]);
|
expect(next.data).toEqual([]);
|
||||||
expect(next.state).toBe(LoadingState.Done);
|
expect(next.state).toBe(LoadingState.Done);
|
||||||
@ -84,7 +85,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
it('performs time series queries', done => {
|
it('performs time series queries', done => {
|
||||||
expect.assertions(2);
|
expect.assertions(2);
|
||||||
|
|
||||||
ds.query(makeQuery([{}])).subscribe({
|
ds.query(createDataRequest([{}])).subscribe({
|
||||||
next(next) {
|
next(next) {
|
||||||
expect(next.data.length).not.toBe(0);
|
expect(next.data.length).not.toBe(0);
|
||||||
expect(next.state).toBe(LoadingState.Done);
|
expect(next.state).toBe(LoadingState.Done);
|
||||||
@ -99,7 +100,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
expect.assertions(4);
|
expect.assertions(4);
|
||||||
|
|
||||||
const responseStatus = [LoadingState.Loading, LoadingState.Done];
|
const responseStatus = [LoadingState.Loading, LoadingState.Done];
|
||||||
ds.query(makeQuery([{ context: PromContext.Explore }, { context: PromContext.Explore }])).subscribe({
|
ds.query(createDataRequest([{}, {}], { app: CoreApp.Explore })).subscribe({
|
||||||
next(next) {
|
next(next) {
|
||||||
expect(next.data.length).not.toBe(0);
|
expect(next.data.length).not.toBe(0);
|
||||||
expect(next.state).toBe(responseStatus.shift());
|
expect(next.state).toBe(responseStatus.shift());
|
||||||
@ -112,7 +113,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
|
|
||||||
it('with 2 queries and used from Panel, waits for all to finish until sending Done status', done => {
|
it('with 2 queries and used from Panel, waits for all to finish until sending Done status', done => {
|
||||||
expect.assertions(2);
|
expect.assertions(2);
|
||||||
ds.query(makeQuery([{ context: PromContext.Panel }, { context: PromContext.Panel }])).subscribe({
|
ds.query(createDataRequest([{}, {}], { app: CoreApp.Dashboard })).subscribe({
|
||||||
next(next) {
|
next(next) {
|
||||||
expect(next.data.length).not.toBe(0);
|
expect(next.data.length).not.toBe(0);
|
||||||
expect(next.state).toBe(LoadingState.Done);
|
expect(next.state).toBe(LoadingState.Done);
|
||||||
@ -1552,7 +1553,7 @@ describe('PrometheusDatasource for POST', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const getPrepareTargetsContext = (target: PromQuery) => {
|
const getPrepareTargetsContext = (target: PromQuery, app?: CoreApp) => {
|
||||||
const instanceSettings = ({
|
const instanceSettings = ({
|
||||||
url: 'proxied',
|
url: 'proxied',
|
||||||
directUrl: 'direct',
|
directUrl: 'direct',
|
||||||
@ -1563,7 +1564,7 @@ const getPrepareTargetsContext = (target: PromQuery) => {
|
|||||||
const start = 0;
|
const start = 0;
|
||||||
const end = 1;
|
const end = 1;
|
||||||
const panelId = '2';
|
const panelId = '2';
|
||||||
const options = ({ targets: [target], interval: '1s', panelId } as any) as DataQueryRequest<PromQuery>;
|
const options = ({ targets: [target], interval: '1s', panelId, app } as any) as DataQueryRequest<PromQuery>;
|
||||||
|
|
||||||
const ds = new PrometheusDatasource(instanceSettings);
|
const ds = new PrometheusDatasource(instanceSettings);
|
||||||
const { queries, activeTargets } = ds.prepareTargets(options, start, end);
|
const { queries, activeTargets } = ds.prepareTargets(options, start, end);
|
||||||
@ -1583,7 +1584,6 @@ describe('prepareTargets', () => {
|
|||||||
const target: PromQuery = {
|
const target: PromQuery = {
|
||||||
refId: 'A',
|
refId: 'A',
|
||||||
expr: 'up',
|
expr: 'up',
|
||||||
context: PromContext.Panel,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext(target);
|
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext(target);
|
||||||
@ -1614,12 +1614,11 @@ describe('prepareTargets', () => {
|
|||||||
const target: PromQuery = {
|
const target: PromQuery = {
|
||||||
refId: 'A',
|
refId: 'A',
|
||||||
expr: 'up',
|
expr: 'up',
|
||||||
context: PromContext.Explore,
|
|
||||||
showingGraph: true,
|
showingGraph: true,
|
||||||
showingTable: true,
|
showingTable: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext(target);
|
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext(target, CoreApp.Explore);
|
||||||
|
|
||||||
expect(queries.length).toBe(2);
|
expect(queries.length).toBe(2);
|
||||||
expect(activeTargets.length).toBe(2);
|
expect(activeTargets.length).toBe(2);
|
||||||
@ -1672,12 +1671,11 @@ describe('prepareTargets', () => {
|
|||||||
const target: PromQuery = {
|
const target: PromQuery = {
|
||||||
refId: 'A',
|
refId: 'A',
|
||||||
expr: 'up',
|
expr: 'up',
|
||||||
context: PromContext.Explore,
|
|
||||||
showingGraph: false,
|
showingGraph: false,
|
||||||
showingTable: false,
|
showingTable: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { queries, activeTargets } = getPrepareTargetsContext(target);
|
const { queries, activeTargets } = getPrepareTargetsContext(target, CoreApp.Explore);
|
||||||
|
|
||||||
expect(queries.length).toBe(0);
|
expect(queries.length).toBe(0);
|
||||||
expect(activeTargets.length).toBe(0);
|
expect(activeTargets.length).toBe(0);
|
||||||
@ -1689,12 +1687,11 @@ describe('prepareTargets', () => {
|
|||||||
const target: PromQuery = {
|
const target: PromQuery = {
|
||||||
refId: 'A',
|
refId: 'A',
|
||||||
expr: 'up',
|
expr: 'up',
|
||||||
context: PromContext.Explore,
|
|
||||||
showingGraph: false,
|
showingGraph: false,
|
||||||
showingTable: true,
|
showingTable: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext(target);
|
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext(target, CoreApp.Explore);
|
||||||
|
|
||||||
expect(queries.length).toBe(1);
|
expect(queries.length).toBe(1);
|
||||||
expect(activeTargets.length).toBe(1);
|
expect(activeTargets.length).toBe(1);
|
||||||
@ -1727,12 +1724,11 @@ describe('prepareTargets', () => {
|
|||||||
const target: PromQuery = {
|
const target: PromQuery = {
|
||||||
refId: 'A',
|
refId: 'A',
|
||||||
expr: 'up',
|
expr: 'up',
|
||||||
context: PromContext.Explore,
|
|
||||||
showingGraph: true,
|
showingGraph: true,
|
||||||
showingTable: false,
|
showingTable: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext(target);
|
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext(target, CoreApp.Explore);
|
||||||
|
|
||||||
expect(queries.length).toBe(1);
|
expect(queries.length).toBe(1);
|
||||||
expect(activeTargets.length).toBe(1);
|
expect(activeTargets.length).toBe(1);
|
||||||
@ -1761,8 +1757,9 @@ describe('prepareTargets', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function makeQuery(targets: any[]): any {
|
function createDataRequest(targets: any[], overrides?: Partial<DataQueryRequest>): DataQueryRequest<PromQuery> {
|
||||||
return {
|
const defaults = {
|
||||||
|
app: CoreApp.Dashboard,
|
||||||
targets: targets.map(t => {
|
targets: targets.map(t => {
|
||||||
return {
|
return {
|
||||||
instant: false,
|
instant: false,
|
||||||
@ -1779,4 +1776,6 @@ function makeQuery(targets: any[]): any {
|
|||||||
},
|
},
|
||||||
interval: '15s',
|
interval: '15s',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return Object.assign(defaults, overrides || {}) as DataQueryRequest<PromQuery>;
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
LoadingState,
|
LoadingState,
|
||||||
TimeRange,
|
TimeRange,
|
||||||
TimeSeries,
|
TimeSeries,
|
||||||
|
CoreApp,
|
||||||
DataQueryError,
|
DataQueryError,
|
||||||
DataQueryRequest,
|
DataQueryRequest,
|
||||||
DataQueryResponse,
|
DataQueryResponse,
|
||||||
@ -29,7 +30,7 @@ import addLabelToQuery from './add_label_to_query';
|
|||||||
import { getQueryHints } from './query_hints';
|
import { getQueryHints } from './query_hints';
|
||||||
import { expandRecordingRules } from './language_utils';
|
import { expandRecordingRules } from './language_utils';
|
||||||
// Types
|
// Types
|
||||||
import { PromContext, PromOptions, PromQuery, PromQueryRequest } from './types';
|
import { PromOptions, PromQuery, PromQueryRequest } from './types';
|
||||||
import { safeStringifyValue } from 'app/core/utils/explore';
|
import { safeStringifyValue } from 'app/core/utils/explore';
|
||||||
import templateSrv from 'app/features/templating/template_srv';
|
import templateSrv from 'app/features/templating/template_srv';
|
||||||
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
@ -202,7 +203,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
|||||||
|
|
||||||
target.requestId = options.panelId + target.refId;
|
target.requestId = options.panelId + target.refId;
|
||||||
|
|
||||||
if (target.context !== PromContext.Explore) {
|
if (options.app !== CoreApp.Explore) {
|
||||||
activeTargets.push(target);
|
activeTargets.push(target);
|
||||||
queries.push(this.createQuery(target, options, start, end));
|
queries.push(this.createQuery(target, options, start, end));
|
||||||
continue;
|
continue;
|
||||||
@ -237,11 +238,6 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
calledFromExplore = (options: DataQueryRequest<PromQuery>): boolean => {
|
|
||||||
const exploreTargets = options.targets.filter(target => target.context === PromContext.Explore).length;
|
|
||||||
return exploreTargets === options.targets.length;
|
|
||||||
};
|
|
||||||
|
|
||||||
query(options: DataQueryRequest<PromQuery>): Observable<DataQueryResponse> {
|
query(options: DataQueryRequest<PromQuery>): Observable<DataQueryResponse> {
|
||||||
const start = this.getPrometheusTime(options.range.from, false);
|
const start = this.getPrometheusTime(options.range.from, false);
|
||||||
const end = this.getPrometheusTime(options.range.to, true);
|
const end = this.getPrometheusTime(options.range.to, true);
|
||||||
@ -255,7 +251,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.calledFromExplore(options)) {
|
if (options.app === CoreApp.Explore) {
|
||||||
return this.exploreQuery(queries, activeTargets, end);
|
return this.exploreQuery(queries, activeTargets, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
import { DataQuery, DataSourceJsonData } from '@grafana/data';
|
import { DataQuery, DataSourceJsonData } from '@grafana/data';
|
||||||
|
|
||||||
export enum PromContext {
|
|
||||||
Explore = 'explore',
|
|
||||||
Panel = 'panel',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PromQuery extends DataQuery {
|
export interface PromQuery extends DataQuery {
|
||||||
expr: string;
|
expr: string;
|
||||||
context?: PromContext;
|
|
||||||
format?: string;
|
format?: string;
|
||||||
instant?: boolean;
|
instant?: boolean;
|
||||||
hinting?: boolean;
|
hinting?: boolean;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { DataQueryRequest, DataQuery } from '@grafana/data';
|
import { DataQueryRequest, DataQuery, CoreApp } from '@grafana/data';
|
||||||
import { dateTime } from '@grafana/data';
|
import { dateTime } from '@grafana/data';
|
||||||
|
|
||||||
export function getQueryOptions<TQuery extends DataQuery>(
|
export function getQueryOptions<TQuery extends DataQuery>(
|
||||||
@ -9,6 +9,7 @@ export function getQueryOptions<TQuery extends DataQuery>(
|
|||||||
|
|
||||||
const defaults: DataQueryRequest<TQuery> = {
|
const defaults: DataQueryRequest<TQuery> = {
|
||||||
requestId: 'TEST',
|
requestId: 'TEST',
|
||||||
|
app: CoreApp.Dashboard,
|
||||||
range: range,
|
range: range,
|
||||||
targets: [],
|
targets: [],
|
||||||
scopedVars: {},
|
scopedVars: {},
|
||||||
|
Loading…
Reference in New Issue
Block a user