LogRowContext.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import React, { useContext, useRef, useState, useLayoutEffect } from 'react';
  2. import {
  3. ThemeContext,
  4. List,
  5. GrafanaTheme,
  6. selectThemeVariant,
  7. ClickOutsideWrapper,
  8. CustomScrollbar,
  9. DataQueryError,
  10. } from '@grafana/ui';
  11. import { LogRowModel } from '@grafana/data';
  12. import { css, cx } from 'emotion';
  13. import { LogRowContextRows, HasMoreContextRows, LogRowContextQueryErrors } from './LogRowContextProvider';
  14. import { Alert } from './Error';
  15. interface LogRowContextProps {
  16. row: LogRowModel;
  17. context: LogRowContextRows;
  18. errors?: LogRowContextQueryErrors;
  19. hasMoreContextRows: HasMoreContextRows;
  20. onOutsideClick: () => void;
  21. onLoadMoreContext: () => void;
  22. }
  23. const getLogRowContextStyles = (theme: GrafanaTheme) => {
  24. const gradientTop = selectThemeVariant(
  25. {
  26. light: theme.colors.white,
  27. dark: theme.colors.dark1,
  28. },
  29. theme.type
  30. );
  31. const gradientBottom = selectThemeVariant(
  32. {
  33. light: theme.colors.gray7,
  34. dark: theme.colors.dark2,
  35. },
  36. theme.type
  37. );
  38. const boxShadowColor = selectThemeVariant(
  39. {
  40. light: theme.colors.gray5,
  41. dark: theme.colors.black,
  42. },
  43. theme.type
  44. );
  45. const borderColor = selectThemeVariant(
  46. {
  47. light: theme.colors.gray5,
  48. dark: theme.colors.dark9,
  49. },
  50. theme.type
  51. );
  52. return {
  53. commonStyles: css`
  54. position: absolute;
  55. width: calc(100% + 20px);
  56. left: -10px;
  57. height: 250px;
  58. z-index: 2;
  59. overflow: hidden;
  60. background: ${theme.colors.pageBg};
  61. background: linear-gradient(180deg, ${gradientTop} 0%, ${gradientBottom} 104.25%);
  62. box-shadow: 0px 2px 4px ${boxShadowColor}, 0px 0px 2px ${boxShadowColor};
  63. border: 1px solid ${borderColor};
  64. border-radius: ${theme.border.radius.md};
  65. `,
  66. header: css`
  67. height: 30px;
  68. padding: 0 10px;
  69. display: flex;
  70. align-items: center;
  71. background: ${borderColor};
  72. `,
  73. logs: css`
  74. height: 220px;
  75. padding: 10px;
  76. `,
  77. };
  78. };
  79. interface LogRowContextGroupHeaderProps {
  80. row: LogRowModel;
  81. rows: Array<string | DataQueryError>;
  82. onLoadMoreContext: () => void;
  83. shouldScrollToBottom?: boolean;
  84. canLoadMoreRows?: boolean;
  85. }
  86. interface LogRowContextGroupProps extends LogRowContextGroupHeaderProps {
  87. rows: Array<string | DataQueryError>;
  88. className: string;
  89. error?: string;
  90. }
  91. const LogRowContextGroupHeader: React.FunctionComponent<LogRowContextGroupHeaderProps> = ({
  92. row,
  93. rows,
  94. onLoadMoreContext,
  95. canLoadMoreRows,
  96. }) => {
  97. const theme = useContext(ThemeContext);
  98. const { header } = getLogRowContextStyles(theme);
  99. return (
  100. <div className={header}>
  101. <span
  102. className={css`
  103. opacity: 0.6;
  104. `}
  105. >
  106. Found {rows.length} rows.
  107. </span>
  108. {(rows.length >= 10 || (rows.length > 10 && rows.length % 10 !== 0)) && canLoadMoreRows && (
  109. <span
  110. className={css`
  111. margin-left: 10px;
  112. &:hover {
  113. text-decoration: underline;
  114. cursor: pointer;
  115. }
  116. `}
  117. onClick={() => onLoadMoreContext()}
  118. >
  119. Load 10 more
  120. </span>
  121. )}
  122. </div>
  123. );
  124. };
  125. const LogRowContextGroup: React.FunctionComponent<LogRowContextGroupProps> = ({
  126. row,
  127. rows,
  128. error,
  129. className,
  130. shouldScrollToBottom,
  131. canLoadMoreRows,
  132. onLoadMoreContext,
  133. }) => {
  134. const theme = useContext(ThemeContext);
  135. const { commonStyles, logs } = getLogRowContextStyles(theme);
  136. const [scrollTop, setScrollTop] = useState(0);
  137. const listContainerRef = useRef<HTMLDivElement>();
  138. useLayoutEffect(() => {
  139. if (shouldScrollToBottom && listContainerRef.current) {
  140. setScrollTop(listContainerRef.current.offsetHeight);
  141. }
  142. });
  143. const headerProps = {
  144. row,
  145. rows,
  146. onLoadMoreContext,
  147. canLoadMoreRows,
  148. };
  149. return (
  150. <div className={cx(className, commonStyles)}>
  151. {/* When displaying "after" context */}
  152. {shouldScrollToBottom && !error && <LogRowContextGroupHeader {...headerProps} />}
  153. <div className={logs}>
  154. <CustomScrollbar autoHide scrollTop={scrollTop}>
  155. <div ref={listContainerRef}>
  156. {!error && (
  157. <List
  158. items={rows}
  159. renderItem={item => {
  160. return (
  161. <div
  162. className={css`
  163. padding: 5px 0;
  164. `}
  165. >
  166. {item}
  167. </div>
  168. );
  169. }}
  170. />
  171. )}
  172. {error && <Alert message={error} />}
  173. </div>
  174. </CustomScrollbar>
  175. </div>
  176. {/* When displaying "before" context */}
  177. {!shouldScrollToBottom && !error && <LogRowContextGroupHeader {...headerProps} />}
  178. </div>
  179. );
  180. };
  181. export const LogRowContext: React.FunctionComponent<LogRowContextProps> = ({
  182. row,
  183. context,
  184. errors,
  185. onOutsideClick,
  186. onLoadMoreContext,
  187. hasMoreContextRows,
  188. }) => {
  189. return (
  190. <ClickOutsideWrapper onClick={onOutsideClick}>
  191. <div>
  192. {context.after && (
  193. <LogRowContextGroup
  194. rows={context.after}
  195. error={errors && errors.after}
  196. row={row}
  197. className={css`
  198. top: -250px;
  199. `}
  200. shouldScrollToBottom
  201. canLoadMoreRows={hasMoreContextRows.after}
  202. onLoadMoreContext={onLoadMoreContext}
  203. />
  204. )}
  205. {context.before && (
  206. <LogRowContextGroup
  207. onLoadMoreContext={onLoadMoreContext}
  208. canLoadMoreRows={hasMoreContextRows.before}
  209. row={row}
  210. rows={context.before}
  211. error={errors && errors.before}
  212. className={css`
  213. top: 100%;
  214. `}
  215. />
  216. )}
  217. </div>
  218. </ClickOutsideWrapper>
  219. );
  220. };