You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ly...@apache.org on 2022/09/14 00:39:16 UTC

[superset] branch master updated: feat: Create dataset header component (#21189)

This is an automated email from the ASF dual-hosted git repository.

lyndsi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 6e8cad3e16 feat: Create dataset header component (#21189)
6e8cad3e16 is described below

commit 6e8cad3e160f0ca7f7dd330861f050ed82a1053b
Author: Lyndsi Kay Williams <55...@users.noreply.github.com>
AuthorDate: Tue Sep 13 19:39:02 2022 -0500

    feat: Create dataset header component (#21189)
---
 .../src/components/PageHeaderWithActions/index.tsx |  8 +++
 superset-frontend/src/components/Tooltip/index.tsx |  7 +-
 .../data/dataset/AddDataset/AddDataset.test.tsx    |  7 +-
 .../data/dataset/AddDataset/Header/Header.test.tsx | 58 ++++++++++++++--
 .../CRUD/data/dataset/AddDataset/Header/index.tsx  | 78 +++++++++++++++++++++-
 .../views/CRUD/data/dataset/AddDataset/index.tsx   |  6 +-
 .../views/CRUD/data/dataset/AddDataset/types.tsx   |  2 +-
 .../dataset/DatasetLayout/DatasetLayout.test.tsx   | 19 ++++--
 .../CRUD/data/dataset/DatasetLayout/index.tsx      | 26 +++++---
 .../src/views/CRUD/data/dataset/styles.ts          | 40 +++++++++--
 10 files changed, 219 insertions(+), 32 deletions(-)

diff --git a/superset-frontend/src/components/PageHeaderWithActions/index.tsx b/superset-frontend/src/components/PageHeaderWithActions/index.tsx
index 19f1ded4a7..9209ab818d 100644
--- a/superset-frontend/src/components/PageHeaderWithActions/index.tsx
+++ b/superset-frontend/src/components/PageHeaderWithActions/index.tsx
@@ -19,6 +19,7 @@
 import React, { ReactNode, ReactElement } from 'react';
 import { css, SupersetTheme, t, useTheme } from '@superset-ui/core';
 import { AntdDropdown, AntdDropdownProps } from 'src/components';
+import { TooltipPlacement } from 'src/components/Tooltip';
 import {
   DynamicEditableTitle,
   DynamicEditableTitleProps,
@@ -112,6 +113,10 @@ export type PageHeaderWithActionsProps = {
   rightPanelAdditionalItems: ReactNode;
   additionalActionsMenu: ReactElement;
   menuDropdownProps: Omit<AntdDropdownProps, 'overlay'>;
+  tooltipProps?: {
+    text?: string;
+    placement?: TooltipPlacement;
+  };
 };
 
 export const PageHeaderWithActions = ({
@@ -124,6 +129,7 @@ export const PageHeaderWithActions = ({
   rightPanelAdditionalItems,
   additionalActionsMenu,
   menuDropdownProps,
+  tooltipProps,
 }: PageHeaderWithActionsProps) => {
   const theme = useTheme();
   return (
@@ -152,6 +158,8 @@ export const PageHeaderWithActions = ({
               css={menuTriggerStyles}
               buttonStyle="tertiary"
               aria-label={t('Menu actions trigger')}
+              tooltip={tooltipProps?.text}
+              placement={tooltipProps?.placement}
               data-test="actions-trigger"
             >
               <Icons.MoreHoriz
diff --git a/superset-frontend/src/components/Tooltip/index.tsx b/superset-frontend/src/components/Tooltip/index.tsx
index 9267502682..06469abd13 100644
--- a/superset-frontend/src/components/Tooltip/index.tsx
+++ b/superset-frontend/src/components/Tooltip/index.tsx
@@ -19,9 +19,14 @@
 import React from 'react';
 import { useTheme, css } from '@superset-ui/core';
 import { Tooltip as AntdTooltip } from 'antd';
-import { TooltipProps } from 'antd/lib/tooltip';
+import {
+  TooltipProps,
+  TooltipPlacement as AntdTooltipPlacement,
+} from 'antd/lib/tooltip';
 import { Global } from '@emotion/react';
 
+export type TooltipPlacement = AntdTooltipPlacement;
+
 export const Tooltip = (props: TooltipProps) => {
   const theme = useTheme();
   return (
diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/AddDataset.test.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/AddDataset.test.tsx
index 6a5bf1ea35..53ece88824 100644
--- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/AddDataset.test.tsx
+++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/AddDataset.test.tsx
@@ -26,9 +26,12 @@ describe('AddDataset', () => {
 
     const blankeStateImgs = screen.getAllByRole('img', { name: /empty/i });
 
-    expect(await screen.findByText(/header/i)).toBeInTheDocument();
     // Header
-    expect(screen.getByText(/header/i)).toBeVisible();
+    expect(
+      await screen.findByRole('textbox', {
+        name: /dataset name/i,
+      }),
+    ).toBeVisible();
     // Left panel
     expect(blankeStateImgs[0]).toBeVisible();
     // Footer
diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/Header/Header.test.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/Header/Header.test.tsx
index c913dbc86a..d4058d8ca7 100644
--- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/Header/Header.test.tsx
+++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/Header/Header.test.tsx
@@ -16,14 +16,64 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import userEvent from '@testing-library/user-event';
 import React from 'react';
-import { render, screen } from 'spec/helpers/testing-library';
+import { render, screen, waitFor } from 'spec/helpers/testing-library';
 import Header from 'src/views/CRUD/data/dataset/AddDataset/Header';
 
 describe('Header', () => {
-  it('renders a blank state Header', () => {
-    render(<Header />);
+  const mockSetDataset = jest.fn();
 
-    expect(screen.getByText(/header/i)).toBeVisible();
+  const waitForRender = (datasetName: string) =>
+    waitFor(() =>
+      render(<Header setDataset={mockSetDataset} datasetName={datasetName} />),
+    );
+
+  it('renders a blank state Header', async () => {
+    await waitForRender('');
+
+    const datasetNameTextbox = screen.getByRole('textbox', {
+      name: /dataset name/i,
+    });
+    const saveButton = screen.getByRole('button', {
+      name: /save save/i,
+    });
+    const menuButton = screen.getByRole('button', {
+      name: /menu actions trigger/i,
+    });
+
+    expect(datasetNameTextbox).toBeVisible();
+    expect(saveButton).toBeVisible();
+    expect(saveButton).toBeDisabled();
+    expect(menuButton).toBeVisible();
+    expect(menuButton).toBeDisabled();
+  });
+
+  it('updates display value of dataset name textbox when Header title is changed', async () => {
+    await waitForRender('');
+
+    const datasetNameTextbox = screen.getByRole('textbox', {
+      name: /dataset name/i,
+    });
+
+    // Textbox should start with an empty display value and placeholder text
+    expect(datasetNameTextbox).toHaveDisplayValue('');
+    expect(
+      screen.getByPlaceholderText(/add the name of the dataset/i),
+    ).toBeVisible();
+
+    // Textbox should update its display value when user inputs a new value
+    userEvent.type(datasetNameTextbox, 'Test name');
+    expect(datasetNameTextbox).toHaveDisplayValue('Test name');
+  });
+
+  it('passes an existing dataset title into the dataset name textbox', async () => {
+    await waitForRender('Existing Dataset Name');
+
+    const datasetNameTextbox = screen.getByRole('textbox', {
+      name: /dataset name/i,
+    });
+
+    expect(datasetNameTextbox).toHaveDisplayValue('Existing Dataset Name');
   });
 });
diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/Header/index.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/Header/index.tsx
index 44f0e19f7b..b4cf81d032 100644
--- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/Header/index.tsx
+++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/Header/index.tsx
@@ -17,7 +17,81 @@
  * under the License.
  */
 import React from 'react';
+import { t } from '@superset-ui/core';
+import { PageHeaderWithActions } from 'src/components/PageHeaderWithActions';
+import Button from 'src/components/Button';
+import Icons from 'src/components/Icons';
+import { Menu } from 'src/components/Menu';
+import { TooltipPlacement } from 'src/components/Tooltip';
+import {
+  HeaderComponentStyles,
+  disabledSaveBtnStyles,
+} from 'src/views/CRUD/data/dataset/styles';
+import {
+  DatasetActionType,
+  DSReducerActionType,
+} from 'src/views/CRUD/data/dataset/AddDataset/types';
 
-export default function Header() {
-  return <div>Header</div>;
+const tooltipProps: { text: string; placement: TooltipPlacement } = {
+  text: t('Select a database table and create dataset'),
+  placement: 'bottomRight',
+};
+
+const renderDisabledSaveButton = () => (
+  <Button
+    buttonStyle="primary"
+    tooltip={tooltipProps?.text}
+    placement={tooltipProps?.placement}
+    disabled
+    css={disabledSaveBtnStyles}
+  >
+    <Icons.Save iconSize="m" />
+    {t('Save')}
+  </Button>
+);
+
+const renderOverlay = () => (
+  <Menu>
+    <Menu.Item>{t('Settings')}</Menu.Item>
+    <Menu.Item>{t('Delete')}</Menu.Item>
+  </Menu>
+);
+
+export default function Header({
+  setDataset,
+  datasetName,
+}: {
+  setDataset: React.Dispatch<DSReducerActionType>;
+  datasetName: string;
+}) {
+  const editableTitleProps = {
+    title: datasetName,
+    placeholder: t('Add the name of the dataset'),
+    onSave: (newDatasetName: string) => {
+      setDataset({
+        type: DatasetActionType.changeDataset,
+        payload: { name: 'dataset_name', value: newDatasetName },
+      });
+    },
+    canEdit: true,
+    label: t('dataset name'),
+  };
+
+  return (
+    <HeaderComponentStyles>
+      <PageHeaderWithActions
+        editableTitleProps={editableTitleProps}
+        showTitlePanelItems={false}
+        showFaveStar={false}
+        faveStarProps={{ itemId: 1, saveFaveStar: () => {} }}
+        titlePanelAdditionalItems={<></>}
+        rightPanelAdditionalItems={renderDisabledSaveButton()}
+        additionalActionsMenu={renderOverlay()}
+        menuDropdownProps={{
+          disabled: true,
+        }}
+        tooltipProps={tooltipProps}
+      />
+    </HeaderComponentStyles>
+  );
 }
diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/index.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/index.tsx
index a1ec33ad17..79c6e5f4c3 100644
--- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/index.tsx
+++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/index.tsx
@@ -70,6 +70,10 @@ export default function AddDataset() {
     Reducer<Partial<DatasetObject> | null, DSReducerActionType>
   >(datasetReducer, null);
 
+  const HeaderComponent = () => (
+    <Header setDataset={setDataset} datasetName={dataset?.dataset_name ?? ''} />
+  );
+
   const LeftPanelComponent = () => (
     <LeftPanel
       setDataset={setDataset}
@@ -80,7 +84,7 @@ export default function AddDataset() {
 
   return (
     <DatasetLayout
-      header={Header()}
+      header={HeaderComponent()}
       leftPanel={LeftPanelComponent()}
       datasetPanel={DatasetPanel()}
       footer={Footer()}
diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/types.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/types.tsx
index 530ed8dd33..b6f04469b8 100644
--- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/types.tsx
+++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/types.tsx
@@ -32,7 +32,7 @@ export interface DatasetObject {
   table_name?: string | null;
 }
 
-interface DatasetReducerPayloadType {
+export interface DatasetReducerPayloadType {
   name: string;
   value?: string;
 }
diff --git a/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/DatasetLayout.test.tsx b/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/DatasetLayout.test.tsx
index 0b6027ea47..cf57c34360 100644
--- a/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/DatasetLayout.test.tsx
+++ b/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/DatasetLayout.test.tsx
@@ -17,7 +17,7 @@
  * under the License.
  */
 import React from 'react';
-import { render, screen } from 'spec/helpers/testing-library';
+import { render, screen, waitFor } from 'spec/helpers/testing-library';
 import DatasetLayout from 'src/views/CRUD/data/dataset/DatasetLayout';
 import Header from 'src/views/CRUD/data/dataset/AddDataset/Header';
 import LeftPanel from 'src/views/CRUD/data/dataset/AddDataset/LeftPanel';
@@ -33,10 +33,21 @@ describe('DatasetLayout', () => {
     expect(layoutWrapper).toHaveTextContent('');
   });
 
-  it('renders a Header when passed in', () => {
-    render(<DatasetLayout header={Header()} />);
+  const mockSetDataset = jest.fn();
 
-    expect(screen.getByText(/header/i)).toBeVisible();
+  const waitForRender = () =>
+    waitFor(() =>
+      render(<Header setDataset={mockSetDataset} datasetName="" />),
+    );
+
+  it('renders a Header when passed in', async () => {
+    await waitForRender();
+
+    expect(
+      screen.getByRole('textbox', {
+        name: /dataset name/i,
+      }),
+    ).toBeVisible();
   });
 
   it('renders a LeftPanel when passed in', async () => {
diff --git a/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/index.tsx b/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/index.tsx
index 8587b25a4a..b5691fe5cb 100644
--- a/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/index.tsx
+++ b/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/index.tsx
@@ -24,11 +24,11 @@ import {
   OuterRow,
   PanelRow,
   FooterRow,
-  StyledHeader,
-  StyledLeftPanel,
-  StyledDatasetPanel,
-  StyledRightPanel,
-  StyledFooter,
+  StyledLayoutHeader,
+  StyledLayoutLeftPanel,
+  StyledLayoutDatasetPanel,
+  StyledLayoutRightPanel,
+  StyledLayoutFooter,
 } from 'src/views/CRUD/data/dataset/styles';
 
 interface DatasetLayoutProps {
@@ -48,22 +48,28 @@ export default function DatasetLayout({
 }: DatasetLayoutProps) {
   return (
     <StyledLayoutWrapper data-test="dataset-layout-wrapper">
-      {header && <StyledHeader>{header}</StyledHeader>}
+      {header && <StyledLayoutHeader>{header}</StyledLayoutHeader>}
       <OuterRow>
         <LeftColumn>
-          {leftPanel && <StyledLeftPanel>{leftPanel}</StyledLeftPanel>}
+          {leftPanel && (
+            <StyledLayoutLeftPanel>{leftPanel}</StyledLayoutLeftPanel>
+          )}
         </LeftColumn>
 
         <RightColumn>
           <PanelRow>
             {datasetPanel && (
-              <StyledDatasetPanel>{datasetPanel}</StyledDatasetPanel>
+              <StyledLayoutDatasetPanel>
+                {datasetPanel}
+              </StyledLayoutDatasetPanel>
+            )}
+            {rightPanel && (
+              <StyledLayoutRightPanel>{rightPanel}</StyledLayoutRightPanel>
             )}
-            {rightPanel && <StyledRightPanel>{rightPanel}</StyledRightPanel>}
           </PanelRow>
 
           <FooterRow>
-            {footer && <StyledFooter>{footer}</StyledFooter>}
+            {footer && <StyledLayoutFooter>{footer}</StyledLayoutFooter>}
           </FooterRow>
         </RightColumn>
       </OuterRow>
diff --git a/superset-frontend/src/views/CRUD/data/dataset/styles.ts b/superset-frontend/src/views/CRUD/data/dataset/styles.ts
index 8d9f771dce..757837f221 100644
--- a/superset-frontend/src/views/CRUD/data/dataset/styles.ts
+++ b/superset-frontend/src/views/CRUD/data/dataset/styles.ts
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { styled } from '@superset-ui/core';
+import { styled, css, SupersetTheme } from '@superset-ui/core';
 
 export const StyledLayoutWrapper = styled.div`
   flex-grow: 1;
@@ -64,32 +64,58 @@ export const FooterRow = styled(Row)`
   height: ${({ theme }) => theme.gridUnit * 16}px;
 `;
 
-export const StyledHeader = styled.div`
+export const StyledLayoutHeader = styled.div`
   flex: 0 0 auto;
   height: ${({ theme }) => theme.gridUnit * 16}px;
   border-bottom: 2px solid ${({ theme }) => theme.colors.grayscale.light2};
-  color: ${({ theme }) => theme.colors.error.base};
+
+  .header-with-actions {
+    height: ${({ theme }) => theme.gridUnit * 15.5}px;
+  }
 `;
 
-export const StyledLeftPanel = styled.div`
+export const StyledLayoutLeftPanel = styled.div`
   width: ${({ theme }) => theme.gridUnit * 80}px;
   height: 100%;
   border-right: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
 `;
 
-export const StyledDatasetPanel = styled.div`
+export const StyledLayoutDatasetPanel = styled.div`
   width: 100%;
 `;
 
-export const StyledRightPanel = styled.div`
+export const StyledLayoutRightPanel = styled.div`
   border-left: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
   color: ${({ theme }) => theme.colors.success.base};
 `;
 
-export const StyledFooter = styled.div`
+export const StyledLayoutFooter = styled.div`
   height: ${({ theme }) => theme.gridUnit * 16}px;
   width: 100%;
   border-top: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
   border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
   color: ${({ theme }) => theme.colors.info.base};
 `;
+
+export const HeaderComponentStyles = styled.div`
+  .ant-btn {
+    span {
+      margin-right: 0;
+    }
+
+    &:disabled {
+      svg {
+        color: ${({ theme }) => theme.colors.grayscale.light1};
+      }
+    }
+  }
+`;
+
+export const disabledSaveBtnStyles = (theme: SupersetTheme) => css`
+  width: ${theme.gridUnit * 21.5}px;
+
+  &:disabled {
+    background-color: ${theme.colors.grayscale.light3};
+    color: ${theme.colors.grayscale.light1};
+  }
+`;