| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- import React, { useContext, useRef, useState, useLayoutEffect } from 'react';
- import {
- ThemeContext,
- List,
- GrafanaTheme,
- selectThemeVariant,
- ClickOutsideWrapper,
- CustomScrollbar,
- DataQueryError,
- } from '@grafana/ui';
- import { css, cx } from 'emotion';
- import { LogRowContextRows, HasMoreContextRows, LogRowContextQueryErrors } from './LogRowContextProvider';
- import { LogRowModel } from 'app/core/logs_model';
- 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>
- );
- };
|