The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/features/logs/components/panel/virtualization.test.ts

277 lines
8.7 KiB

import { createTheme, LogsSortOrder } from '@grafana/data';
import { LOG_LINE_BODY_FIELD_NAME } from '../LogDetailsBody';
import { createLogLine } from '../__mocks__/logRow';
import { LogListModel, PreProcessOptions } from './processing';
import { LogLineVirtualization, getLogLineSize, DisplayOptions } from './virtualization';
describe('Virtualization', () => {
let log: LogListModel, container: HTMLDivElement;
let virtualization = new LogLineVirtualization(createTheme(), 'default');
const PADDING_BOTTOM = 6;
const LINE_HEIGHT = virtualization.getLineHeight();
const SINGLE_LINE_HEIGHT = LINE_HEIGHT + PADDING_BOTTOM;
const TWO_LINES_HEIGHT = 2 * LINE_HEIGHT + PADDING_BOTTOM;
const THREE_LINES_HEIGHT = 3 * LINE_HEIGHT + PADDING_BOTTOM;
let LETTER_WIDTH: number;
let CONTAINER_SIZE = 200;
let TWO_LINES_OF_CHARACTERS: number;
const defaultOptions: DisplayOptions = {
wrap: false,
showTime: false,
showDuplicates: false,
hasLogsWithErrors: false,
hasSampledLogs: false,
};
const preProcessOptions: PreProcessOptions = {
escape: false,
order: LogsSortOrder.Descending,
timeZone: 'browser',
virtualization,
wrapLogMessage: true,
};
beforeEach(() => {
log = createLogLine({ labels: { place: 'luna' }, entry: `log message 1` }, preProcessOptions);
container = document.createElement('div');
jest.spyOn(container, 'clientWidth', 'get').mockReturnValue(CONTAINER_SIZE);
LETTER_WIDTH = virtualization.measureTextWidth('e');
TWO_LINES_OF_CHARACTERS = (CONTAINER_SIZE / LETTER_WIDTH) * 1.5;
});
describe('getLogLineSize', () => {
test('Returns the a single line if the display mode is unwrapped', () => {
const size = getLogLineSize(virtualization, [log], container, [], { ...defaultOptions, showTime: true }, 0);
expect(size).toBe(SINGLE_LINE_HEIGHT);
});
test('Returns the a single line if the line is not loaded yet', () => {
const logs = [log];
const size = getLogLineSize(
virtualization,
logs,
container,
[],
{ ...defaultOptions, wrap: true, showTime: true },
logs.length + 1
);
expect(size).toBe(SINGLE_LINE_HEIGHT);
});
test('Returns the size of a truncated long line', () => {
// Very small container
log.collapsed = true;
jest.spyOn(container, 'clientWidth', 'get').mockReturnValue(10);
const size = getLogLineSize(
virtualization,
[log],
container,
[],
{ ...defaultOptions, wrap: true, showTime: true },
0
);
expect(size).toBe((virtualization.getTruncationLineCount() + 1) * LINE_HEIGHT);
});
test.each([true, false])('Measures a log line with controls %s and displayed time %s', (showTime: boolean) => {
const size = getLogLineSize(virtualization, [log], container, [], { ...defaultOptions, wrap: true, showTime }, 0);
expect(size).toBe(SINGLE_LINE_HEIGHT);
});
test('Measures a multi-line log line with no displayed time', () => {
log = createLogLine(
{
labels: { place: 'luna' },
entry: new Array(TWO_LINES_OF_CHARACTERS).fill('e').join(''),
logLevel: undefined,
},
preProcessOptions
);
const size = getLogLineSize(virtualization, [log], container, [], { ...defaultOptions, wrap: true }, 0);
expect(size).toBe(TWO_LINES_HEIGHT);
});
test('Measures a multi-line log line with level, controls, and displayed time', () => {
log = createLogLine(
{ labels: { place: 'luna' }, entry: new Array(TWO_LINES_OF_CHARACTERS).fill('e').join('') },
preProcessOptions
);
const size = getLogLineSize(
virtualization,
[log],
container,
[],
{ ...defaultOptions, wrap: true, showTime: true },
0
);
// Two lines for the log and one extra for level and time
expect(size).toBe(THREE_LINES_HEIGHT);
});
test('Measures a multi-line log line with displayed fields', () => {
log = createLogLine(
{
labels: { place: 'very very long value for the displayed field that causes a new line' },
entry: new Array(TWO_LINES_OF_CHARACTERS).fill('e').join(''),
logLevel: undefined,
},
preProcessOptions
);
const size = getLogLineSize(
virtualization,
[log],
container,
['place', LOG_LINE_BODY_FIELD_NAME],
{ ...defaultOptions, wrap: true },
0
);
// Two lines for the log and one extra for the displayed fields
expect(size).toBe(THREE_LINES_HEIGHT);
});
test('Measures displayed fields in a log line with level, controls, and displayed time', () => {
log = createLogLine(
{ labels: { place: 'luna' }, entry: new Array(TWO_LINES_OF_CHARACTERS).fill('e').join('') },
preProcessOptions
);
const size = getLogLineSize(
virtualization,
[log],
container,
['place'],
{ ...defaultOptions, wrap: true, showTime: true },
0
);
// Only renders a short displayed field, so a single line
expect(size).toBe(SINGLE_LINE_HEIGHT);
});
test('Measures a multi-line log line with duplicates', () => {
log = createLogLine(
{ labels: { place: 'luna' }, entry: new Array(TWO_LINES_OF_CHARACTERS).fill('e').join('') },
preProcessOptions
);
log.duplicates = 1;
const size = getLogLineSize(
virtualization,
[log],
container,
[],
{ ...defaultOptions, wrap: true, showDuplicates: true },
0
);
// Two lines for the log and one extra for duplicates
expect(size).toBe(THREE_LINES_HEIGHT);
});
test('Measures a multi-line log line with errors', () => {
log = createLogLine(
{ labels: { place: 'luna' }, entry: new Array(TWO_LINES_OF_CHARACTERS).fill('e').join('') },
preProcessOptions
);
const size = getLogLineSize(
virtualization,
[log],
container,
[],
{ ...defaultOptions, wrap: true, hasLogsWithErrors: true },
0
);
// Two lines for the log and one extra for the error icon
expect(size).toBe(THREE_LINES_HEIGHT);
});
test('Measures a multi-line sampled log line', () => {
log = createLogLine(
{ labels: { place: 'luna' }, entry: new Array(TWO_LINES_OF_CHARACTERS).fill('e').join('') },
preProcessOptions
);
const size = getLogLineSize(
virtualization,
[log],
container,
[],
{ ...defaultOptions, wrap: true, hasSampledLogs: true },
0
);
// Two lines for the log and one extra for the sampled icon
expect(size).toBe(THREE_LINES_HEIGHT);
});
test('Adds an extra line for the expand/collapse controls if present', () => {
jest.spyOn(log, 'updateCollapsedState').mockImplementation(() => undefined);
log.collapsed = false;
const size = getLogLineSize(virtualization, [log], container, [], { ...defaultOptions, wrap: true }, 0);
expect(size).toBe(TWO_LINES_HEIGHT);
});
});
describe('calculateFieldDimensions', () => {
test('Measures displayed fields including the log line body', () => {
expect(virtualization.calculateFieldDimensions([log], ['place', LOG_LINE_BODY_FIELD_NAME])).toEqual([
{
field: 'timestamp',
width: 23,
},
{
field: 'level',
width: 4,
},
{
field: 'place',
width: 4,
},
{
field: '___LOG_LINE_BODY___',
width: 13,
},
]);
});
});
describe('With small font size', () => {
const virtualization = new LogLineVirtualization(createTheme(), 'small');
beforeEach(() => {
LETTER_WIDTH = virtualization.measureTextWidth('e');
TWO_LINES_OF_CHARACTERS = (CONTAINER_SIZE / LETTER_WIDTH) * 1.5;
});
test('Measures a multi-line log line with displayed fields', () => {
const SMALL_LINE_HEIGHT = virtualization.getLineHeight();
const SMALL_THREE_LINES_HEIGHT = 3 * SMALL_LINE_HEIGHT + PADDING_BOTTOM;
log = createLogLine(
{
labels: { place: 'very very long value for the displayed field that causes a new line' },
entry: new Array(TWO_LINES_OF_CHARACTERS).fill('e').join(''),
logLevel: undefined,
},
preProcessOptions
);
const size = getLogLineSize(
virtualization,
[log],
container,
['place', LOG_LINE_BODY_FIELD_NAME],
{ ...defaultOptions, wrap: true },
0
);
// Two lines for the log and one extra for the displayed fields
expect(size).toBe(SMALL_THREE_LINES_HEIGHT);
});
});
});