export default function DrillByModal()

in superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx [160:487]


export default function DrillByModal({
  column,
  dataset,
  drillByConfig,
  formData,
  onHideModal,
  canDownload,
}: DrillByModalProps) {
  const dispatch = useDispatch();
  const theme = useTheme();
  const { addDangerToast } = useToasts();
  const [isChartDataLoading, setIsChartDataLoading] = useState(true);

  const [drillByConfigs, setDrillByConfigs] = useState<DrillByConfigs>([
    { ...drillByConfig, column },
  ]);

  useEffect(() => {
    dispatch(
      logEvent(LOG_ACTIONS_DRILL_BY_MODAL_OPENED, {
        slice_id: formData.slice_id,
      }),
    );
  }, [dispatch, formData.slice_id]);

  const {
    column: currentColumn,
    groupbyFieldName = drillByConfig.groupbyFieldName,
  } = drillByConfigs[drillByConfigs.length - 1] || {};

  const initialGroupbyColumns = useMemo(
    () =>
      ensureIsArray(formData[groupbyFieldName])
        .map(colName =>
          dataset.columns?.find(col => col.column_name === colName),
        )
        .filter(isDefined),
    [dataset.columns, formData, groupbyFieldName],
  );

  const { displayModeToggle, drillByDisplayMode } = useDisplayModeToggle();
  const [chartDataResult, setChartDataResult] = useState<QueryData[]>();

  const resultsTable = useResultsTableView(
    chartDataResult,
    formData.datasource,
    canDownload,
  );

  const [currentFormData, setCurrentFormData] = useState(formData);
  const [usedGroupbyColumns, setUsedGroupbyColumns] = useState<Column[]>(
    [...initialGroupbyColumns, column].filter(isDefined),
  );
  const [breadcrumbsData, setBreadcrumbsData] = useState<DrillByBreadcrumb[]>([
    { groupby: initialGroupbyColumns, filters: drillByConfig.filters },
    { groupby: column || [] },
  ]);

  const getNewGroupby = useCallback(
    (groupbyCol: Column, fieldName = groupbyFieldName) =>
      Array.isArray(formData[fieldName])
        ? [groupbyCol.column_name]
        : groupbyCol.column_name,
    [formData, groupbyFieldName],
  );

  const getFormDataChangesFromConfigs = useCallback(
    (configs: DrillByConfigs) =>
      configs.reduce<Record<string, any>>(
        (acc, config) => {
          if (config?.groupbyFieldName && config.column) {
            acc.formData[config.groupbyFieldName] = getNewGroupby(
              config.column,
              config.groupbyFieldName,
            );
            acc.overriddenGroupbyFields.add(config.groupbyFieldName);
          }
          const adhocFilterFieldName =
            config?.adhocFilterFieldName || DEFAULT_ADHOC_FILTER_FIELD_NAME;
          acc.formData[adhocFilterFieldName] = [
            ...ensureIsArray(acc[adhocFilterFieldName]),
            ...ensureIsArray(config.filters).map(filter =>
              simpleFilterToAdhoc(filter),
            ),
          ];
          acc.overriddenAdhocFilterFields.add(adhocFilterFieldName);

          return acc;
        },
        {
          formData: {} as Record<string, string | string[] | Set<string>>,
          overriddenGroupbyFields: new Set<string>(),
          overriddenAdhocFilterFields: new Set<string>(),
        },
      ),
    [getNewGroupby],
  );

  const getFiltersFromConfigsByFieldName = useCallback(
    () =>
      drillByConfigs.reduce<Record<string, AdhocFilter[]>>((acc, config) => {
        const adhocFilterFieldName =
          config.adhocFilterFieldName || DEFAULT_ADHOC_FILTER_FIELD_NAME;
        acc[adhocFilterFieldName] = [
          ...(acc[adhocFilterFieldName] || []),
          ...config.filters.map(filter => simpleFilterToAdhoc(filter)),
        ];
        return acc;
      }, {}),
    [drillByConfigs],
  );

  const onBreadcrumbClick = useCallback(
    (breadcrumb: DrillByBreadcrumb, index: number) => {
      dispatch(
        logEvent(LOG_ACTIONS_DRILL_BY_BREADCRUMB_CLICKED, {
          slice_id: formData.slice_id,
        }),
      );
      setDrillByConfigs(prevConfigs => prevConfigs.slice(0, index));
      setBreadcrumbsData(prevBreadcrumbs => {
        const newBreadcrumbs = prevBreadcrumbs.slice(0, index + 1);
        delete newBreadcrumbs[newBreadcrumbs.length - 1].filters;
        return newBreadcrumbs;
      });
      setUsedGroupbyColumns(prevUsedGroupbyColumns =>
        prevUsedGroupbyColumns.slice(0, index),
      );
      setCurrentFormData(() => {
        if (index === 0) {
          return formData;
        }
        const { formData: overrideFormData, overriddenAdhocFilterFields } =
          getFormDataChangesFromConfigs(drillByConfigs.slice(0, index));

        const newFormData = {
          ...formData,
          ...overrideFormData,
        };
        overriddenAdhocFilterFields.forEach((adhocFilterField: string) => ({
          ...newFormData,
          [adhocFilterField]: [
            ...formData[adhocFilterField],
            ...overrideFormData[adhocFilterField],
          ],
        }));
        return newFormData;
      });
    },
    [dispatch, drillByConfigs, formData, getFormDataChangesFromConfigs],
  );

  const breadcrumbs = useDrillByBreadcrumbs(breadcrumbsData, onBreadcrumbClick);

  const drilledFormData = useMemo(() => {
    let updatedFormData = { ...currentFormData };
    if (currentColumn && groupbyFieldName) {
      updatedFormData[groupbyFieldName] = getNewGroupby(currentColumn);
    }

    const adhocFilters = getFiltersFromConfigsByFieldName();
    Object.keys(adhocFilters).forEach(adhocFilterFieldName => {
      updatedFormData = {
        ...updatedFormData,
        [adhocFilterFieldName]: [
          ...ensureIsArray(formData[adhocFilterFieldName]),
          ...adhocFilters[adhocFilterFieldName],
        ],
      };
    });

    updatedFormData.slice_id = 0;
    delete updatedFormData.slice_name;
    delete updatedFormData.dashboards;
    return updatedFormData;
  }, [
    currentFormData,
    currentColumn,
    groupbyFieldName,
    getFiltersFromConfigsByFieldName,
    getNewGroupby,
    formData,
  ]);

  useEffect(() => {
    setUsedGroupbyColumns(usedCols =>
      !currentColumn ||
      usedCols.some(
        usedCol => usedCol.column_name === currentColumn.column_name,
      )
        ? usedCols
        : [...usedCols, currentColumn],
    );
  }, [currentColumn]);

  const onSelection = useCallback(
    (
      newColumn: Column,
      drillByConfig: Required<ContextMenuFilters>['drillBy'],
    ) => {
      dispatch(
        logEvent(LOG_ACTIONS_FURTHER_DRILL_BY, {
          drill_depth: drillByConfigs.length + 1,
          slice_id: formData.slice_id,
        }),
      );
      setCurrentFormData(drilledFormData);
      setDrillByConfigs(prevConfigs => [
        ...prevConfigs,
        { ...drillByConfig, column: newColumn },
      ]);
      setBreadcrumbsData(prevBreadcrumbs => {
        const newBreadcrumbs = [...prevBreadcrumbs, { groupby: newColumn }];
        newBreadcrumbs[newBreadcrumbs.length - 2].filters =
          drillByConfig.filters;
        return newBreadcrumbs;
      });
    },
    [dispatch, drillByConfigs.length, drilledFormData, formData.slice_id],
  );

  const additionalConfig = useMemo(
    () => ({
      drillBy: { excludedColumns: usedGroupbyColumns, openNewModal: false },
    }),
    [usedGroupbyColumns],
  );

  const { contextMenu, inContextMenu, onContextMenu } = useContextMenu(
    0,
    currentFormData,
    onSelection,
    ContextMenuItem.DrillBy,
    additionalConfig,
  );

  const chartName = useSelector<RootState, string | undefined>(state => {
    const chartLayoutItem = Object.values(state.dashboardLayout.present).find(
      layoutItem => layoutItem.meta?.chartId === formData.slice_id,
    );
    return (
      chartLayoutItem?.meta.sliceNameOverride || chartLayoutItem?.meta.sliceName
    );
  });

  useEffect(() => {
    if (drilledFormData) {
      const [useLegacyApi] = getQuerySettings(drilledFormData);
      setIsChartDataLoading(true);
      setChartDataResult(undefined);
      getChartDataRequest({
        formData: drilledFormData,
      })
        .then(({ response, json }) =>
          handleChartDataResponse(response, json, useLegacyApi),
        )
        .then(queriesResponse => {
          setChartDataResult(queriesResponse);
        })
        .catch(() => {
          addDangerToast(t('Failed to load chart data.'));
        })
        .finally(() => {
          setIsChartDataLoading(false);
        });
    }
  }, [addDangerToast, drilledFormData]);
  const { metadataBar } = useDatasetMetadataBar({ dataset });

  return (
    <Modal
      css={css`
        .antd5-modal-footer {
          border-top: none;
        }
      `}
      show
      onHide={onHideModal ?? (() => null)}
      title={t('Drill by: %s', chartName)}
      footer={<ModalFooter formData={drilledFormData} />}
      responsive
      resizable
      resizableConfig={{
        minHeight: theme.gridUnit * 128,
        minWidth: theme.gridUnit * 128,
        defaultSize: {
          width: 'auto',
          height: '80vh',
        },
      }}
      draggable
      destroyOnClose
      maskClosable={false}
    >
      <div
        css={css`
          display: flex;
          flex-direction: column;
          height: 100%;
        `}
      >
        {metadataBar}
        {breadcrumbs}
        {displayModeToggle}
        {isChartDataLoading && <Loading />}
        {!isChartDataLoading && !chartDataResult && (
          <Alert
            type="error"
            message={t('There was an error loading the chart data')}
          />
        )}
        {drillByDisplayMode === DrillByType.Chart && chartDataResult && (
          <DrillByChart
            dataset={dataset}
            formData={drilledFormData}
            result={chartDataResult}
            onContextMenu={onContextMenu}
            inContextMenu={inContextMenu}
          />
        )}
        {drillByDisplayMode === DrillByType.Table &&
          chartDataResult &&
          resultsTable}
        {contextMenu}
      </div>
    </Modal>
  );
}