airbnb/caravel

View on GitHub
superset-frontend/src/explore/components/DataTablesPane/DataTablesPane.tsx

Summary

Maintainability
B
6 hrs
Test Coverage
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import { useCallback, useEffect, useMemo, useState, MouseEvent } from 'react';
import {
  isFeatureEnabled,
  FeatureFlag,
  styled,
  t,
  useTheme,
} from '@superset-ui/core';
import Icons from 'src/components/Icons';
import Tabs from 'src/components/Tabs';
import {
  getItem,
  setItem,
  LocalStorageKeys,
} from 'src/utils/localStorageHelpers';
import {
  SamplesPane,
  TableControlsWrapper,
  useResultsPane,
} from './components';
import { DataTablesPaneProps, ResultTypes } from './types';

const SouthPane = styled.div`
  ${({ theme }) => `
    position: relative;
    background-color: ${theme.colors.grayscale.light5};
    z-index: 5;
    overflow: hidden;

    .ant-tabs {
      height: 100%;
    }

    .ant-tabs-content-holder {
      height: 100%;
    }

    .ant-tabs-content {
      height: 100%;
    }

    .ant-tabs-tabpane {
      display: flex;
      flex-direction: column;
      height: 100%;

      .table-condensed {
        height: 100%;
        overflow: auto;
        margin-bottom: ${theme.gridUnit * 4}px;

        .table {
          margin-bottom: ${theme.gridUnit * 2}px;
        }
      }

      .pagination-container > ul[role='navigation'] {
        margin-top: 0;
      }
    }
  `}
`;

export const DataTablesPane = ({
  queryFormData,
  datasource,
  queryForce,
  onCollapseChange,
  chartStatus,
  ownState,
  errorMessage,
  actions,
  canDownload,
}: DataTablesPaneProps) => {
  const theme = useTheme();
  const [activeTabKey, setActiveTabKey] = useState<string>(ResultTypes.Results);
  const [isRequest, setIsRequest] = useState<Record<ResultTypes, boolean>>({
    results: false,
    samples: false,
  });
  const [panelOpen, setPanelOpen] = useState(
    isFeatureEnabled(FeatureFlag.DatapanelClosedByDefault)
      ? false
      : getItem(LocalStorageKeys.IsDatapanelOpen, false),
  );

  useEffect(() => {
    if (!isFeatureEnabled(FeatureFlag.DatapanelClosedByDefault))
      setItem(LocalStorageKeys.IsDatapanelOpen, panelOpen);
  }, [panelOpen]);

  useEffect(() => {
    if (!panelOpen) {
      setIsRequest({
        results: false,
        samples: false,
      });
    }

    if (
      panelOpen &&
      activeTabKey.startsWith(ResultTypes.Results) &&
      chartStatus &&
      chartStatus !== 'loading'
    ) {
      setIsRequest({
        results: true,
        samples: false,
      });
    }

    if (panelOpen && activeTabKey === ResultTypes.Samples) {
      setIsRequest({
        results: false,
        samples: true,
      });
    }
  }, [panelOpen, activeTabKey, chartStatus]);

  const handleCollapseChange = useCallback(
    (isOpen: boolean) => {
      onCollapseChange(isOpen);
      setPanelOpen(isOpen);
    },
    [onCollapseChange],
  );

  const handleTabClick = useCallback(
    (tabKey: string, e: MouseEvent) => {
      if (!panelOpen) {
        handleCollapseChange(true);
      } else if (tabKey === activeTabKey) {
        e.preventDefault();
        handleCollapseChange(false);
      }
      setActiveTabKey(tabKey);
    },
    [activeTabKey, handleCollapseChange, panelOpen],
  );

  const CollapseButton = useMemo(() => {
    const caretIcon = panelOpen ? (
      <Icons.CaretUp
        iconColor={theme.colors.grayscale.base}
        aria-label={t('Collapse data panel')}
      />
    ) : (
      <Icons.CaretDown
        iconColor={theme.colors.grayscale.base}
        aria-label={t('Expand data panel')}
      />
    );
    return (
      <TableControlsWrapper>
        {panelOpen ? (
          <span
            role="button"
            tabIndex={0}
            onClick={() => handleCollapseChange(false)}
          >
            {caretIcon}
          </span>
        ) : (
          <span
            role="button"
            tabIndex={0}
            onClick={() => handleCollapseChange(true)}
          >
            {caretIcon}
          </span>
        )}
      </TableControlsWrapper>
    );
  }, [handleCollapseChange, panelOpen, theme.colors.grayscale.base]);

  const queryResultsPanes = useResultsPane({
    errorMessage,
    queryFormData,
    queryForce,
    ownState,
    isRequest: isRequest.results,
    actions,
    isVisible: ResultTypes.Results === activeTabKey,
    canDownload,
  }).map((pane, idx) => {
    if (idx === 0) {
      return (
        <Tabs.TabPane tab={t('Results')} key={ResultTypes.Results}>
          {pane}
        </Tabs.TabPane>
      );
    }
    if (idx > 0) {
      return (
        <Tabs.TabPane
          tab={t('Results %s', idx + 1)}
          key={`${ResultTypes.Results} ${idx + 1}`}
        >
          {pane}
        </Tabs.TabPane>
      );
    }
    return null;
  });

  return (
    <SouthPane data-test="some-purposeful-instance">
      <Tabs
        fullWidth={false}
        tabBarExtraContent={CollapseButton}
        activeKey={panelOpen ? activeTabKey : ''}
        onTabClick={handleTabClick}
      >
        {queryResultsPanes}
        <Tabs.TabPane tab={t('Samples')} key={ResultTypes.Samples}>
          <SamplesPane
            datasource={datasource}
            queryForce={queryForce}
            isRequest={isRequest.samples}
            actions={actions}
            isVisible={ResultTypes.Samples === activeTabKey}
            canDownload={canDownload}
          />
        </Tabs.TabPane>
      </Tabs>
    </SouthPane>
  );
};