mirror of https://github.com/grafana/grafana.git
182 lines
6.6 KiB
TypeScript
182 lines
6.6 KiB
TypeScript
import saveAs from 'file-saver';
|
||
|
||
import { dataFrameFromJSON, DataFrameJSON, dateTimeFormat, FieldType, LogRowModel, LogsMetaKind } from '@grafana/data';
|
||
import { createLogRow } from 'app/features/logs/components/mocks/logRow';
|
||
|
||
import { downloadAsJson, downloadDataFrameAsCsv, downloadLogsModelAsTxt } from './download';
|
||
|
||
jest.mock('file-saver', () => jest.fn());
|
||
|
||
describe('inspector download', () => {
|
||
beforeEach(() => {
|
||
jest.clearAllMocks();
|
||
jest.useFakeTimers({ now: new Date(1400000000000) });
|
||
});
|
||
afterEach(() => {
|
||
jest.useRealTimers();
|
||
});
|
||
|
||
describe('downloadDataFrameAsCsv', () => {
|
||
const json: DataFrameJSON = {
|
||
schema: {
|
||
fields: [
|
||
{ name: 'time', type: FieldType.time },
|
||
{ name: 'name', type: FieldType.string },
|
||
{ name: 'value', type: FieldType.number },
|
||
],
|
||
},
|
||
data: {
|
||
values: [[100], ['Åäö中文العربية'], [1]],
|
||
},
|
||
};
|
||
|
||
it.each([[dataFrameFromJSON(json), 'test', '"time","name","value"\r\n100,Åäö中文العربية,1']])(
|
||
'should, when logsModel is %s and title is %s, resolve in %s',
|
||
async (dataFrame, title, expected) => {
|
||
downloadDataFrameAsCsv(dataFrame, title);
|
||
const call = jest.mocked(saveAs).mock.calls[0];
|
||
const blob = call[0];
|
||
const filename = call[1];
|
||
const text = typeof blob === 'string' ? blob : await blob.text();
|
||
|
||
// By default the BOM character should not be included
|
||
expect(blob instanceof Blob ? await getBomType(blob) : undefined).toBeUndefined();
|
||
expect(text).toEqual(expected);
|
||
expect(filename).toEqual(`${title}-data-${dateTimeFormat(1400000000000)}.csv`);
|
||
}
|
||
);
|
||
|
||
it('should use \t as the delimiter and the file should be utf16le if excelCompatibilityMode is true', async () => {
|
||
downloadDataFrameAsCsv(dataFrameFromJSON(json), 'test', undefined, undefined, true);
|
||
|
||
const call = jest.mocked(saveAs).mock.calls[0];
|
||
const blob = call[0];
|
||
const filename = call[1];
|
||
const text = typeof blob === 'string' ? blob : await blob.text();
|
||
|
||
if (blob instanceof Blob) {
|
||
expect(await getBomType(blob)).toBe('utf-16le');
|
||
expect(blob.type).toBe('text/csv;charset=utf-16le');
|
||
}
|
||
expect(text).toEqual('"time"\t"name"\t"value"\r\n100\tÅäö中文العربية\t1');
|
||
expect(filename).toEqual(`test-data-${dateTimeFormat(1400000000000)}.csv`);
|
||
expect.assertions(4);
|
||
});
|
||
});
|
||
|
||
describe('downloadAsJson', () => {
|
||
it.each([
|
||
['foo', 'test', '"foo"'],
|
||
[1, 'test', '1'],
|
||
[{ foo: 'bar' }, 'test', '{"foo":"bar"}'],
|
||
])('should, when logsModel is %s and title is %s, resolve in %s', async (logsModel, title, expected) => {
|
||
downloadAsJson(logsModel, title);
|
||
const call = jest.mocked(saveAs).mock.calls[0];
|
||
const blob = call[0];
|
||
const filename = call[1];
|
||
const text = typeof blob === 'string' ? blob : await blob.text();
|
||
|
||
expect(text).toEqual(expected);
|
||
expect(filename).toEqual(`${title}-${dateTimeFormat(1400000000000)}.json`);
|
||
});
|
||
});
|
||
|
||
describe('downloadLogsModelAsTxt', () => {
|
||
it.each([
|
||
[{ meta: [], rows: [] }, 'test', '\n\n'],
|
||
[
|
||
{ meta: [{ label: 'testLabel', value: 'testValue', kind: LogsMetaKind.String }], rows: [] },
|
||
'test',
|
||
'testLabel: "testValue"\n\n\n',
|
||
],
|
||
[{ meta: [{ label: 'testLabel', value: 1, kind: LogsMetaKind.Number }], rows: [] }, 'test', 'testLabel: 1\n\n\n'],
|
||
[
|
||
{
|
||
meta: [
|
||
{ label: 'testLabel', value: 1, kind: LogsMetaKind.String },
|
||
{ label: 'secondTestLabel', value: 2, kind: LogsMetaKind.String },
|
||
],
|
||
rows: [],
|
||
},
|
||
'test',
|
||
'testLabel: 1\nsecondTestLabel: 2\n\n\n',
|
||
],
|
||
[
|
||
{
|
||
meta: [
|
||
{ label: 'testLabel', value: 1, kind: LogsMetaKind.String },
|
||
{ label: 'secondTestLabel', value: 2, kind: LogsMetaKind.String },
|
||
],
|
||
rows: [{ timeEpochMs: 100, entry: 'testEntry' } as unknown as LogRowModel],
|
||
},
|
||
'test',
|
||
`testLabel: 1\nsecondTestLabel: 2\n\n\n100\t1970-01-01T00:00:00.100Z\ttestEntry\n`,
|
||
],
|
||
])('should, when logsModel is %s and title is %s, resolve in %s', async (logsModel, title, expected) => {
|
||
downloadLogsModelAsTxt(logsModel, title);
|
||
const call = jest.mocked(saveAs).mock.calls[0];
|
||
const blob = call[0];
|
||
const filename = call[1];
|
||
const text = typeof blob === 'string' ? blob : await blob.text();
|
||
|
||
expect(text).toEqual(expected);
|
||
expect(filename).toEqual(`${title}-logs-${dateTimeFormat(1400000000000)}.txt`);
|
||
});
|
||
|
||
it('should, when title is empty, resolve in %s', async () => {
|
||
downloadLogsModelAsTxt({ meta: [], rows: [] });
|
||
const call = jest.mocked(saveAs).mock.calls[0];
|
||
const filename = call[1];
|
||
expect(filename).toEqual(`Logs-${dateTimeFormat(1400000000000)}.txt`);
|
||
});
|
||
|
||
it('should, when title is empty, resolve in %s', async () => {
|
||
downloadLogsModelAsTxt({ meta: [], rows: [] });
|
||
const call = jest.mocked(saveAs).mock.calls[0];
|
||
const filename = call[1];
|
||
expect(filename).toEqual(`Logs-${dateTimeFormat(1400000000000)}.txt`);
|
||
});
|
||
|
||
it('should should download selected fields', async () => {
|
||
const logsModel = {
|
||
meta: [],
|
||
rows: [
|
||
createLogRow({ timeEpochMs: 100, entry: 'testEntry', labels: { label: 'value', otherLabel: 'other value' } }),
|
||
],
|
||
};
|
||
downloadLogsModelAsTxt(logsModel, undefined, ['label', 'otherLabel']);
|
||
const call = jest.mocked(saveAs).mock.calls[0];
|
||
const blob = call[0];
|
||
const text = typeof blob === 'string' ? blob : await blob.text();
|
||
|
||
expect(text).toContain('value other value');
|
||
});
|
||
});
|
||
});
|
||
|
||
async function getBomType(blob: Blob): Promise<'utf-8' | 'utf-16le' | undefined> {
|
||
const reader = new FileReader();
|
||
return new Promise((resolve, reject) => {
|
||
reader.onload = (event: ProgressEvent<FileReader>) => {
|
||
if (event.target?.result instanceof ArrayBuffer) {
|
||
const arr = new Uint8Array(event.target.result);
|
||
// UTF-8: EF BB BF
|
||
if (arr.length >= 3 && arr[0] === 0xef && arr[1] === 0xbb && arr[2] === 0xbf) {
|
||
resolve('utf-8');
|
||
return;
|
||
}
|
||
// UTF-16 LE: FF FE
|
||
if (arr.length >= 2 && arr[0] === 0xff && arr[1] === 0xfe) {
|
||
resolve('utf-16le');
|
||
return;
|
||
}
|
||
resolve(undefined);
|
||
} else {
|
||
reject(new Error('Unexpected FileReader result type'));
|
||
}
|
||
};
|
||
reader.onerror = reject;
|
||
reader.readAsArrayBuffer(blob.slice(0, 3)); // Read first 3 bytes (covers UTF-8 and UTF-16LE)
|
||
});
|
||
}
|