mirror of
https://github.com/grafana/grafana.git
synced 2025-02-13 00:55:47 -06:00
* add implementation of `logRowsToReadableJson` * add test for logRowsToReadableJson * add json, txt download buttons * changed downloadmenu to `Menu` * set closed state when menu closes * removed unused css * removed unused imports * remove isOpen state * remove unused import * add tests * remove untouched file
171 lines
4.9 KiB
TypeScript
171 lines
4.9 KiB
TypeScript
import { css } from '@emotion/css';
|
|
import saveAs from 'file-saver';
|
|
import React from 'react';
|
|
|
|
import { LogsDedupStrategy, LogsMetaItem, LogsMetaKind, LogRowModel, CoreApp, dateTimeFormat } from '@grafana/data';
|
|
import { reportInteraction } from '@grafana/runtime';
|
|
import { Button, Dropdown, Menu, ToolbarButton, Tooltip, useStyles2 } from '@grafana/ui';
|
|
|
|
import { downloadLogsModelAsTxt } from '../inspector/utils/download';
|
|
import { LogLabels } from '../logs/components/LogLabels';
|
|
import { MAX_CHARACTERS } from '../logs/components/LogRowMessage';
|
|
import { logRowsToReadableJson } from '../logs/utils';
|
|
|
|
import { MetaInfoText, MetaItemProps } from './MetaInfoText';
|
|
|
|
const getStyles = () => ({
|
|
metaContainer: css`
|
|
flex: 1;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
`,
|
|
});
|
|
|
|
export type Props = {
|
|
meta: LogsMetaItem[];
|
|
dedupStrategy: LogsDedupStrategy;
|
|
dedupCount: number;
|
|
displayedFields: string[];
|
|
hasUnescapedContent: boolean;
|
|
forceEscape: boolean;
|
|
logRows: LogRowModel[];
|
|
onEscapeNewlines: () => void;
|
|
clearDetectedFields: () => void;
|
|
};
|
|
|
|
enum DownloadFormat {
|
|
Text = 'text',
|
|
Json = 'json',
|
|
}
|
|
|
|
export const LogsMetaRow = React.memo(
|
|
({
|
|
meta,
|
|
dedupStrategy,
|
|
dedupCount,
|
|
displayedFields,
|
|
clearDetectedFields,
|
|
hasUnescapedContent,
|
|
forceEscape,
|
|
onEscapeNewlines,
|
|
logRows,
|
|
}: Props) => {
|
|
const style = useStyles2(getStyles);
|
|
|
|
const downloadLogs = (format: DownloadFormat) => {
|
|
reportInteraction('grafana_logs_download_logs_clicked', {
|
|
app: CoreApp.Explore,
|
|
format,
|
|
area: 'logs-meta-row',
|
|
});
|
|
|
|
switch (format) {
|
|
case DownloadFormat.Text:
|
|
downloadLogsModelAsTxt({ meta, rows: logRows }, 'Explore');
|
|
break;
|
|
case DownloadFormat.Json:
|
|
const jsonLogs = logRowsToReadableJson(logRows);
|
|
const blob = new Blob([JSON.stringify(jsonLogs)], {
|
|
type: 'application/json;charset=utf-8',
|
|
});
|
|
|
|
const fileName = `Explore-logs-${dateTimeFormat(new Date())}.json`;
|
|
saveAs(blob, fileName);
|
|
break;
|
|
}
|
|
};
|
|
|
|
const logsMetaItem: Array<LogsMetaItem | MetaItemProps> = [...meta];
|
|
|
|
// Add deduplication info
|
|
if (dedupStrategy !== LogsDedupStrategy.none) {
|
|
logsMetaItem.push({
|
|
label: 'Dedup count',
|
|
value: dedupCount,
|
|
kind: LogsMetaKind.Number,
|
|
});
|
|
}
|
|
// Add info about limit for highlighting
|
|
if (logRows.some((r) => r.entry.length > MAX_CHARACTERS)) {
|
|
logsMetaItem.push({
|
|
label: 'Info',
|
|
value: 'Logs with more than 100,000 characters could not be parsed and highlighted',
|
|
kind: LogsMetaKind.String,
|
|
});
|
|
}
|
|
|
|
// Add detected fields info
|
|
if (displayedFields?.length > 0) {
|
|
logsMetaItem.push(
|
|
{
|
|
label: 'Showing only selected fields',
|
|
value: renderMetaItem(displayedFields, LogsMetaKind.LabelsMap),
|
|
},
|
|
{
|
|
label: '',
|
|
value: (
|
|
<Button variant="secondary" size="sm" onClick={clearDetectedFields}>
|
|
Show original line
|
|
</Button>
|
|
),
|
|
}
|
|
);
|
|
}
|
|
|
|
// Add unescaped content info
|
|
if (hasUnescapedContent) {
|
|
logsMetaItem.push({
|
|
label: 'Your logs might have incorrectly escaped content',
|
|
value: (
|
|
<Tooltip
|
|
content="Fix incorrectly escaped newline and tab sequences in log lines. Manually review the results to confirm that the replacements are correct."
|
|
placement="right"
|
|
>
|
|
<Button variant="secondary" size="sm" onClick={onEscapeNewlines}>
|
|
{forceEscape ? 'Remove escaping' : 'Escape newlines'}
|
|
</Button>
|
|
</Tooltip>
|
|
),
|
|
});
|
|
}
|
|
const downloadMenu = (
|
|
<Menu>
|
|
<Menu.Item label="txt" onClick={() => downloadLogs(DownloadFormat.Text)} />
|
|
<Menu.Item label="json" onClick={() => downloadLogs(DownloadFormat.Json)} />
|
|
</Menu>
|
|
);
|
|
return (
|
|
<>
|
|
{logsMetaItem && (
|
|
<div className={style.metaContainer}>
|
|
<MetaInfoText
|
|
metaItems={logsMetaItem.map((item) => {
|
|
return {
|
|
label: item.label,
|
|
value: 'kind' in item ? renderMetaItem(item.value, item.kind) : item.value,
|
|
};
|
|
})}
|
|
/>
|
|
<Dropdown overlay={downloadMenu}>
|
|
<ToolbarButton isOpen={false} variant="default" icon="download-alt">
|
|
Download
|
|
</ToolbarButton>
|
|
</Dropdown>
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
);
|
|
|
|
LogsMetaRow.displayName = 'LogsMetaRow';
|
|
|
|
function renderMetaItem(value: any, kind: LogsMetaKind) {
|
|
if (kind === LogsMetaKind.LabelsMap) {
|
|
return <LogLabels labels={value} />;
|
|
} else if (kind === LogsMetaKind.Error) {
|
|
return <span className="logs-meta-item__error">{value}</span>;
|
|
}
|
|
return value;
|
|
}
|