useAutofix.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import {useCallback, useState} from 'react';
  2. import {
  3. type AutofixData,
  4. AutofixStatus,
  5. AutofixStepType,
  6. type GroupWithAutofix,
  7. } from 'sentry/components/events/autofix/types';
  8. import type {Event} from 'sentry/types/event';
  9. import {
  10. type ApiQueryKey,
  11. setApiQueryData,
  12. useApiQuery,
  13. useQueryClient,
  14. } from 'sentry/utils/queryClient';
  15. import useApi from 'sentry/utils/useApi';
  16. export type AutofixResponse = {
  17. autofix: AutofixData | null;
  18. };
  19. const POLL_INTERVAL = 500;
  20. export const makeAutofixQueryKey = (groupId: string): ApiQueryKey => [
  21. `/issues/${groupId}/autofix/`,
  22. ];
  23. const makeInitialAutofixData = (): AutofixResponse => ({
  24. autofix: {
  25. status: AutofixStatus.PROCESSING,
  26. run_id: '',
  27. steps: [
  28. {
  29. type: AutofixStepType.DEFAULT,
  30. id: '1',
  31. index: 0,
  32. status: AutofixStatus.PROCESSING,
  33. title: 'Starting Autofix...',
  34. insights: [],
  35. progress: [],
  36. },
  37. ],
  38. created_at: new Date().toISOString(),
  39. repositories: [],
  40. },
  41. });
  42. const makeErrorAutofixData = (errorMessage: string): AutofixResponse => {
  43. const data = makeInitialAutofixData();
  44. if (data.autofix) {
  45. data.autofix.status = AutofixStatus.ERROR;
  46. data.autofix.steps = [
  47. {
  48. type: AutofixStepType.DEFAULT,
  49. id: '1',
  50. index: 0,
  51. status: AutofixStatus.ERROR,
  52. title: 'Something went wrong',
  53. completedMessage: errorMessage,
  54. insights: [],
  55. progress: [],
  56. },
  57. ];
  58. }
  59. return data;
  60. };
  61. /** Will not poll when the autofix is in an error state or has completed */
  62. const isPolling = (autofixData?: AutofixData | null) =>
  63. !autofixData ||
  64. ![AutofixStatus.ERROR, AutofixStatus.COMPLETED, AutofixStatus.CANCELLED].includes(
  65. autofixData.status
  66. );
  67. export const useAutofixData = ({groupId}: {groupId: string}) => {
  68. const {data} = useApiQuery<AutofixResponse>(makeAutofixQueryKey(groupId), {
  69. staleTime: Infinity,
  70. enabled: false,
  71. notifyOnChangeProps: ['data'],
  72. });
  73. return data?.autofix ?? null;
  74. };
  75. export const useAiAutofix = (group: GroupWithAutofix, event: Event) => {
  76. const api = useApi();
  77. const queryClient = useQueryClient();
  78. const [isReset, setIsReset] = useState<boolean>(false);
  79. const {data: apiData} = useApiQuery<AutofixResponse>(makeAutofixQueryKey(group.id), {
  80. staleTime: 0,
  81. retry: false,
  82. refetchInterval: query => {
  83. if (isPolling(query.state.data?.[0]?.autofix)) {
  84. return POLL_INTERVAL;
  85. }
  86. return false;
  87. },
  88. });
  89. const triggerAutofix = useCallback(
  90. async (instruction: string) => {
  91. setIsReset(false);
  92. setApiQueryData<AutofixResponse>(
  93. queryClient,
  94. makeAutofixQueryKey(group.id),
  95. makeInitialAutofixData()
  96. );
  97. try {
  98. await api.requestPromise(`/issues/${group.id}/autofix/`, {
  99. method: 'POST',
  100. data: {
  101. event_id: event.id,
  102. instruction,
  103. },
  104. });
  105. } catch (e) {
  106. setApiQueryData<AutofixResponse>(
  107. queryClient,
  108. makeAutofixQueryKey(group.id),
  109. makeErrorAutofixData(e?.responseJSON?.detail ?? 'An error occurred')
  110. );
  111. }
  112. },
  113. [queryClient, group.id, api, event.id]
  114. );
  115. const reset = useCallback(() => {
  116. setIsReset(true);
  117. }, []);
  118. const autofixData = isReset ? null : apiData?.autofix ?? null;
  119. return {
  120. autofixData,
  121. isPolling: isPolling(autofixData),
  122. triggerAutofix,
  123. reset,
  124. };
  125. };