mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: Cleanup duplicated code in grafana-prometheus package (#89542)
* remove redundant test matchers * use amendTable, trimTable functions from @grafana/data package * move getMockDataSource function into the mocks.ts * use LocalStorageValueProvider from @grafana/o11y-ds-frontend * move all mocks under __mocks__ directory * use store from @grafana/o11y-ds-frontend * move test related files under test directory * use getNextRefId from @grafana/data instead of deprecated getNextRefIdChar See: https://github.com/grafana/grafana/pull/87460 * betterer * remove unnecessary mockings * import from @grafana/data * import from @grafana/data
This commit is contained in:
parent
f659bc1f40
commit
7f5dde6ed3
@ -455,24 +455,6 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "5"]
|
||||
],
|
||||
"packages/grafana-prometheus/src/gcopypaste/app/features/live/data/amendTimeSeries.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "2"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "3"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "4"]
|
||||
],
|
||||
"packages/grafana-prometheus/src/gcopypaste/public/test/matchers/index.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
|
||||
],
|
||||
"packages/grafana-prometheus/src/gcopypaste/public/test/matchers/utils.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"packages/grafana-prometheus/src/language_provider.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
|
@ -2,13 +2,19 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { PureComponent, ReactNode } from 'react';
|
||||
|
||||
import { isDataFrame, QueryEditorProps, QueryHint, TimeRange, toLegacyResponseData } from '@grafana/data';
|
||||
import {
|
||||
isDataFrame,
|
||||
LocalStorageValueProvider,
|
||||
QueryEditorProps,
|
||||
QueryHint,
|
||||
TimeRange,
|
||||
toLegacyResponseData,
|
||||
} from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { clearButtonStyles, Icon, Themeable2, withTheme2 } from '@grafana/ui';
|
||||
|
||||
import { PrometheusDatasource } from '../datasource';
|
||||
import { LocalStorageValueProvider } from '../gcopypaste/app/core/components/LocalStorageValueProvider';
|
||||
import { roundMsToMin } from '../language_utils';
|
||||
import { PromOptions, PromQuery } from '../types';
|
||||
|
||||
|
@ -5,9 +5,9 @@ import userEvent from '@testing-library/user-event';
|
||||
import { dateTime, TimeRange } from '@grafana/data';
|
||||
|
||||
import { PrometheusDatasource } from '../datasource';
|
||||
import { selectOptionInTest } from '../gcopypaste/test/helpers/selectOptionInTest';
|
||||
import PrometheusLanguageProvider from '../language_provider';
|
||||
import { migrateVariableEditorBackToVariableSupport } from '../migrations/variableMigration';
|
||||
import { selectOptionInTest } from '../test/helpers/selectOptionInTest';
|
||||
import { PromVariableQuery, PromVariableQueryType, StandardPromVariableQuery } from '../types';
|
||||
|
||||
import { PromVariableQueryEditor, Props, variableMigration } from './VariableQueryEditor';
|
||||
|
@ -6,8 +6,9 @@ import { SelectableValue } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
import { createDefaultConfigOptions } from '../test/__mocks__/datasource';
|
||||
|
||||
import { countError, getValueFromEventItem, PromSettings } from './PromSettings';
|
||||
import { createDefaultConfigOptions } from './mocks';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.replaceProperty(config, 'featureToggles', {
|
||||
|
@ -1,15 +0,0 @@
|
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/configuration/mocks.ts
|
||||
import { DataSourceSettings } from '@grafana/data';
|
||||
|
||||
import { getMockDataSource } from '../gcopypaste/app/features/datasources/__mocks__/dataSourcesMocks';
|
||||
import { PromOptions } from '../types';
|
||||
|
||||
export function createDefaultConfigOptions(): DataSourceSettings<PromOptions> {
|
||||
return getMockDataSource<PromOptions>({
|
||||
jsonData: {
|
||||
timeInterval: '1m',
|
||||
queryTimeout: '1m',
|
||||
httpMethod: 'GET',
|
||||
},
|
||||
});
|
||||
}
|
@ -18,14 +18,6 @@ import {
|
||||
} from '@grafana/data';
|
||||
import { config, getBackendSrv, setBackendSrv, TemplateSrv } from '@grafana/runtime';
|
||||
|
||||
import {
|
||||
createAnnotationResponse,
|
||||
createDataRequest,
|
||||
createDefaultPromResponse,
|
||||
createEmptyAnnotationResponse,
|
||||
fetchMockCalledWith,
|
||||
getMockTimeRange,
|
||||
} from './__mocks__/datasource';
|
||||
import {
|
||||
alignRange,
|
||||
extractRuleMappingFromGroups,
|
||||
@ -34,6 +26,14 @@ import {
|
||||
prometheusSpecialRegexEscape,
|
||||
} from './datasource';
|
||||
import PromQlLanguageProvider from './language_provider';
|
||||
import {
|
||||
createAnnotationResponse,
|
||||
createDataRequest,
|
||||
createDefaultPromResponse,
|
||||
createEmptyAnnotationResponse,
|
||||
fetchMockCalledWith,
|
||||
getMockTimeRange,
|
||||
} from './test/__mocks__/datasource';
|
||||
import { PromApplication, PrometheusCacheLevel, PromOptions, PromQuery, PromQueryRequest } from './types';
|
||||
|
||||
const fetchMock = jest.fn().mockReturnValue(of(createDefaultPromResponse()));
|
||||
|
@ -1,51 +0,0 @@
|
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/core/components/LocalStorageValueProvider/LocalStorageValueProvider.tsx
|
||||
import { useEffect, useState } from 'react';
|
||||
import * as React 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,2 +0,0 @@
|
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/core/components/LocalStorageValueProvider/index.tsx
|
||||
export { LocalStorageValueProvider } from './LocalStorageValueProvider';
|
@ -1,66 +0,0 @@
|
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/core/store.ts
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
const store = new Store();
|
||||
export default store;
|
@ -1,21 +0,0 @@
|
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/core/utils/query.ts
|
||||
import { DataQuery } from '@grafana/data';
|
||||
|
||||
export const getNextRefIdChar = (queries: DataQuery[]): string => {
|
||||
for (let num = 0; ; num++) {
|
||||
const refId = getRefId(num);
|
||||
if (!queries.some((query) => query.refId === refId)) {
|
||||
return refId;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function getRefId(num: number): string {
|
||||
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
if (num < letters.length) {
|
||||
return letters[num];
|
||||
} else {
|
||||
return getRefId(Math.floor(num / letters.length) - 1) + letters[num % letters.length];
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/features/datasources/__mocks__/dataSourcesMocks.ts
|
||||
import { merge } from 'lodash';
|
||||
|
||||
import { DataSourceJsonData, DataSourceSettings } from '@grafana/data';
|
||||
|
||||
export const getMockDataSource = <T extends DataSourceJsonData>(
|
||||
overrides?: Partial<DataSourceSettings<T>>
|
||||
): DataSourceSettings<T> =>
|
||||
merge(
|
||||
{
|
||||
access: '',
|
||||
basicAuth: false,
|
||||
basicAuthUser: '',
|
||||
withCredentials: false,
|
||||
database: '',
|
||||
id: 13,
|
||||
uid: 'x',
|
||||
isDefault: false,
|
||||
jsonData: { authType: 'credentials', defaultRegion: 'eu-west-2' },
|
||||
name: 'gdev-prometheus',
|
||||
typeName: 'Prometheus',
|
||||
orgId: 1,
|
||||
readOnly: false,
|
||||
type: 'prometheus',
|
||||
typeLogoUrl: 'packages/grafana-prometheus/src/img/prometheus_logo.svg',
|
||||
url: '',
|
||||
user: '',
|
||||
secureJsonFields: {},
|
||||
},
|
||||
overrides
|
||||
);
|
@ -1,94 +0,0 @@
|
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/features/live/data/amendTimeSeries.ts
|
||||
import { closestIdx } from '@grafana/data';
|
||||
|
||||
export type Table = [times: number[], ...values: any[][]];
|
||||
|
||||
// prevTable and nextTable are assumed sorted ASC on reference [0] arrays
|
||||
// nextTable is assumed to be contiguous, only edges are checked for overlap
|
||||
// ...so prev: [1,2,5] + next: [3,4,6] -> [1,2,3,4,6]
|
||||
export function amendTable(prevTable: Table, nextTable: Table): Table {
|
||||
let [prevTimes] = prevTable;
|
||||
let [nextTimes] = nextTable;
|
||||
|
||||
let pLen = prevTimes.length;
|
||||
let pStart = prevTimes[0];
|
||||
let pEnd = prevTimes[pLen - 1];
|
||||
|
||||
let nLen = nextTimes.length;
|
||||
let nStart = nextTimes[0];
|
||||
let nEnd = nextTimes[nLen - 1];
|
||||
|
||||
let outTable: Table;
|
||||
|
||||
if (pLen) {
|
||||
if (nLen) {
|
||||
// append, no overlap
|
||||
if (nStart > pEnd) {
|
||||
outTable = prevTable.map((_, i) => prevTable[i].concat(nextTable[i])) as Table;
|
||||
}
|
||||
// prepend, no overlap
|
||||
else if (nEnd < pStart) {
|
||||
outTable = nextTable.map((_, i) => nextTable[i].concat(prevTable[i])) as Table;
|
||||
}
|
||||
// full replace
|
||||
else if (nStart <= pStart && nEnd >= pEnd) {
|
||||
outTable = nextTable;
|
||||
}
|
||||
// partial replace
|
||||
else if (nStart > pStart && nEnd < pEnd) {
|
||||
}
|
||||
// append, with overlap
|
||||
else if (nStart >= pStart) {
|
||||
let idx = closestIdx(nStart, prevTimes);
|
||||
idx = prevTimes[idx] < nStart ? idx - 1 : idx;
|
||||
outTable = prevTable.map((_, i) => prevTable[i].slice(0, idx).concat(nextTable[i])) as Table;
|
||||
}
|
||||
// prepend, with overlap
|
||||
else if (nEnd >= pStart) {
|
||||
let idx = closestIdx(nEnd, prevTimes);
|
||||
idx = prevTimes[idx] < nEnd ? idx : idx + 1;
|
||||
outTable = nextTable.map((_, i) => nextTable[i].concat(prevTable[i].slice(idx))) as Table;
|
||||
}
|
||||
} else {
|
||||
outTable = prevTable;
|
||||
}
|
||||
} else {
|
||||
if (nLen) {
|
||||
outTable = nextTable;
|
||||
} else {
|
||||
outTable = [[]];
|
||||
}
|
||||
}
|
||||
|
||||
return outTable!;
|
||||
}
|
||||
|
||||
export function trimTable(table: Table, fromTime: number, toTime: number): Table {
|
||||
let [times, ...vals] = table;
|
||||
let fromIdx: number | undefined;
|
||||
let toIdx: number | undefined;
|
||||
|
||||
// trim to bounds
|
||||
if (times[0] < fromTime) {
|
||||
fromIdx = closestIdx(fromTime, times);
|
||||
|
||||
if (times[fromIdx] < fromTime) {
|
||||
fromIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
if (times[times.length - 1] > toTime) {
|
||||
toIdx = closestIdx(toTime, times);
|
||||
|
||||
if (times[toIdx] > toTime) {
|
||||
toIdx--;
|
||||
}
|
||||
}
|
||||
|
||||
if (fromIdx != null || toIdx != null) {
|
||||
times = times.slice(fromIdx ?? 0, toIdx);
|
||||
vals = vals.map(vals2 => vals2.slice(fromIdx ?? 0, toIdx));
|
||||
}
|
||||
|
||||
return [times, ...vals];
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/index.ts
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { toEmitValues } from './toEmitValues';
|
||||
import { toEmitValuesWith } from './toEmitValuesWith';
|
||||
import { ObservableMatchers } from './types';
|
||||
|
||||
export const matchers: ObservableMatchers<void, Observable<any>> = {
|
||||
toEmitValues,
|
||||
toEmitValuesWith,
|
||||
};
|
@ -1,141 +0,0 @@
|
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/toEmitValues.test.ts
|
||||
import { interval, Observable, of, throwError } from 'rxjs';
|
||||
import { map, mergeMap, take } from 'rxjs/operators';
|
||||
|
||||
import { OBSERVABLE_TEST_TIMEOUT_IN_MS } from './types';
|
||||
|
||||
describe('toEmitValues matcher', () => {
|
||||
describe('failing tests', () => {
|
||||
describe('passing null in expect', () => {
|
||||
it('should fail', async () => {
|
||||
const observable = null as unknown as Observable<number>;
|
||||
|
||||
const rejects = expect(() => expect(observable).toEmitValues([1, 2, 3])).rejects;
|
||||
await rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('passing undefined in expect', () => {
|
||||
it('should fail', async () => {
|
||||
const observable = undefined as unknown as Observable<number>;
|
||||
|
||||
const rejects = expect(() => expect(observable).toEmitValues([1, 2, 3])).rejects;
|
||||
await rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('passing number instead of Observable in expect', () => {
|
||||
it('should fail', async () => {
|
||||
const observable = 1 as unknown as Observable<number>;
|
||||
|
||||
const rejects = expect(() => expect(observable).toEmitValues([1, 2, 3])).rejects;
|
||||
await rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('wrong number of emitted values', () => {
|
||||
it('should fail', async () => {
|
||||
const observable = interval(10).pipe(take(3));
|
||||
|
||||
const rejects = expect(() => expect(observable).toEmitValues([0, 1])).rejects;
|
||||
await rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('wrong emitted values', () => {
|
||||
it('should fail', async () => {
|
||||
const observable = interval(10).pipe(take(3));
|
||||
|
||||
const rejects = expect(() => expect(observable).toEmitValues([1, 2, 3])).rejects;
|
||||
await rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('wrong emitted value types', () => {
|
||||
it('should fail', async () => {
|
||||
const observable = interval(10).pipe(take(3)) as unknown as Observable<string>;
|
||||
|
||||
const rejects = expect(() => expect(observable).toEmitValues(['0', '1', '2'])).rejects;
|
||||
await rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe(`observable that does not complete within ${OBSERVABLE_TEST_TIMEOUT_IN_MS}ms`, () => {
|
||||
it('should fail', async () => {
|
||||
const observable = interval(600);
|
||||
|
||||
const rejects = expect(() => expect(observable).toEmitValues([0])).rejects;
|
||||
await rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('passing tests', () => {
|
||||
describe('correct emitted values', () => {
|
||||
it('should pass with correct message', async () => {
|
||||
const observable = interval(10).pipe(take(3));
|
||||
await expect(observable).toEmitValues([0, 1, 2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('using nested arrays', () => {
|
||||
it('should pass with correct message', async () => {
|
||||
const observable = interval(10).pipe(
|
||||
map((interval) => [{ text: interval.toString(), value: interval }]),
|
||||
take(3)
|
||||
);
|
||||
await expect(observable).toEmitValues([
|
||||
[{ text: '0', value: 0 }],
|
||||
[{ text: '1', value: 1 }],
|
||||
[{ text: '2', value: 2 }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('using nested objects', () => {
|
||||
it('should pass with correct message', async () => {
|
||||
const observable = interval(10).pipe(
|
||||
map((interval) => ({ inner: { text: interval.toString(), value: interval } })),
|
||||
take(3)
|
||||
);
|
||||
await expect(observable).toEmitValues([
|
||||
{ inner: { text: '0', value: 0 } },
|
||||
{ inner: { text: '1', value: 1 } },
|
||||
{ inner: { text: '2', value: 2 } },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('correct emitted values with throw', () => {
|
||||
it('should pass with correct message', async () => {
|
||||
const observable = interval(10).pipe(
|
||||
map((interval) => {
|
||||
if (interval > 1) {
|
||||
throw 'an error';
|
||||
}
|
||||
|
||||
return interval;
|
||||
})
|
||||
) as unknown as Observable<string | number>;
|
||||
|
||||
await expect(observable).toEmitValues([0, 1, 'an error']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('correct emitted values with throwError', () => {
|
||||
it('should pass with correct message', async () => {
|
||||
const observable = interval(10).pipe(
|
||||
mergeMap((interval) => {
|
||||
if (interval === 1) {
|
||||
return throwError('an error');
|
||||
}
|
||||
|
||||
return of(interval);
|
||||
})
|
||||
) as unknown as Observable<string | number>;
|
||||
|
||||
await expect(observable).toEmitValues([0, 'an error']);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,91 +0,0 @@
|
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/toEmitValues.ts
|
||||
import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils';
|
||||
import { isEqual } from 'lodash';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { expectObservable, forceObservableCompletion } from './utils';
|
||||
|
||||
function passMessage(received: unknown[], expected: unknown[]) {
|
||||
return `${matcherHint('.not.toEmitValues')}
|
||||
|
||||
Expected observable to emit values:
|
||||
${printExpected(expected)}
|
||||
Received:
|
||||
${printReceived(received)}
|
||||
`;
|
||||
}
|
||||
|
||||
function failMessage(received: unknown[], expected: unknown[]) {
|
||||
return `${matcherHint('.toEmitValues')}
|
||||
|
||||
Expected observable to emit values:
|
||||
${printExpected(expected)}
|
||||
Received:
|
||||
${printReceived(received)}
|
||||
`;
|
||||
}
|
||||
|
||||
function tryExpectations(received: unknown[], expected: unknown[]): jest.CustomMatcherResult {
|
||||
try {
|
||||
if (received.length !== expected.length) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => failMessage(received, expected),
|
||||
};
|
||||
}
|
||||
|
||||
for (let index = 0; index < received.length; index++) {
|
||||
const left = received[index];
|
||||
const right = expected[index];
|
||||
|
||||
if (!isEqual(left, right)) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => failMessage(received, expected),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
pass: true,
|
||||
message: () => passMessage(received, expected),
|
||||
};
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'An unknown error occurred';
|
||||
return {
|
||||
pass: false,
|
||||
message: () => message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function toEmitValues(received: Observable<unknown>, expected: unknown[]): Promise<jest.CustomMatcherResult> {
|
||||
const failsChecks = expectObservable(received);
|
||||
if (failsChecks) {
|
||||
return Promise.resolve(failsChecks);
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const receivedValues: unknown[] = [];
|
||||
const subscription = new Subscription();
|
||||
|
||||
subscription.add(
|
||||
received.subscribe({
|
||||
next: (value) => {
|
||||
receivedValues.push(value);
|
||||
},
|
||||
error: (err) => {
|
||||
receivedValues.push(err);
|
||||
subscription.unsubscribe();
|
||||
resolve(tryExpectations(receivedValues, expected));
|
||||
},
|
||||
complete: () => {
|
||||
subscription.unsubscribe();
|
||||
resolve(tryExpectations(receivedValues, expected));
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
forceObservableCompletion(subscription, resolve);
|
||||
});
|
||||
}
|
@ -1,154 +0,0 @@
|
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/toEmitValuesWith.test.ts
|
||||
import { interval, Observable, of, throwError } from 'rxjs';
|
||||
import { map, mergeMap, take } from 'rxjs/operators';
|
||||
|
||||
import { OBSERVABLE_TEST_TIMEOUT_IN_MS } from './types';
|
||||
|
||||
describe('toEmitValuesWith matcher', () => {
|
||||
describe('failing tests', () => {
|
||||
describe('passing null in expect', () => {
|
||||
it('should fail with correct message', async () => {
|
||||
const observable = null as unknown as Observable<number>;
|
||||
|
||||
const rejects = expect(() =>
|
||||
expect(observable).toEmitValuesWith((received) => {
|
||||
expect(received).toEqual([1, 2, 3]);
|
||||
})
|
||||
).rejects;
|
||||
|
||||
await rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('passing undefined in expect', () => {
|
||||
it('should fail with correct message', async () => {
|
||||
const observable = undefined as unknown as Observable<number>;
|
||||
|
||||
const rejects = expect(() =>
|
||||
expect(observable).toEmitValuesWith((received) => {
|
||||
expect(received).toEqual([1, 2, 3]);
|
||||
})
|
||||
).rejects;
|
||||
|
||||
await rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('passing number instead of Observable in expect', () => {
|
||||
it('should fail with correct message', async () => {
|
||||
const observable = 1 as unknown as Observable<number>;
|
||||
|
||||
const rejects = expect(() =>
|
||||
expect(observable).toEmitValuesWith((received) => {
|
||||
expect(received).toEqual([1, 2, 3]);
|
||||
})
|
||||
).rejects;
|
||||
|
||||
await rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('wrong number of emitted values', () => {
|
||||
it('should fail with correct message', async () => {
|
||||
const observable = interval(10).pipe(take(3));
|
||||
|
||||
const rejects = expect(() =>
|
||||
expect(observable).toEmitValuesWith((received) => {
|
||||
expect(received).toEqual([0, 1]);
|
||||
})
|
||||
).rejects;
|
||||
|
||||
await rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('wrong emitted values', () => {
|
||||
it('should fail with correct message', async () => {
|
||||
const observable = interval(10).pipe(take(3));
|
||||
|
||||
const rejects = expect(() =>
|
||||
expect(observable).toEmitValuesWith((received) => {
|
||||
expect(received).toEqual([1, 2, 3]);
|
||||
})
|
||||
).rejects;
|
||||
|
||||
await rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('wrong emitted value types', () => {
|
||||
it('should fail with correct message', async () => {
|
||||
const observable = interval(10).pipe(take(3)) as unknown as Observable<string>;
|
||||
|
||||
const rejects = expect(() =>
|
||||
expect(observable).toEmitValuesWith((received) => {
|
||||
expect(received).toEqual(['0', '1', '2']);
|
||||
})
|
||||
).rejects;
|
||||
|
||||
await rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe(`observable that does not complete within ${OBSERVABLE_TEST_TIMEOUT_IN_MS}ms`, () => {
|
||||
it('should fail with correct message', async () => {
|
||||
const observable = interval(600);
|
||||
|
||||
const rejects = expect(() =>
|
||||
expect(observable).toEmitValuesWith((received) => {
|
||||
expect(received).toEqual([0]);
|
||||
})
|
||||
).rejects;
|
||||
|
||||
await rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('passing tests', () => {
|
||||
describe('correct emitted values', () => {
|
||||
it('should pass with correct message', async () => {
|
||||
const observable = interval(10).pipe(take(3));
|
||||
await expect(observable).toEmitValuesWith((received) => {
|
||||
expect(received).toEqual([0, 1, 2]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('correct emitted values with throw', () => {
|
||||
it('should pass with correct message', async () => {
|
||||
const observable = interval(10).pipe(
|
||||
map((interval) => {
|
||||
if (interval > 1) {
|
||||
throw 'an error';
|
||||
}
|
||||
|
||||
return interval;
|
||||
})
|
||||
);
|
||||
|
||||
await expect(observable).toEmitValuesWith((received) => {
|
||||
expect(received).toEqual([0, 1, 'an error']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('correct emitted values with throwError', () => {
|
||||
it('should pass with correct message', async () => {
|
||||
const observable = interval(10).pipe(
|
||||
mergeMap((interval) => {
|
||||
if (interval === 1) {
|
||||
return throwError('an error');
|
||||
}
|
||||
|
||||
return of(interval);
|
||||
})
|
||||
);
|
||||
|
||||
await expect(observable).toEmitValuesWith((received) => {
|
||||
expect(received).toEqual([0, 'an error']);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,63 +0,0 @@
|
||||
// Core Grafana history
|
||||
import { matcherHint, printReceived } from 'jest-matcher-utils';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { expectObservable, forceObservableCompletion } from './utils';
|
||||
|
||||
function tryExpectations(received: unknown[], expectations: (received: unknown[]) => void): jest.CustomMatcherResult {
|
||||
try {
|
||||
expectations(received);
|
||||
return {
|
||||
pass: true,
|
||||
message: () => `${matcherHint('.not.toEmitValues')}
|
||||
|
||||
Expected observable to complete with
|
||||
${printReceived(received)}
|
||||
`,
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => 'failed ' + err,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all the values emitted by the observables (also errors) and pass them to the expectations functions after
|
||||
* the observable ended (or emitted error). If Observable does not complete within OBSERVABLE_TEST_TIMEOUT_IN_MS the
|
||||
* test fails.
|
||||
*/
|
||||
export function toEmitValuesWith(
|
||||
received: Observable<any>,
|
||||
expectations: (actual: any[]) => void
|
||||
): Promise<jest.CustomMatcherResult> {
|
||||
const failsChecks = expectObservable(received);
|
||||
if (failsChecks) {
|
||||
return Promise.resolve(failsChecks);
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const receivedValues: any[] = [];
|
||||
const subscription = new Subscription();
|
||||
|
||||
subscription.add(
|
||||
received.subscribe({
|
||||
next: (value) => {
|
||||
receivedValues.push(value);
|
||||
},
|
||||
error: (err) => {
|
||||
receivedValues.push(err);
|
||||
subscription.unsubscribe();
|
||||
resolve(tryExpectations(receivedValues, expectations));
|
||||
},
|
||||
complete: () => {
|
||||
subscription.unsubscribe();
|
||||
resolve(tryExpectations(receivedValues, expectations));
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
forceObservableCompletion(subscription, resolve);
|
||||
});
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/toEmitValuesWith.ts
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export const OBSERVABLE_TEST_TIMEOUT_IN_MS = 1000;
|
||||
|
||||
export type ObservableType<T> = T extends Observable<infer V> ? V : never;
|
||||
|
||||
export interface ObservableMatchers<R, T = {}> extends jest.ExpectExtendMap {
|
||||
toEmitValues<E = ObservableType<T>>(received: T, expected: E[]): Promise<jest.CustomMatcherResult>;
|
||||
toEmitValuesWith<E = ObservableType<T>>(
|
||||
received: T,
|
||||
expectations: (received: E[]) => void
|
||||
): Promise<jest.CustomMatcherResult>;
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/utils.ts
|
||||
import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils';
|
||||
import { asapScheduler, Subscription, timer, isObservable } from 'rxjs';
|
||||
|
||||
import { OBSERVABLE_TEST_TIMEOUT_IN_MS } from './types';
|
||||
|
||||
export function forceObservableCompletion(subscription: Subscription, resolve: (args: any) => void) {
|
||||
const timeoutObservable = timer(OBSERVABLE_TEST_TIMEOUT_IN_MS, asapScheduler);
|
||||
|
||||
subscription.add(
|
||||
timeoutObservable.subscribe(() => {
|
||||
subscription.unsubscribe();
|
||||
resolve({
|
||||
pass: false,
|
||||
message: () =>
|
||||
`${matcherHint('.toEmitValues')}
|
||||
|
||||
Expected ${printReceived('Observable')} to be ${printExpected(
|
||||
`completed within ${OBSERVABLE_TEST_TIMEOUT_IN_MS}ms`
|
||||
)} but it did not.`,
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function expectObservableToBeDefined(received: unknown): jest.CustomMatcherResult | null {
|
||||
if (received) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `${matcherHint('.toEmitValues')}
|
||||
|
||||
Expected ${printReceived(received)} to be ${printExpected('defined')}.`,
|
||||
};
|
||||
}
|
||||
|
||||
export function expectObservableToBeObservable(received: unknown): jest.CustomMatcherResult | null {
|
||||
if (isObservable(received)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `${matcherHint('.toEmitValues')}
|
||||
|
||||
Expected ${printReceived(received)} to be ${printExpected('an Observable')}.`,
|
||||
};
|
||||
}
|
||||
|
||||
export function expectObservable(received: unknown): jest.CustomMatcherResult | null {
|
||||
const toBeDefined = expectObservableToBeDefined(received);
|
||||
if (toBeDefined) {
|
||||
return toBeDefined;
|
||||
}
|
||||
|
||||
const toBeObservable = expectObservableToBeObservable(received);
|
||||
if (toBeObservable) {
|
||||
return toBeObservable;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
@ -3,11 +3,10 @@ import { css } from '@emotion/css';
|
||||
import { capitalize } from 'lodash';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { CoreApp, DataQuery, GrafanaTheme2 } from '@grafana/data';
|
||||
import { CoreApp, DataQuery, getNextRefId, GrafanaTheme2 } from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { Button, Collapse, Modal, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { getNextRefIdChar } from '../gcopypaste/app/core/utils/query';
|
||||
import { PromQuery } from '../types';
|
||||
|
||||
import { promQueryModeller } from './PromQueryModeller';
|
||||
@ -59,7 +58,7 @@ export const QueryPatternsModal = (props: Props) => {
|
||||
if (hasNewQueryOption && selectAsNewQuery) {
|
||||
onAddQuery({
|
||||
...query,
|
||||
refId: getNextRefIdChar(queries ?? [query]),
|
||||
refId: getNextRefId(queries ?? [query]),
|
||||
expr: promQueryModeller.renderQuery(visualQuery.query),
|
||||
});
|
||||
} else {
|
||||
|
@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { ComponentProps } from 'react';
|
||||
|
||||
import { selectOptionInTest } from '../../gcopypaste/test/helpers/selectOptionInTest';
|
||||
import { selectOptionInTest } from '../../test/helpers/selectOptionInTest';
|
||||
import { getLabelSelects } from '../testUtils';
|
||||
|
||||
import { LabelFilters, MISSING_LABEL_FILTER_ERROR_MESSAGE, LabelFiltersProps } from './LabelFilters';
|
||||
|
@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { CoreApp } from '@grafana/data';
|
||||
|
||||
import { selectOptionInTest } from '../../gcopypaste/test/helpers/selectOptionInTest';
|
||||
import { selectOptionInTest } from '../../test/helpers/selectOptionInTest';
|
||||
import { PromQuery } from '../../types';
|
||||
import { getQueryWithDefaults } from '../state';
|
||||
|
||||
|
@ -30,18 +30,6 @@ jest.mock('../../components/monaco-query-field/MonacoQueryFieldWrapper', () => {
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../gcopypaste/app/core/store', () => {
|
||||
return {
|
||||
get() {
|
||||
return undefined;
|
||||
},
|
||||
set() {},
|
||||
getObject(key: string, defaultValue: unknown) {
|
||||
return defaultValue;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('@grafana/runtime', () => {
|
||||
return {
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
|
@ -24,18 +24,6 @@ jest.mock('../../components/monaco-query-field/MonacoQueryFieldWrapper', () => {
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('app/core/store', () => {
|
||||
return {
|
||||
get() {
|
||||
return undefined;
|
||||
},
|
||||
set() {},
|
||||
getObject(key: string, defaultValue: unknown) {
|
||||
return defaultValue;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('@grafana/runtime', () => {
|
||||
return {
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
|
@ -3,12 +3,11 @@ import { css, cx } from '@emotion/css';
|
||||
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
|
||||
import { useEffect, useReducer, useRef, useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { GrafanaTheme2, store } from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { Alert, Button, Checkbox, Input, Spinner, useTheme2 } from '@grafana/ui';
|
||||
|
||||
import { PrometheusDatasource } from '../../../datasource';
|
||||
import store from '../../../gcopypaste/app/core/store';
|
||||
import { PromVisualQuery } from '../../types';
|
||||
|
||||
import { QuerySuggestionContainer } from './QuerySuggestionContainer';
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/hooks/useFlag.ts
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import store from '../../gcopypaste/app/core/store';
|
||||
import { store } from '@grafana/data';
|
||||
|
||||
export const promQueryEditorExplainKey = 'PrometheusQueryEditorExplainDefault';
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/state.ts
|
||||
import { CoreApp } from '@grafana/data';
|
||||
import { CoreApp, store } from '@grafana/data';
|
||||
|
||||
import store from '../gcopypaste/app/core/store';
|
||||
import { LegendFormatMode, PromQuery } from '../types';
|
||||
|
||||
import { QueryEditorMode } from './shared/types';
|
||||
|
@ -1,5 +1,6 @@
|
||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querycache/QueryCache.ts
|
||||
import {
|
||||
amendTable,
|
||||
DataFrame,
|
||||
DataQueryRequest,
|
||||
dateTime,
|
||||
@ -8,9 +9,10 @@ import {
|
||||
incrRoundDn,
|
||||
isValidDuration,
|
||||
parseDuration,
|
||||
Table,
|
||||
trimTable,
|
||||
} from '@grafana/data';
|
||||
|
||||
import { amendTable, Table, trimTable } from '../gcopypaste/app/features/live/data/amendTimeSeries';
|
||||
import { PromQuery } from '../types';
|
||||
|
||||
// dashboardUID + panelId + refId
|
||||
|
@ -1,6 +1,53 @@
|
||||
import { CoreApp, DataQueryRequest, dateTime, rangeUtil, TimeRange } from '@grafana/data';
|
||||
import { merge } from 'lodash';
|
||||
|
||||
import { PromQuery } from '../types';
|
||||
import {
|
||||
CoreApp,
|
||||
DataQueryRequest,
|
||||
DataSourceJsonData,
|
||||
DataSourceSettings,
|
||||
dateTime,
|
||||
rangeUtil,
|
||||
TimeRange,
|
||||
} from '@grafana/data';
|
||||
|
||||
import { PromOptions, PromQuery } from '../../types';
|
||||
|
||||
export const getMockDataSource = <T extends DataSourceJsonData>(
|
||||
overrides?: Partial<DataSourceSettings<T>>
|
||||
): DataSourceSettings<T> =>
|
||||
merge(
|
||||
{
|
||||
access: '',
|
||||
basicAuth: false,
|
||||
basicAuthUser: '',
|
||||
withCredentials: false,
|
||||
database: '',
|
||||
id: 13,
|
||||
uid: 'x',
|
||||
isDefault: false,
|
||||
jsonData: { authType: 'credentials', defaultRegion: 'eu-west-2' },
|
||||
name: 'gdev-prometheus',
|
||||
typeName: 'Prometheus',
|
||||
orgId: 1,
|
||||
readOnly: false,
|
||||
type: 'prometheus',
|
||||
typeLogoUrl: 'packages/grafana-prometheus/src/img/prometheus_logo.svg',
|
||||
url: '',
|
||||
user: '',
|
||||
secureJsonFields: {},
|
||||
},
|
||||
overrides
|
||||
);
|
||||
|
||||
export function createDefaultConfigOptions(): DataSourceSettings<PromOptions> {
|
||||
return getMockDataSource<PromOptions>({
|
||||
jsonData: {
|
||||
timeInterval: '1m',
|
||||
queryTimeout: '1m',
|
||||
httpMethod: 'GET',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function createDataRequest(
|
||||
targets: PromQuery[],
|
Loading…
Reference in New Issue
Block a user