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};
+ }
+`;