pankod/refine

View on GitHub
packages/inferencer/src/use-relation-fetch/index.ts

Summary

Maintainability
F
4 days
Test Coverage
import React from "react";
import { useDataProvider } from "@refinedev/core";

import {
  dataProviderFromResource,
  removeRelationSuffix,
  toPlural,
  toSingular,
} from "../utilities";
import {
  FieldInferencer,
  InferField,
  InferencerComponentProps,
  ResourceInferenceAttempt,
} from "../types";
import { get } from "lodash";
import { pickMeta } from "../utilities/get-meta-props";

type UseRelationFetchProps = {
  record?: Record<string, unknown>;
  fields?: (InferField | null | false)[];
  infer: FieldInferencer;
  meta?: InferencerComponentProps["meta"];
};

export const useRelationFetch = ({
  record,
  fields,
  infer,
  meta,
}: UseRelationFetchProps) => {
  const dataProvider = useDataProvider();

  const [updatedFields, setUpdatedFields] = React.useState<InferField[]>([]);

  const [initial, setInitial] = React.useState(true);
  const [loading, setLoading] = React.useState<boolean>(false);

  const resolver = React.useCallback(
    async (allFields: (InferField | false | null)[]) => {
      console.groupCollapsed(
        "@refinedev/inferencer is trying to detect relations",
      );
      const attempts: Array<ResourceInferenceAttempt> = [];
      setLoading(true);
      try {
        const promises = allFields.map(async (field) => {
          if (field && (field.relation || field.canRelation)) {
            if (record) {
              if (field.relationInfer) {
                return field;
              }
              const dataProviderName = dataProviderFromResource(field.resource);
              const dp = dataProvider(dataProviderName);

              const isMultiple = field.multiple;

              const requestId = Array.isArray(field.accessor)
                ? undefined
                : field.multiple
                  ? (record[field.key] as Array<unknown>).map((el) => {
                      return field.accessor ? get(el, field.accessor) : el;
                    })[0]
                  : field.accessor
                    ? get(record[field.key], field.accessor)
                    : record[field.key];

              if (requestId && field.resource) {
                try {
                  let record: Record<string, unknown> | undefined = {};

                  if (isMultiple && dp.getMany) {
                    const { data } = await dp.getMany({
                      resource: field.resource.name,
                      ids: [requestId],
                      meta: pickMeta(
                        field.resource?.identifier ?? field.resource?.name,
                        meta,
                        ["getMany"],
                      ),
                    });
                    record = data?.[0];
                  } else {
                    const { data } = await dp.getOne({
                      resource: field.resource.name,
                      id: requestId,
                      meta: pickMeta(
                        field.resource?.identifier ?? field.resource?.name,
                        meta,
                        isMultiple ? ["getMany", "getOne"] : ["getOne"],
                      ),
                    });
                    record = data;
                  }

                  attempts.push({
                    status: "success",
                    resource: field.resource.name,
                    field: field.key,
                  });

                  const relationInfer = infer("__", record, {}, infer);

                  return {
                    ...field,
                    relationInfer,
                  };
                } catch (error) {
                  attempts.push({
                    status: "error",
                    resource: field.resource.name,
                    field: field.key,
                  });
                  return {
                    ...field,
                    relationInfer: null,
                  };
                }
              }

              if (requestId) {
                let responseData;
                let isPlural;

                try {
                  let record: Record<string, unknown> | undefined = {};

                  if (isMultiple && dp.getMany) {
                    const { data } = await dp.getMany({
                      resource: toPlural(removeRelationSuffix(field.key)),
                      ids: [requestId],
                      meta: pickMeta(
                        toPlural(removeRelationSuffix(field.key)),
                        meta,
                        ["getMany"],
                      ),
                    });
                    record = data?.[0];
                  } else {
                    const { data } = await dp.getOne({
                      resource: toPlural(removeRelationSuffix(field.key)),
                      id: requestId,
                      meta: pickMeta(
                        toPlural(removeRelationSuffix(field.key)),
                        meta,
                        isMultiple ? ["getMany", "getOne"] : ["getOne"],
                      ),
                    });
                    record = data;
                  }

                  attempts.push({
                    status: "success",
                    resource: toPlural(removeRelationSuffix(field.key)),
                    field: field.key,
                  });

                  responseData = record;
                  isPlural = true;
                } catch (error) {
                  attempts.push({
                    status: "error",
                    resource: toPlural(removeRelationSuffix(field.key)),
                    field: field.key,
                  });

                  let record: Record<string, unknown> | undefined = {};

                  try {
                    if (isMultiple && dp.getMany) {
                      const { data } = await dp.getMany({
                        resource: toSingular(removeRelationSuffix(field.key)),
                        meta: pickMeta(
                          toSingular(removeRelationSuffix(field.key)),
                          meta,
                          ["getMany"],
                        ),
                        ids: [requestId],
                      });
                      record = data?.[0];
                    } else {
                      const { data } = await dp.getOne({
                        resource: toSingular(removeRelationSuffix(field.key)),
                        meta: pickMeta(
                          toSingular(removeRelationSuffix(field.key)),
                          meta,
                          isMultiple ? ["getMany", "getOne"] : ["getOne"],
                        ),
                        id: requestId,
                      });
                      record = data;
                    }

                    attempts.push({
                      status: "success",
                      resource: toSingular(removeRelationSuffix(field.key)),
                      field: field.key,
                    });

                    responseData = record;
                    isPlural = false;
                  } catch (error) {
                    attempts.push({
                      status: "error",
                      resource: toSingular(removeRelationSuffix(field.key)),
                      field: field.key,
                    });

                    return {
                      ...field,
                      relationInfer: null,
                    };
                  }
                }

                const relationInfer = infer("__", responseData, {}, infer);

                const resourceNameWithoutRelationSuffix = removeRelationSuffix(
                  field.key,
                );

                return {
                  ...field,
                  relation: true,
                  type: "relation",
                  resource: {
                    name: isPlural
                      ? toPlural(resourceNameWithoutRelationSuffix)
                      : toSingular(resourceNameWithoutRelationSuffix),
                  },
                  fieldable: false,
                  canRelation: undefined,
                  relationInfer,
                };
              }

              return {
                ...field,
                relationInfer: null,
              };
            }
          }
          return field;
        });

        const results = await Promise.all(promises);

        setUpdatedFields(results.filter((el) => el) as InferField[]);
        setTimeout(() => {
          setLoading(false);
        }, 500);
      } catch (error) {
        setTimeout(() => {
          setLoading(false);
        }, 500);
      }
      setTimeout(() => {
        console.log(
          `Tried to detect relations with ${
            attempts.length
          } attempts and succeeded with ${
            attempts.filter((el) => el.status === "success").length
          } attempts.`,
        );
        console.groupEnd();

        console.info(
          "@refinedev/inferencer may send multiple requests to your API for nonexistent resources when trying to detect relations. To learn more about how the inferencer works, visit https://s.refine.dev/how-inferencer-works",
        );
      }, 500);
    },
    [dataProvider, record],
  );

  React.useEffect(() => {
    setInitial(false);
    if (!loading && fields && fields.length > 0 && updatedFields.length === 0) {
      resolver(fields);
    }
  }, [resolver, loading, fields, resolver]);

  return {
    fields: updatedFields,
    loading,
    initial,
  };
};