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 { getMockFrames } from 'app/plugins/datasource/loki/__mocks__/frames';
|
||||
import {
|
||||
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', () => {
|
||||
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';
|
||||
|
||||
// This is specific for Loki
|
||||
function getCombinedMetadataStats(
|
||||
destStats: QueryResultMetaStat[],
|
||||
sourceStats: QueryResultMetaStat[]
|
@ -13,3 +13,6 @@ export * from './TraceToLogs/TraceToLogsSettings';
|
||||
export * from './TraceToMetrics/TraceToMetricsSettings';
|
||||
export * from './TraceToProfiles/TraceToProfilesSettings';
|
||||
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,
|
||||
toLegacyResponseData,
|
||||
} from '@grafana/data';
|
||||
import { combinePanelData } from '@grafana/o11y-ds-frontend';
|
||||
import { config, getDataSourceSrv, reportInteraction } from '@grafana/runtime';
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
import store from 'app/core/store';
|
||||
@ -39,7 +40,6 @@ import {
|
||||
import { getShiftedTimeRange } from 'app/core/utils/timePicker';
|
||||
import { getCorrelationsBySourceUIDs } from 'app/features/correlations/utils';
|
||||
import { infiniteScrollRefId } from 'app/features/logs/logsModel';
|
||||
import { combinePanelData } from 'app/features/logs/response';
|
||||
import { getFiscalYearStartMonth, getTimeZone } from 'app/features/profile/state/selectors';
|
||||
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
|
||||
import {
|
||||
@ -764,6 +764,7 @@ export const runLoadMoreLogsQueries = createAsyncThunk<void, RunLoadMoreLogsQuer
|
||||
return of({ ...queryResponse, state: LoadingState.Loading });
|
||||
}
|
||||
return decorateData(
|
||||
// This shouldn't be needed after https://github.com/grafana/grafana/issues/57327 is fixed
|
||||
combinePanelData(queryResponse, data),
|
||||
queryResponse,
|
||||
absoluteRange,
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
TimeRange,
|
||||
LoadingState,
|
||||
} from '@grafana/data';
|
||||
import { combineResponses } from 'app/features/logs/response';
|
||||
import { combineResponses } from '@grafana/o11y-ds-frontend';
|
||||
|
||||
import { LokiDatasource } from './datasource';
|
||||
import { splitTimeRange as splitLogsTimeRange } from './logsTimeSplitting';
|
||||
|
@ -2,9 +2,9 @@ import { css } from '@emotion/css';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import { CoreApp, GrafanaTheme2, TimeRange } from '@grafana/data';
|
||||
import { LocalStorageValueProvider } from '@grafana/o11y-ds-frontend';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { LoadingPlaceholder, Modal, useStyles2 } from '@grafana/ui';
|
||||
import { LocalStorageValueProvider } from 'app/core/components/LocalStorageValueProvider';
|
||||
|
||||
import { LokiLabelBrowser } from '../../components/LokiLabelBrowser';
|
||||
import { LokiDatasource } from '../../datasource';
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { render, screen, getAllByRole, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { getSelectParent } from 'test/helpers/selectOptionInTest';
|
||||
|
||||
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 userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { getSelectParent } from 'test/helpers/selectOptionInTest';
|
||||
|
||||
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.
|
||||
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 { config } from '@grafana/runtime';
|
||||
import { useStyles2, HorizontalGroup, IconButton, Tooltip, Icon } from '@grafana/ui';
|
||||
import { getModKey } from 'app/core/utils/browser';
|
||||
|
||||
import { testIds } from '../../components/LokiQueryEditor';
|
||||
import { LokiQueryField } from '../../components/LokiQueryField';
|
||||
@ -57,7 +56,7 @@ export function LokiQueryCodeEditor({
|
||||
size="xs"
|
||||
tooltip="Format query"
|
||||
/>
|
||||
<Tooltip content={`Use ${getModKey()}+z to undo`}>
|
||||
<Tooltip content={`Use ctrl/cmd + z to undo`}>
|
||||
<Icon className={styles.hint} name="keyboard" />
|
||||
</Tooltip>
|
||||
</HorizontalGroup>
|
||||
|
@ -2,10 +2,9 @@ import { css } from '@emotion/css';
|
||||
import { capitalize } from 'lodash';
|
||||
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 { Button, Collapse, Modal, useStyles2 } from '@grafana/ui';
|
||||
import { getNextRefIdChar } from 'app/core/utils/query';
|
||||
|
||||
import { LokiQuery } from '../../types';
|
||||
import { lokiQueryModeller } from '../LokiQueryModeller';
|
||||
@ -52,7 +51,7 @@ export const QueryPatternsModal = (props: Props) => {
|
||||
if (hasNewQueryOption && selectAsNewQuery) {
|
||||
onAddQuery({
|
||||
...query,
|
||||
refId: getNextRefIdChar(queries ?? [query]),
|
||||
refId: getNextRefId(queries ?? [query]),
|
||||
expr: lokiQueryModeller.renderQuery(visualQuery.query),
|
||||
});
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user