export default function useTaskCards()

in src/components/MFCard/useTaskCards.ts [39:153]


export default function useTaskCards(task: Task | null, decorators: Decorator[]): CardResult {
  const [poll, setPoll] = useState(false);
  // Task cards are stored in a map where key is the url and value is the result
  // This is because this hook can be called with a stale task
  const [taskCards, setTaskCards] = useState<Record<string, CardResult>>({});
  const aborter = useRef<AbortController>();
  const taskFinishedAt = task?.finished_at;
  const expectedCards = decorators.filter((item) => item.name === 'card');

  const url = task ? taskCardsPath(task) : '';

  // timeout in seconds
  const maxTimeout: number = expectedCards.reduce((timeout, decorator) => {
    if (typeof decorator.attributes.timeout === 'number' && decorator.attributes.timeout > timeout) {
      return decorator.attributes.timeout;
    }
    return timeout;
  }, 0);

  const fetchCards = useCallback(
    (path: string, invalidate = false) => {
      if (!path) {
        return;
      }

      setPoll(false);

      if (aborter.current) {
        aborter.current.abort();
      }

      const currentAborter = new AbortController();
      aborter.current = currentAborter;
      // We want to invalidate cache when polling since cache would return old results.
      // First request will be without invalidate and if that returns us all expected cards
      // we don't need to poll and invalidate.
      fetch(`${apiHttp(path)}${invalidate ? '?invalidate=true' : ''}`)
        .then((result) => result.json())
        .then((result: DataModel<CardDefinition[]>) => {
          if (result.status === 200) {
            setTaskCards((prev) => ({
              ...prev,
              [path]: {
                ...prev[path],
                cards: result.data,
                status: result.data.length >= expectedCards.length ? 'success' : prev[path]?.status,
              },
            }));
          } else {
            // The request returned a 500 because there are no cards for this task
            setTaskCards((prev) => ({
              ...prev,
              [path]: {
                cards: prev[path]?.cards ?? [],
                status: result.status === 500 ? 'success' : 'error',
              },
            }));
          }
        })
        .catch((e) => {
          console.error('Cards request failed for ', apiHttp(path), e);
          setTaskCards((prev) => ({
            ...prev,
            [path]: { cards: prev[path]?.cards ?? [], status: 'error' },
          }));
        })
        .finally(() => {
          setPoll(true);
        });
    },
    [expectedCards.length],
  );

  // Poll for new cards
  useEffect(() => {
    let t: number;

    // Check if polling is activated (activated after finished requests).
    if (poll) {
      // if we have enough cards (as presented by decorators list) or timeout has passed, skip request.
      if (expectedCards.length <= taskCards[url]?.cards.length) {
        setPoll(false);
        if (taskCards[url]?.status !== 'success') {
          setTaskCards((prev) => ({
            ...prev,
            [url]: { cards: prev[url]?.cards ?? [], status: 'success' },
          }));
        }
        // If the timeout has been reached
      } else {
        // Otherwise set the status to loading and continue polling
        if (taskCards[url]?.status !== 'loading') {
          setTaskCards((prev) => ({
            ...prev,
            [url]: { cards: prev[url]?.cards ?? [], status: 'loading' },
          }));
        }
        t = window.setTimeout(() => {
          fetchCards(url, true);
        }, POSTLOAD_POLL_INTERVAL);
      }
    }

    return () => {
      clearTimeout(t);
    };
  }, [url, poll, expectedCards, maxTimeout, taskFinishedAt, fetchCards, taskCards]);

  // Initial fetch
  useEffect(() => {
    fetchCards(url);
  }, [fetchCards, url]);

  return taskCards[url] ?? emptyTaskCard;
}