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/explore/Logs/LogsVolumePanelList.tsx

174 lines
5.3 KiB

import { css } from '@emotion/css';
import { flatten, groupBy, mapValues, sortBy } from 'lodash';
import React, { useMemo } from 'react';
import {
AbsoluteTimeRange,
DataFrame,
DataQueryResponse,
EventBus,
GrafanaTheme2,
LoadingState,
SplitOpen,
TimeZone,
} from '@grafana/data';
import { Button, InlineField, Alert, useStyles2 } from '@grafana/ui';
import { mergeLogsVolumeDataFrames, isLogsVolumeLimited, getLogsVolumeMaximumRange } from '../../logs/utils';
import { SupplementaryResultError } from '../SupplementaryResultError';
import { LogsVolumePanel } from './LogsVolumePanel';
import { isTimeoutErrorResponse } from './utils/logsVolumeResponse';
type Props = {
logsVolumeData: DataQueryResponse | undefined;
absoluteRange: AbsoluteTimeRange;
timeZone: TimeZone;
splitOpen: SplitOpen;
width: number;
onUpdateTimeRange: (timeRange: AbsoluteTimeRange) => void;
onLoadLogsVolume: () => void;
onHiddenSeriesChanged: (hiddenSeries: string[]) => void;
eventBus: EventBus;
onClose?(): void;
};
export const LogsVolumePanelList = ({
logsVolumeData,
absoluteRange,
onUpdateTimeRange,
width,
onLoadLogsVolume,
onHiddenSeriesChanged,
eventBus,
splitOpen,
timeZone,
onClose,
}: Props) => {
const {
logVolumes,
maximumValue: allLogsVolumeMaximumValue,
maximumRange: allLogsVolumeMaximumRange,
} = useMemo(() => {
let maximumValue = -Infinity;
const sorted = sortBy(logsVolumeData?.data || [], 'meta.custom.datasourceName');
const grouped = groupBy(sorted, 'meta.custom.datasourceName');
const logVolumes = mapValues(grouped, (value) => {
const mergedData = mergeLogsVolumeDataFrames(value);
maximumValue = Math.max(maximumValue, mergedData.maximum);
return mergedData.dataFrames;
});
const maximumRange = getLogsVolumeMaximumRange(flatten(Object.values(logVolumes)));
return {
maximumValue,
maximumRange,
logVolumes,
};
}, [logsVolumeData]);
const styles = useStyles2(getStyles);
const numberOfLogVolumes = Object.keys(logVolumes).length;
const containsZoomed = Object.values(logVolumes).some((data: DataFrame[]) => {
const zoomRatio = logsLevelZoomRatio(data, absoluteRange);
return !isLogsVolumeLimited(data) && zoomRatio && zoomRatio < 1;
});
const timeoutError = isTimeoutErrorResponse(logsVolumeData);
const visibleRange = {
from: Math.max(absoluteRange.from, allLogsVolumeMaximumRange.from),
to: Math.min(absoluteRange.to, allLogsVolumeMaximumRange.to),
};
if (logsVolumeData?.state === LoadingState.Loading) {
return <span>Loading...</span>;
} else if (timeoutError) {
return (
<SupplementaryResultError
title="The logs volume query has timed out"
// Using info to avoid users thinking that the actual query has failed.
severity="info"
suggestedAction="Retry"
onSuggestedAction={onLoadLogsVolume}
onRemove={onClose}
/>
);
} else if (logsVolumeData?.error !== undefined) {
return <SupplementaryResultError error={logsVolumeData.error} title="Failed to load log volume for this query" />;
}
if (numberOfLogVolumes === 0) {
return (
<div className={styles.alertContainer}>
<Alert severity="info" title="No logs volume available">
No volume information available for the current queries and time range.
</Alert>
</div>
);
}
return (
<div className={styles.listContainer}>
{Object.keys(logVolumes).map((name, index) => {
const logsVolumeData = { data: logVolumes[name] };
return (
<LogsVolumePanel
key={index}
absoluteRange={visibleRange}
allLogsVolumeMaximum={allLogsVolumeMaximumValue}
width={width}
logsVolumeData={logsVolumeData}
onUpdateTimeRange={onUpdateTimeRange}
timeZone={timeZone}
splitOpen={splitOpen}
onLoadLogsVolume={onLoadLogsVolume}
// TODO: Support filtering level from multiple log levels
onHiddenSeriesChanged={numberOfLogVolumes > 1 ? () => {} : onHiddenSeriesChanged}
eventBus={eventBus}
/>
);
})}
{containsZoomed && (
<div className={styles.extraInfoContainer}>
<InlineField label="Reload log volume" transparent>
<Button size="xs" icon="sync" variant="secondary" onClick={onLoadLogsVolume} id="reload-volume" />
</InlineField>
</div>
)}
</div>
);
};
const getStyles = (theme: GrafanaTheme2) => {
return {
listContainer: css`
padding-top: 10px;
`,
extraInfoContainer: css`
display: flex;
justify-content: end;
position: absolute;
right: 5px;
top: 5px;
`,
oldInfoText: css`
font-size: ${theme.typography.bodySmall.fontSize};
color: ${theme.colors.text.secondary};
`,
alertContainer: css`
width: 50%;
min-width: ${theme.breakpoints.values.sm}px;
margin: 0 auto;
`,
};
};
function logsLevelZoomRatio(
logsVolumeData: DataFrame[] | undefined,
selectedTimeRange: AbsoluteTimeRange
): number | undefined {
const dataRange = logsVolumeData && logsVolumeData[0] && logsVolumeData[0].meta?.custom?.absoluteRange;
return dataRange ? (selectedTimeRange.from - selectedTimeRange.to) / (dataRange.from - dataRange.to) : undefined;
}