grafana/public/app/features/explore/LogRowContext.tsx
Dominik Prokop a9c94ec93b
Explore: Update the way Loki retrieve log context (#17204)
* Move log's typings into grafana/ui
* Update the way context is retrieved for Loki

Major changes:

1. getLogRowContext expects row to be of LogRowModel type
2. getLogRowContext accepts generic options object, specific to a datasource of interest. limit option has been removed, and now it's a part of Loki's context query options (see below)
3. LogRowContextProvider performs two queries now. Before, it was Loki ds that performed queries in both directions when getLogRowContext.
4. Loki's getLogRowContext accepts options object of a type:

interface LokiContextQueryOptions {
    direction?: 'BACKWARD' | 'FORWARD';
    limit?: number;
}

This will enable querying in either direction independently and also slightly simplifies the way query errors are handled.

LogRowContextProvider maps the results to a Loki specific context types, basically string[][], as raw log lines are displayed in first version.
2019-05-22 23:10:05 +02:00

240 lines
6.1 KiB
TypeScript

import React, { useContext, useRef, useState, useLayoutEffect } from 'react';
import {
ThemeContext,
List,
GrafanaTheme,
selectThemeVariant,
ClickOutsideWrapper,
CustomScrollbar,
DataQueryError,
LogRowModel,
} from '@grafana/ui';
import { css, cx } from 'emotion';
import { LogRowContextRows, HasMoreContextRows, LogRowContextQueryErrors } from './LogRowContextProvider';
import { Alert } from './Error';
interface LogRowContextProps {
row: LogRowModel;
context: LogRowContextRows;
errors?: LogRowContextQueryErrors;
hasMoreContextRows: HasMoreContextRows;
onOutsideClick: () => void;
onLoadMoreContext: () => void;
}
const getLogRowContextStyles = (theme: GrafanaTheme) => {
const gradientTop = selectThemeVariant(
{
light: theme.colors.white,
dark: theme.colors.dark1,
},
theme.type
);
const gradientBottom = selectThemeVariant(
{
light: theme.colors.gray7,
dark: theme.colors.dark2,
},
theme.type
);
const boxShadowColor = selectThemeVariant(
{
light: theme.colors.gray5,
dark: theme.colors.black,
},
theme.type
);
const borderColor = selectThemeVariant(
{
light: theme.colors.gray5,
dark: theme.colors.dark9,
},
theme.type
);
return {
commonStyles: css`
position: absolute;
width: calc(100% + 20px);
left: -10px;
height: 250px;
z-index: 2;
overflow: hidden;
background: ${theme.colors.pageBg};
background: linear-gradient(180deg, ${gradientTop} 0%, ${gradientBottom} 104.25%);
box-shadow: 0px 2px 4px ${boxShadowColor}, 0px 0px 2px ${boxShadowColor};
border: 1px solid ${borderColor};
border-radius: ${theme.border.radius.md};
`,
header: css`
height: 30px;
padding: 0 10px;
display: flex;
align-items: center;
background: ${borderColor};
`,
logs: css`
height: 220px;
padding: 10px;
`,
};
};
interface LogRowContextGroupHeaderProps {
row: LogRowModel;
rows: Array<string | DataQueryError>;
onLoadMoreContext: () => void;
shouldScrollToBottom?: boolean;
canLoadMoreRows?: boolean;
}
interface LogRowContextGroupProps extends LogRowContextGroupHeaderProps {
rows: Array<string | DataQueryError>;
className: string;
error?: string;
}
const LogRowContextGroupHeader: React.FunctionComponent<LogRowContextGroupHeaderProps> = ({
row,
rows,
onLoadMoreContext,
canLoadMoreRows,
}) => {
const theme = useContext(ThemeContext);
const { header } = getLogRowContextStyles(theme);
// Filtering out the original row from the context.
// Loki requires a rowTimestamp+1ns for the following logs to be queried.
// We don't to ns-precision calculations in Loki log row context retrieval, hence the filtering here
// Also see: https://github.com/grafana/loki/issues/597
const logRowsToRender = rows.filter(contextRow => contextRow !== row.raw);
return (
<div className={header}>
<span
className={css`
opacity: 0.6;
`}
>
Found {logRowsToRender.length} rows.
</span>
{(rows.length >= 10 || (rows.length > 10 && rows.length % 10 !== 0)) && canLoadMoreRows && (
<span
className={css`
margin-left: 10px;
&:hover {
text-decoration: underline;
cursor: pointer;
}
`}
onClick={() => onLoadMoreContext()}
>
Load 10 more
</span>
)}
</div>
);
};
const LogRowContextGroup: React.FunctionComponent<LogRowContextGroupProps> = ({
row,
rows,
error,
className,
shouldScrollToBottom,
canLoadMoreRows,
onLoadMoreContext,
}) => {
const theme = useContext(ThemeContext);
const { commonStyles, logs } = getLogRowContextStyles(theme);
const [scrollTop, setScrollTop] = useState(0);
const listContainerRef = useRef<HTMLDivElement>();
useLayoutEffect(() => {
if (shouldScrollToBottom && listContainerRef.current) {
setScrollTop(listContainerRef.current.offsetHeight);
}
});
const headerProps = {
row,
rows,
onLoadMoreContext,
canLoadMoreRows,
};
return (
<div className={cx(className, commonStyles)}>
{/* When displaying "after" context */}
{shouldScrollToBottom && !error && <LogRowContextGroupHeader {...headerProps} />}
<div className={logs}>
<CustomScrollbar autoHide scrollTop={scrollTop}>
<div ref={listContainerRef}>
{!error && (
<List
items={rows}
renderItem={item => {
return (
<div
className={css`
padding: 5px 0;
`}
>
{item}
</div>
);
}}
/>
)}
{error && <Alert message={error} />}
</div>
</CustomScrollbar>
</div>
{/* When displaying "before" context */}
{!shouldScrollToBottom && !error && <LogRowContextGroupHeader {...headerProps} />}
</div>
);
};
export const LogRowContext: React.FunctionComponent<LogRowContextProps> = ({
row,
context,
errors,
onOutsideClick,
onLoadMoreContext,
hasMoreContextRows,
}) => {
return (
<ClickOutsideWrapper onClick={onOutsideClick}>
<div>
{context.after && (
<LogRowContextGroup
rows={context.after}
error={errors && errors.after}
row={row}
className={css`
top: -250px;
`}
shouldScrollToBottom
canLoadMoreRows={hasMoreContextRows.after}
onLoadMoreContext={onLoadMoreContext}
/>
)}
{context.before && (
<LogRowContextGroup
onLoadMoreContext={onLoadMoreContext}
canLoadMoreRows={hasMoreContextRows.before}
row={row}
rows={context.before}
error={errors && errors.before}
className={css`
top: 100%;
`}
/>
)}
</div>
</ClickOutsideWrapper>
);
};