LogRowContextProvider.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import { DataQueryResponse, DataQueryError, LogRowModel } from '@grafana/ui';
  2. import { useState, useEffect } from 'react';
  3. import flatten from 'lodash/flatten';
  4. import useAsync from 'react-use/lib/useAsync';
  5. export interface LogRowContextRows {
  6. before?: string[];
  7. after?: string[];
  8. }
  9. export interface LogRowContextQueryErrors {
  10. before?: string;
  11. after?: string;
  12. }
  13. export interface HasMoreContextRows {
  14. before: boolean;
  15. after: boolean;
  16. }
  17. interface LogRowContextProviderProps {
  18. row: LogRowModel;
  19. getRowContext: (row: LogRowModel, options?: any) => Promise<DataQueryResponse>;
  20. children: (props: {
  21. result: LogRowContextRows;
  22. errors: LogRowContextQueryErrors;
  23. hasMoreContextRows: HasMoreContextRows;
  24. updateLimit: () => void;
  25. }) => JSX.Element;
  26. }
  27. export const getRowContexts = async (
  28. getRowContext: (row: LogRowModel, options?: any) => Promise<DataQueryResponse>,
  29. row: LogRowModel,
  30. limit: number
  31. ) => {
  32. const promises = [
  33. getRowContext(row, {
  34. limit,
  35. }),
  36. getRowContext(row, {
  37. limit: limit + 1, // Lets add one more to the limit as we're filtering out one row see comment below
  38. direction: 'FORWARD',
  39. }),
  40. ];
  41. const results: Array<DataQueryResponse | DataQueryError> = await Promise.all(promises.map(p => p.catch(e => e)));
  42. return {
  43. data: results.map((result, index) => {
  44. const dataResult: DataQueryResponse = result as DataQueryResponse;
  45. if (!dataResult.data) {
  46. return [];
  47. }
  48. // We need to filter out the row we're basing our search from because of how start/end params work in Loki API
  49. // see https://github.com/grafana/loki/issues/597#issuecomment-506408980
  50. // the alternative to create our own add 1 nanosecond method to the a timestamp string would be quite complex
  51. return dataResult.data.map(series => {
  52. const filteredRows = series.rows.filter((r: any) => r[0] !== row.timestamp);
  53. return filteredRows.map((row: any) => row[1]);
  54. });
  55. }),
  56. errors: results.map(result => {
  57. const errorResult: DataQueryError = result as DataQueryError;
  58. if (!errorResult.message) {
  59. return null;
  60. }
  61. return errorResult.message;
  62. }),
  63. };
  64. };
  65. export const LogRowContextProvider: React.FunctionComponent<LogRowContextProviderProps> = ({
  66. getRowContext,
  67. row,
  68. children,
  69. }) => {
  70. // React Hook that creates a number state value called limit to component state and a setter function called setLimit
  71. // The intial value for limit is 10
  72. // Used for the number of rows to retrieve from backend from a specific point in time
  73. const [limit, setLimit] = useState(10);
  74. // React Hook that creates an object state value called result to component state and a setter function called setResult
  75. // The intial value for result is null
  76. // Used for sorting the response from backend
  77. const [result, setResult] = useState<{
  78. data: string[][];
  79. errors: string[];
  80. }>(null);
  81. // React Hook that creates an object state value called hasMoreContextRows to component state and a setter function called setHasMoreContextRows
  82. // The intial value for hasMoreContextRows is {before: true, after: true}
  83. // Used for indicating in UI if there are more rows to load in a given direction
  84. const [hasMoreContextRows, setHasMoreContextRows] = useState({
  85. before: true,
  86. after: true,
  87. });
  88. // React Hook that resolves two promises every time the limit prop changes
  89. // First promise fetches limit number of rows backwards in time from a specific point in time
  90. // Second promise fetches limit number of rows forwards in time from a specific point in time
  91. const { value } = useAsync(async () => {
  92. return await getRowContexts(getRowContext, row, limit); // Moved it to a separate function for debugging purposes
  93. }, [limit]);
  94. // React Hook that performs a side effect every time the value (from useAsync hook) prop changes
  95. // The side effect changes the result state with the response from the useAsync hook
  96. // The side effect changes the hasMoreContextRows state if there are more context rows before or after the current result
  97. useEffect(() => {
  98. if (value) {
  99. setResult(currentResult => {
  100. let hasMoreLogsBefore = true,
  101. hasMoreLogsAfter = true;
  102. if (currentResult && currentResult.data[0].length === value.data[0].length) {
  103. hasMoreLogsBefore = false;
  104. }
  105. if (currentResult && currentResult.data[1].length === value.data[1].length) {
  106. hasMoreLogsAfter = false;
  107. }
  108. setHasMoreContextRows({
  109. before: hasMoreLogsBefore,
  110. after: hasMoreLogsAfter,
  111. });
  112. return value;
  113. });
  114. }
  115. }, [value]);
  116. return children({
  117. result: {
  118. before: result ? flatten(result.data[0]) : [],
  119. after: result ? flatten(result.data[1]) : [],
  120. },
  121. errors: {
  122. before: result ? result.errors[0] : null,
  123. after: result ? result.errors[1] : null,
  124. },
  125. hasMoreContextRows,
  126. updateLimit: () => setLimit(limit + 10),
  127. });
  128. };