LogRowContextProvider.tsx 5.0 KB

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