mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Loki: Remove dependecy on core and move to @grafana/o11y-ds-frontend
(#81284)
* Loki: Remove dependecy on core and move to o11y-ds-frontend * Fix ctr -> ctrl * Remove test helpers
This commit is contained in:
parent
34e88077aa
commit
a83e01918a
@ -0,0 +1,49 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { store } from '../store';
|
||||||
|
|
||||||
|
export interface Props<T> {
|
||||||
|
storageKey: string;
|
||||||
|
defaultValue: T;
|
||||||
|
children: (value: T, onSaveToStore: (value: T) => void, onDeleteFromStore: () => void) => React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LocalStorageValueProvider = <T,>(props: Props<T>) => {
|
||||||
|
const { children, storageKey, defaultValue } = props;
|
||||||
|
|
||||||
|
const [state, setState] = useState({ value: store.getObject(props.storageKey, props.defaultValue) });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onStorageUpdate = (v: StorageEvent) => {
|
||||||
|
if (v.key === storageKey) {
|
||||||
|
setState({ value: store.getObject(props.storageKey, props.defaultValue) });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('storage', onStorageUpdate);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('storage', onStorageUpdate);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSaveToStore = (value: T) => {
|
||||||
|
try {
|
||||||
|
store.setObject(storageKey, value);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
setState({ value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteFromStore = () => {
|
||||||
|
try {
|
||||||
|
store.delete(storageKey);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
setState({ value: defaultValue });
|
||||||
|
};
|
||||||
|
|
||||||
|
return <>{children(state.value, onSaveToStore, onDeleteFromStore)}</>;
|
||||||
|
};
|
@ -1,7 +1,15 @@
|
|||||||
import { DataQueryResponse, LoadingState, PanelData, QueryResultMetaStat, getDefaultTimeRange } from '@grafana/data';
|
import {
|
||||||
import { getMockFrames } from 'app/plugins/datasource/loki/__mocks__/frames';
|
DataFrame,
|
||||||
|
DataFrameType,
|
||||||
|
DataQueryResponse,
|
||||||
|
FieldType,
|
||||||
|
LoadingState,
|
||||||
|
PanelData,
|
||||||
|
QueryResultMetaStat,
|
||||||
|
getDefaultTimeRange,
|
||||||
|
} from '@grafana/data';
|
||||||
|
|
||||||
import { cloneQueryResponse, combinePanelData, combineResponses } from './response';
|
import { cloneQueryResponse, combinePanelData, combineResponses } from './combineResponses';
|
||||||
|
|
||||||
describe('cloneQueryResponse', () => {
|
describe('cloneQueryResponse', () => {
|
||||||
const { logFrameA } = getMockFrames();
|
const { logFrameA } = getMockFrames();
|
||||||
@ -489,3 +497,204 @@ describe('combinePanelData', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export function getMockFrames() {
|
||||||
|
const logFrameA: DataFrame = {
|
||||||
|
refId: 'A',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'Time',
|
||||||
|
type: FieldType.time,
|
||||||
|
config: {},
|
||||||
|
values: [3, 4],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Line',
|
||||||
|
type: FieldType.string,
|
||||||
|
config: {},
|
||||||
|
values: ['line1', 'line2'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'labels',
|
||||||
|
type: FieldType.other,
|
||||||
|
config: {},
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
label: 'value',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
otherLabel: 'other value',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tsNs',
|
||||||
|
type: FieldType.string,
|
||||||
|
config: {},
|
||||||
|
values: ['3000000', '4000000'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: FieldType.string,
|
||||||
|
config: {},
|
||||||
|
values: ['id1', 'id2'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
custom: {
|
||||||
|
frameType: 'LabeledTimeValues',
|
||||||
|
},
|
||||||
|
stats: [
|
||||||
|
{ displayName: 'Summary: total bytes processed', unit: 'decbytes', value: 11 },
|
||||||
|
{ displayName: 'Ingester: total reached', value: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
length: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const logFrameB: DataFrame = {
|
||||||
|
refId: 'A',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'Time',
|
||||||
|
type: FieldType.time,
|
||||||
|
config: {},
|
||||||
|
values: [1, 2],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Line',
|
||||||
|
type: FieldType.string,
|
||||||
|
config: {},
|
||||||
|
values: ['line3', 'line4'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'labels',
|
||||||
|
type: FieldType.other,
|
||||||
|
config: {},
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
otherLabel: 'other value',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tsNs',
|
||||||
|
type: FieldType.string,
|
||||||
|
config: {},
|
||||||
|
values: ['1000000', '2000000'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: FieldType.string,
|
||||||
|
config: {},
|
||||||
|
values: ['id3', 'id4'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
custom: {
|
||||||
|
frameType: 'LabeledTimeValues',
|
||||||
|
},
|
||||||
|
stats: [
|
||||||
|
{ displayName: 'Summary: total bytes processed', unit: 'decbytes', value: 22 },
|
||||||
|
{ displayName: 'Ingester: total reached', value: 2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
length: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const metricFrameA: DataFrame = {
|
||||||
|
refId: 'A',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'Time',
|
||||||
|
type: FieldType.time,
|
||||||
|
config: {},
|
||||||
|
values: [3000000, 4000000],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Value',
|
||||||
|
type: FieldType.number,
|
||||||
|
config: {},
|
||||||
|
values: [5, 4],
|
||||||
|
labels: {
|
||||||
|
level: 'debug',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
type: DataFrameType.TimeSeriesMulti,
|
||||||
|
stats: [
|
||||||
|
{ displayName: 'Ingester: total reached', value: 1 },
|
||||||
|
{ displayName: 'Summary: total bytes processed', unit: 'decbytes', value: 11 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
length: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const metricFrameB: DataFrame = {
|
||||||
|
refId: 'A',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'Time',
|
||||||
|
type: FieldType.time,
|
||||||
|
config: {},
|
||||||
|
values: [1000000, 2000000],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Value',
|
||||||
|
type: FieldType.number,
|
||||||
|
config: {},
|
||||||
|
values: [6, 7],
|
||||||
|
labels: {
|
||||||
|
level: 'debug',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
type: DataFrameType.TimeSeriesMulti,
|
||||||
|
stats: [
|
||||||
|
{ displayName: 'Ingester: total reached', value: 2 },
|
||||||
|
{ displayName: 'Summary: total bytes processed', unit: 'decbytes', value: 22 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
length: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const metricFrameC: DataFrame = {
|
||||||
|
refId: 'A',
|
||||||
|
name: 'some-time-series',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'Time',
|
||||||
|
type: FieldType.time,
|
||||||
|
config: {},
|
||||||
|
values: [3000000, 4000000],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Value',
|
||||||
|
type: FieldType.number,
|
||||||
|
config: {},
|
||||||
|
values: [6, 7],
|
||||||
|
labels: {
|
||||||
|
level: 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
type: DataFrameType.TimeSeriesMulti,
|
||||||
|
stats: [
|
||||||
|
{ displayName: 'Ingester: total reached', value: 2 },
|
||||||
|
{ displayName: 'Summary: total bytes processed', unit: 'decbytes', value: 33 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
length: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
logFrameA,
|
||||||
|
logFrameB,
|
||||||
|
metricFrameA,
|
||||||
|
metricFrameB,
|
||||||
|
metricFrameC,
|
||||||
|
};
|
||||||
|
}
|
@ -73,7 +73,7 @@ function combineFrames(dest: DataFrame, source: DataFrame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TOTAL_BYTES_STAT = 'Summary: total bytes processed';
|
const TOTAL_BYTES_STAT = 'Summary: total bytes processed';
|
||||||
|
// This is specific for Loki
|
||||||
function getCombinedMetadataStats(
|
function getCombinedMetadataStats(
|
||||||
destStats: QueryResultMetaStat[],
|
destStats: QueryResultMetaStat[],
|
||||||
sourceStats: QueryResultMetaStat[]
|
sourceStats: QueryResultMetaStat[]
|
@ -13,3 +13,6 @@ export * from './TraceToLogs/TraceToLogsSettings';
|
|||||||
export * from './TraceToMetrics/TraceToMetricsSettings';
|
export * from './TraceToMetrics/TraceToMetricsSettings';
|
||||||
export * from './TraceToProfiles/TraceToProfilesSettings';
|
export * from './TraceToProfiles/TraceToProfilesSettings';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
|
export * from './store';
|
||||||
|
export * from './LocalStorageValueProvider/LocalStorageValueProvider';
|
||||||
|
export * from './combineResponses';
|
||||||
|
64
packages/grafana-o11y-ds-frontend/src/store.ts
Normal file
64
packages/grafana-o11y-ds-frontend/src/store.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
type StoreValue = string | number | boolean | null;
|
||||||
|
|
||||||
|
export class Store {
|
||||||
|
get(key: string) {
|
||||||
|
return window.localStorage[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key: string, value: StoreValue) {
|
||||||
|
window.localStorage[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBool(key: string, def: boolean): boolean {
|
||||||
|
if (def !== void 0 && !this.exists(key)) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
return window.localStorage[key] === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
getObject<T = unknown>(key: string): T | undefined;
|
||||||
|
getObject<T = unknown>(key: string, def: T): T;
|
||||||
|
getObject<T = unknown>(key: string, def?: T) {
|
||||||
|
let ret = def;
|
||||||
|
if (this.exists(key)) {
|
||||||
|
const json = window.localStorage[key];
|
||||||
|
try {
|
||||||
|
ret = JSON.parse(json);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error parsing store object: ${key}. Returning default: ${def}. [${error}]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns true when successfully stored, throws error if not successfully stored */
|
||||||
|
setObject(key: string, value: unknown) {
|
||||||
|
let json;
|
||||||
|
try {
|
||||||
|
json = JSON.stringify(value);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Could not stringify object: ${key}. [${error}]`);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.set(key, json);
|
||||||
|
} catch (error) {
|
||||||
|
// Likely hitting storage quota
|
||||||
|
const errorToThrow = new Error(`Could not save item in localStorage: ${key}. [${error}]`);
|
||||||
|
if (error instanceof Error) {
|
||||||
|
errorToThrow.name = error.name;
|
||||||
|
}
|
||||||
|
throw errorToThrow;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
exists(key: string) {
|
||||||
|
return window.localStorage[key] !== void 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(key: string) {
|
||||||
|
window.localStorage.removeItem(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const store = new Store();
|
@ -22,6 +22,7 @@ import {
|
|||||||
SupplementaryQueryType,
|
SupplementaryQueryType,
|
||||||
toLegacyResponseData,
|
toLegacyResponseData,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
import { combinePanelData } from '@grafana/o11y-ds-frontend';
|
||||||
import { config, getDataSourceSrv, reportInteraction } from '@grafana/runtime';
|
import { config, getDataSourceSrv, reportInteraction } from '@grafana/runtime';
|
||||||
import { DataQuery } from '@grafana/schema';
|
import { DataQuery } from '@grafana/schema';
|
||||||
import store from 'app/core/store';
|
import store from 'app/core/store';
|
||||||
@ -39,7 +40,6 @@ import {
|
|||||||
import { getShiftedTimeRange } from 'app/core/utils/timePicker';
|
import { getShiftedTimeRange } from 'app/core/utils/timePicker';
|
||||||
import { getCorrelationsBySourceUIDs } from 'app/features/correlations/utils';
|
import { getCorrelationsBySourceUIDs } from 'app/features/correlations/utils';
|
||||||
import { infiniteScrollRefId } from 'app/features/logs/logsModel';
|
import { infiniteScrollRefId } from 'app/features/logs/logsModel';
|
||||||
import { combinePanelData } from 'app/features/logs/response';
|
|
||||||
import { getFiscalYearStartMonth, getTimeZone } from 'app/features/profile/state/selectors';
|
import { getFiscalYearStartMonth, getTimeZone } from 'app/features/profile/state/selectors';
|
||||||
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
|
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
|
||||||
import {
|
import {
|
||||||
@ -764,6 +764,7 @@ export const runLoadMoreLogsQueries = createAsyncThunk<void, RunLoadMoreLogsQuer
|
|||||||
return of({ ...queryResponse, state: LoadingState.Loading });
|
return of({ ...queryResponse, state: LoadingState.Loading });
|
||||||
}
|
}
|
||||||
return decorateData(
|
return decorateData(
|
||||||
|
// This shouldn't be needed after https://github.com/grafana/grafana/issues/57327 is fixed
|
||||||
combinePanelData(queryResponse, data),
|
combinePanelData(queryResponse, data),
|
||||||
queryResponse,
|
queryResponse,
|
||||||
absoluteRange,
|
absoluteRange,
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
TimeRange,
|
TimeRange,
|
||||||
LoadingState,
|
LoadingState,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { combineResponses } from 'app/features/logs/response';
|
import { combineResponses } from '@grafana/o11y-ds-frontend';
|
||||||
|
|
||||||
import { LokiDatasource } from './datasource';
|
import { LokiDatasource } from './datasource';
|
||||||
import { splitTimeRange as splitLogsTimeRange } from './logsTimeSplitting';
|
import { splitTimeRange as splitLogsTimeRange } from './logsTimeSplitting';
|
||||||
|
@ -2,9 +2,9 @@ import { css } from '@emotion/css';
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
import { CoreApp, GrafanaTheme2, TimeRange } from '@grafana/data';
|
import { CoreApp, GrafanaTheme2, TimeRange } from '@grafana/data';
|
||||||
|
import { LocalStorageValueProvider } from '@grafana/o11y-ds-frontend';
|
||||||
import { reportInteraction } from '@grafana/runtime';
|
import { reportInteraction } from '@grafana/runtime';
|
||||||
import { LoadingPlaceholder, Modal, useStyles2 } from '@grafana/ui';
|
import { LoadingPlaceholder, Modal, useStyles2 } from '@grafana/ui';
|
||||||
import { LocalStorageValueProvider } from 'app/core/components/LocalStorageValueProvider';
|
|
||||||
|
|
||||||
import { LokiLabelBrowser } from '../../components/LokiLabelBrowser';
|
import { LokiLabelBrowser } from '../../components/LokiLabelBrowser';
|
||||||
import { LokiDatasource } from '../../datasource';
|
import { LokiDatasource } from '../../datasource';
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { render, screen, getAllByRole, waitFor } from '@testing-library/react';
|
import { render, screen, getAllByRole, waitFor } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { getSelectParent } from 'test/helpers/selectOptionInTest';
|
|
||||||
|
|
||||||
import { dateTime } from '@grafana/data';
|
import { dateTime } from '@grafana/data';
|
||||||
|
|
||||||
@ -136,3 +135,6 @@ describe('LokiQueryBuilder', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getSelectParent = (input: HTMLElement) =>
|
||||||
|
input.parentElement?.parentElement?.parentElement?.parentElement?.parentElement;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { render, screen, waitFor, findAllByRole } from '@testing-library/react';
|
import { render, screen, waitFor, findAllByRole } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { getSelectParent } from 'test/helpers/selectOptionInTest';
|
|
||||||
|
|
||||||
import { createLokiDatasource } from '../../__mocks__/datasource';
|
import { createLokiDatasource } from '../../__mocks__/datasource';
|
||||||
|
|
||||||
@ -138,3 +137,6 @@ async function addOperation(section: string, op: string) {
|
|||||||
// anywhere when debugging so not sure what style is it picking up.
|
// anywhere when debugging so not sure what style is it picking up.
|
||||||
await userEvent.click(opItem, { pointerEventsCheck: 0 });
|
await userEvent.click(opItem, { pointerEventsCheck: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getSelectParent = (input: HTMLElement) =>
|
||||||
|
input.parentElement?.parentElement?.parentElement?.parentElement?.parentElement;
|
||||||
|
@ -4,7 +4,6 @@ import React from 'react';
|
|||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import { useStyles2, HorizontalGroup, IconButton, Tooltip, Icon } from '@grafana/ui';
|
import { useStyles2, HorizontalGroup, IconButton, Tooltip, Icon } from '@grafana/ui';
|
||||||
import { getModKey } from 'app/core/utils/browser';
|
|
||||||
|
|
||||||
import { testIds } from '../../components/LokiQueryEditor';
|
import { testIds } from '../../components/LokiQueryEditor';
|
||||||
import { LokiQueryField } from '../../components/LokiQueryField';
|
import { LokiQueryField } from '../../components/LokiQueryField';
|
||||||
@ -57,7 +56,7 @@ export function LokiQueryCodeEditor({
|
|||||||
size="xs"
|
size="xs"
|
||||||
tooltip="Format query"
|
tooltip="Format query"
|
||||||
/>
|
/>
|
||||||
<Tooltip content={`Use ${getModKey()}+z to undo`}>
|
<Tooltip content={`Use ctrl/cmd + z to undo`}>
|
||||||
<Icon className={styles.hint} name="keyboard" />
|
<Icon className={styles.hint} name="keyboard" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
|
@ -2,10 +2,9 @@ import { css } from '@emotion/css';
|
|||||||
import { capitalize } from 'lodash';
|
import { capitalize } from 'lodash';
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { CoreApp, DataQuery, GrafanaTheme2 } from '@grafana/data';
|
import { CoreApp, DataQuery, GrafanaTheme2, getNextRefId } from '@grafana/data';
|
||||||
import { reportInteraction } from '@grafana/runtime';
|
import { reportInteraction } from '@grafana/runtime';
|
||||||
import { Button, Collapse, Modal, useStyles2 } from '@grafana/ui';
|
import { Button, Collapse, Modal, useStyles2 } from '@grafana/ui';
|
||||||
import { getNextRefIdChar } from 'app/core/utils/query';
|
|
||||||
|
|
||||||
import { LokiQuery } from '../../types';
|
import { LokiQuery } from '../../types';
|
||||||
import { lokiQueryModeller } from '../LokiQueryModeller';
|
import { lokiQueryModeller } from '../LokiQueryModeller';
|
||||||
@ -52,7 +51,7 @@ export const QueryPatternsModal = (props: Props) => {
|
|||||||
if (hasNewQueryOption && selectAsNewQuery) {
|
if (hasNewQueryOption && selectAsNewQuery) {
|
||||||
onAddQuery({
|
onAddQuery({
|
||||||
...query,
|
...query,
|
||||||
refId: getNextRefIdChar(queries ?? [query]),
|
refId: getNextRefId(queries ?? [query]),
|
||||||
expr: lokiQueryModeller.renderQuery(visualQuery.query),
|
expr: lokiQueryModeller.renderQuery(visualQuery.query),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user