Skip to content
Snippets Groups Projects
InteractiveQuestionProvider.tsx 4.6 KiB
Newer Older
import { createContext, useContext, useEffect, useMemo } from "react";
import { useLoadQuestion } from "embedding-sdk/hooks/private/use-load-question";
import { useSdkSelector } from "embedding-sdk/store";
import { getPlugins } from "embedding-sdk/store/selectors";
import type { DataPickerValue } from "metabase/common/components/DataPicker";
import { useValidatedEntityId } from "metabase/lib/entity-id/hooks/use-validated-entity-id";
import { useCreateQuestion } from "metabase/query_builder/containers/use-create-question";
import { useSaveQuestion } from "metabase/query_builder/containers/use-save-question";
import { getEmbeddingMode } from "metabase/visualizations/click-actions/lib/modes";
import type Question from "metabase-lib/v1/Question";
import type {
  InteractiveQuestionContextType,
  InteractiveQuestionProviderProps,
} from "./types";

/**
 * Note: This context should only be used as a wrapper for the InteractiveQuestionResult
 * component. The idea behind this context is to allow the InteractiveQuestionResult component
 * to use components within the ./components folder, which use the context for display
 * */
export const InteractiveQuestionContext = createContext<
  InteractiveQuestionContextType | undefined
>(undefined);

const DEFAULT_OPTIONS = {};

const FILTER_MODEL_MAP: Record<EntityTypeFilterKeys, DataPickerValue["model"]> =
  {
    table: "table",
    question: "card",
    model: "dataset",
    metric: "metric",
  };
const mapEntityTypeFilterToDataPickerModels = (
  entityTypeFilter: InteractiveQuestionProviderProps["entityTypeFilter"],
): InteractiveQuestionContextType["modelsFilterList"] => {
  return entityTypeFilter?.map(entityType => FILTER_MODEL_MAP[entityType]);
};

export const InteractiveQuestionProvider = ({
  options = DEFAULT_OPTIONS,
  onBeforeSave,
  onSave,
  isSaveEnabled = true,
}: InteractiveQuestionProviderProps) => {
  const { id: cardId, isLoading: isLoadingValidatedId } = useValidatedEntityId({
    type: "card",
    id: initId,
  });

  const handleCreateQuestion = useCreateQuestion();
  const handleSaveQuestion = useSaveQuestion();

  const handleSave = async (question: Question) => {
    if (isSaveEnabled) {
      const saveContext = { isNewQuestion: false };

      await onBeforeSave?.(question, saveContext);
      await handleSaveQuestion(question);
    }
  };

  const handleCreate = async (question: Question) => {
    if (isSaveEnabled) {
      const saveContext = { isNewQuestion: true };

      await onBeforeSave?.(question, saveContext);

      const createdQuestion = await handleCreateQuestion(question);
      onSave?.(createdQuestion, saveContext);

      // Set the latest saved question object to update the question title.
      replaceQuestion(createdQuestion);
  } = useLoadQuestion({
    cardId,
    options,
    deserializedCard,
  });

  const globalPlugins = useSdkSelector(getPlugins);

  const combinedPlugins = useMemo(() => {
    return { ...globalPlugins, ...componentPlugins };
  }, [globalPlugins, componentPlugins]);
    return question && getEmbeddingMode(question, combinedPlugins ?? undefined);
  }, [question, combinedPlugins]);
    isQuestionLoading: isQuestionLoading || isLoadingValidatedId,
    onReset: loadQuestion,
    onSave: handleSave,
    onCreate: handleCreate,
    modelsFilterList: mapEntityTypeFilterToDataPickerModels(entityTypeFilter),
    <InteractiveQuestionContext.Provider value={questionContext}>
      {children}
    </InteractiveQuestionContext.Provider>
  );
};

export const useInteractiveQuestionContext = () => {
  const context = useContext(InteractiveQuestionContext);
  if (context === undefined) {
    throw new Error(
      "useInteractiveQuestionContext must be used within a InteractiveQuestionProvider",
    );
  }
  return context;
};