DataFrame: Add cached response notice for X-Cache: HIT header (#45564)

This commit is contained in:
Todd Treece 2022-02-22 22:17:35 -05:00 committed by GitHub
parent af2d19b02e
commit 01df00fd0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 80 additions and 2 deletions

View File

@ -1,6 +1,6 @@
import { DataQuery, toDataFrameDTO, DataFrame } from '@grafana/data';
import { FetchError, FetchResponse } from 'src/services';
import { BackendDataSourceResponse, toDataQueryResponse, toTestingStatus } from './queryResponse';
import { BackendDataSourceResponse, cachedResponseNotice, toDataQueryResponse, toTestingStatus } from './queryResponse';
const resp = {
data: {
@ -277,6 +277,55 @@ describe('Query Response parser', () => {
expect(ids).toEqual(['A', 'B']);
});
describe('Cache notice', () => {
let resp: any;
beforeEach(() => {
resp = {
url: '',
type: 'basic',
config: { url: '' },
status: 200,
statusText: 'OK',
ok: true,
redirected: false,
headers: new Headers(),
data: {
results: {
A: { frames: [{ schema: { fields: [] } }] },
},
},
};
});
test('adds notice for responses with X-Cache: HIT header', () => {
const queries: DataQuery[] = [{ refId: 'A' }];
resp.headers.set('X-Cache', 'HIT');
expect(toDataQueryResponse(resp, queries).data[0].meta.notices).toStrictEqual([cachedResponseNotice]);
});
test('does not remove existing notices', () => {
const queries: DataQuery[] = [{ refId: 'A' }];
resp.headers.set('X-Cache', 'HIT');
resp.data.results.A.frames[0].schema.meta = { notices: [{ severity: 'info', text: 'Example' }] };
expect(toDataQueryResponse(resp, queries).data[0].meta.notices).toStrictEqual([
{ severity: 'info', text: 'Example' },
cachedResponseNotice,
]);
});
test('does not add notice for responses with X-Cache: MISS header', () => {
const queries: DataQuery[] = [{ refId: 'A' }];
resp.headers.set('X-Cache', 'MISS');
expect(toDataQueryResponse(resp, queries).data[0].meta?.notices).toBeUndefined();
});
test('does not add notice for responses without X-Cache header', () => {
const queries: DataQuery[] = [{ refId: 'A' }];
expect(toDataQueryResponse(resp, queries).data[0].meta?.notices).toBeUndefined();
});
});
test('resultWithError', () => {
// Generated from:
// qdr.Responses[q.GetRefID()] = backend.DataResponse{

View File

@ -12,10 +12,13 @@ import {
DataQuery,
DataFrameJSON,
dataFrameFromJSON,
QueryResultMetaNotice,
} from '@grafana/data';
import { FetchError, FetchResponse } from '../services';
import { toDataQueryError } from './toDataQueryError';
export const cachedResponseNotice: QueryResultMetaNotice = { severity: 'info', text: 'Cached response' };
/**
* Single response object from a backend data source. Properties are optional but response should contain at least
* an error or a some data (but can contain both). Main way to send data is with dataframes attribute as series and
@ -62,6 +65,7 @@ export function toDataQueryResponse(
if ((res as FetchResponse).data?.results) {
const results = (res as FetchResponse).data.results;
const refIDs = queries?.length ? queries.map((q) => q.refId) : Object.keys(results);
const cachedResponse = isCachedResponse(res as FetchResponse);
const data: DataResponse[] = [];
for (const refId of refIDs) {
@ -85,7 +89,10 @@ export function toDataQueryResponse(
}
if (dr.frames?.length) {
for (const js of dr.frames) {
for (let js of dr.frames) {
if (cachedResponse) {
js = addCacheNotice(js);
}
const df = dataFrameFromJSON(js);
if (!df.refId) {
df.refId = dr.refId;
@ -128,6 +135,28 @@ export function toDataQueryResponse(
return rsp;
}
function isCachedResponse(res: FetchResponse<BackendDataSourceResponse | undefined>): boolean {
const headers = res?.headers;
if (!headers || !headers.get) {
return false;
}
return headers.get('X-Cache') === 'HIT';
}
function addCacheNotice(frame: DataFrameJSON): DataFrameJSON {
return {
...frame,
schema: {
...frame.schema,
fields: [...(frame.schema?.fields ?? [])],
meta: {
...frame.schema?.meta,
notices: [...(frame.schema?.meta?.notices ?? []), cachedResponseNotice],
},
},
};
}
/**
* Data sources using api/ds/query to test data sources can use this function to
* handle errors and convert them to TestingStatus object.