Logs Panel: Add ISO8601 date to log download files (#102932)

* feat: add ISO8601 date to log download files
This commit is contained in:
Galen Kistler
2025-03-27 07:11:16 -05:00
committed by GitHub
parent 3121633eba
commit 684b37556f
6 changed files with 52 additions and 14 deletions
@@ -273,7 +273,7 @@ describe('LogsMetaRow', () => {
{
name: 'time',
type: FieldType.time,
values: ['1970-01-02T00:00:00Z'],
values: [1],
},
{
name: 'message',
@@ -315,6 +315,6 @@ describe('LogsMetaRow', () => {
const blob = (saveAs as unknown as jest.Mock).mock.lastCall[0];
expect(blob.type).toBe('text/csv;charset=utf-8');
const text = await blob.text();
expect(text).toBe(`"time","message bar"\r\n1970-01-02T00:00:00Z,INFO 1`);
expect(text).toBe(`"Date","time","message bar"\r\n1970-01-01T00:00:00.001Z,1,INFO 1`);
});
});
@@ -1,7 +1,7 @@
import { css } from '@emotion/css';
import saveAs from 'file-saver';
import { memo } from 'react';
import { lastValueFrom } from 'rxjs';
import { lastValueFrom, map, Observable } from 'rxjs';
import {
LogsDedupStrategy,
@@ -15,6 +15,9 @@ import {
CustomTransformOperator,
Labels,
DataFrame,
Field,
getTimeField,
dateTime,
} from '@grafana/data';
import { config, reportInteraction } from '@grafana/runtime';
import { Button, Dropdown, Menu, ToolbarButton, Tooltip, useStyles2 } from '@grafana/ui';
@@ -96,15 +99,18 @@ export const LogsMetaRow = memo(
});
dataFrameMap.forEach(async (dataFrame) => {
const transforms: Array<DataTransformerConfig | CustomTransformOperator> = getLogsExtractFields(dataFrame);
transforms.push({
id: 'organize',
options: {
excludeByName: {
['labels']: true,
['labelTypes']: true,
transforms.push(
{
id: 'organize',
options: {
excludeByName: {
['labels']: true,
['labelTypes']: true,
},
},
},
});
addISODateTransformation
);
const transformedDataFrame = await lastValueFrom(transformDataFrame(transforms, [dataFrame]));
downloadDataFrameAsCsv(transformedDataFrame[0], `Explore-logs-${dataFrame.refId}`);
});
@@ -215,3 +221,23 @@ function renderMetaItem(value: string | number | Labels, kind: LogsMetaKind) {
console.error(`Meta type ${typeof value} ${value} not recognized.`);
return <></>;
}
const addISODateTransformation: CustomTransformOperator = () => (source: Observable<DataFrame[]>) => {
return source.pipe(
map((data: DataFrame[]) => {
return data.map((frame: DataFrame) => {
const timeField = getTimeField(frame);
return {
...frame,
fields: [
{
name: 'Date',
values: timeField.timeField?.values.map((v) => dateTime(v).toISOString()),
} as Field,
...frame.fields,
],
};
});
})
);
};
@@ -105,7 +105,7 @@ describe('inspector download', () => {
rows: [{ timeEpochMs: 100, entry: 'testEntry' } as unknown as LogRowModel],
},
'test',
`testLabel: 1\nsecondTestLabel: 2\n\n\n100\ttestEntry\n`,
`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);
@@ -4,6 +4,7 @@ import {
CSVConfig,
DataFrame,
DataTransformerID,
dateTime,
dateTimeFormat,
LogsModel,
MutableDataFrame,
@@ -30,7 +31,7 @@ export function downloadLogsModelAsTxt(logsModel: Pick<LogsModel, 'meta' | 'rows
textToDownload = textToDownload + '\n\n';
logsModel.rows.forEach((row) => {
const newRow = row.timeEpochMs + '\t' + row.entry + '\n';
const newRow = row.timeEpochMs + '\t' + dateTime(row.timeEpochMs).toISOString() + '\t' + row.entry + '\n';
textToDownload = textToDownload + newRow;
});
+11 -2
View File
@@ -356,13 +356,22 @@ describe('logRowsToReadableJson', () => {
it('should format a single row', () => {
const result = logRowsToReadableJson([testRow]);
expect(result).toEqual([{ line: 'test entry', timestamp: '123456789', fields: { foo: 'bar' } }]);
expect(result).toEqual([
{ date: '1970-01-01T00:00:00.010Z', line: 'test entry', timestamp: '123456789', fields: { foo: 'bar' } },
]);
});
it('should format a df field row', () => {
const result = logRowsToReadableJson([testRow2]);
expect(result).toEqual([{ line: 'test entry', timestamp: '123456789', fields: { foo: 'bar', foo2: 'bar2' } }]);
expect(result).toEqual([
{
date: '1970-01-01T00:00:00.010Z',
line: 'test entry',
timestamp: '123456789',
fields: { foo: 'bar', foo2: 'bar2' },
},
]);
});
});
+2
View File
@@ -19,6 +19,7 @@ import {
getDefaultTimeRange,
locationUtil,
urlUtil,
dateTime,
} from '@grafana/data';
import { getConfig } from 'app/core/config';
@@ -176,6 +177,7 @@ export function logRowsToReadableJson(logs: LogRowModel[]) {
return {
line: log.entry,
timestamp: log.timeEpochNs,
date: dateTime(log.timeEpochMs).toISOString(),
fields: {
...fields,
...log.labels,