mirror of
https://github.com/apache/superset.git
synced 2026-04-18 23:55:00 +00:00
feat(filters): Adding empty state for filter modal (#38909)
This commit is contained in:
@@ -75,6 +75,7 @@ export const BaseModalWrapper = styled(StyledModal)<BaseModalWrapperProps>`
|
||||
export const BaseModalBody = styled.div<BaseModalBodyProps>`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
min-height: 500px;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
|
||||
|
||||
@@ -17,7 +17,13 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { act, render, screen, userEvent } from 'spec/helpers/testing-library';
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
userEvent,
|
||||
} from 'spec/helpers/testing-library';
|
||||
import { stateWithoutNativeFilters } from 'spec/fixtures/mockStore';
|
||||
import { testWithId } from 'src/utils/testUtils';
|
||||
import { Preset, makeApi } from '@superset-ui/core';
|
||||
@@ -387,6 +393,15 @@ test('FilterBar apply button is disabled after creating a filter', async () => {
|
||||
userEvent.click(screen.getByTestId(getTestId('collapsable')));
|
||||
userEvent.click(screen.getByLabelText('setting'));
|
||||
userEvent.click(screen.getByText('Add or edit filters and controls'));
|
||||
|
||||
// First add a filter via the dropdown (modal now shows empty state by default)
|
||||
const dropdownButton = screen.getByTestId('new-item-dropdown-button');
|
||||
fireEvent.mouseEnter(dropdownButton);
|
||||
const addFilterMenuItem = await screen.findByRole('menuitem', {
|
||||
name: /add filter/i,
|
||||
});
|
||||
fireEvent.click(addFilterMenuItem);
|
||||
|
||||
userEvent.click(screen.getByText('Value'));
|
||||
userEvent.click(screen.getByText('Time range'));
|
||||
userEvent.type(
|
||||
|
||||
@@ -84,7 +84,7 @@ const FilterBarSettings = () => {
|
||||
|
||||
const { openFilterConfigModal, FilterConfigModalComponent } =
|
||||
useFilterConfigModal({
|
||||
createNewOnOpen: filterValues.length === 0,
|
||||
createNewOnOpen: false,
|
||||
dashboardId,
|
||||
});
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ import {
|
||||
} from '@superset-ui/core';
|
||||
import type { FormInstance } from '@superset-ui/core/components';
|
||||
import { styled } from '@apache-superset/core/theme';
|
||||
import { Flex } from '@superset-ui/core/components';
|
||||
import { EmptyState, Flex } from '@superset-ui/core/components';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import FilterContentRenderer from './FilterContentRenderer';
|
||||
import CustomizationContentRenderer from './CustomizationContentRenderer';
|
||||
import { FiltersConfigFormHandle } from '../FiltersConfigForm/FiltersConfigForm';
|
||||
@@ -105,6 +106,28 @@ function ConfigModalContent({
|
||||
isChartCustomizationId(currentItemId) &&
|
||||
chartCustomizationIds.includes(currentItemId);
|
||||
|
||||
const hasNoItems =
|
||||
filterState.orderedIds.length === 0 &&
|
||||
customizationState.orderedIds.length === 0;
|
||||
const showEmptyState = hasNoItems || !currentItemId;
|
||||
|
||||
if (showEmptyState) {
|
||||
return (
|
||||
<StyledContentFlex vertical>
|
||||
<Flex flex={1}>
|
||||
<EmptyState
|
||||
size="small"
|
||||
title=""
|
||||
image="empty.svg"
|
||||
description={t(
|
||||
'Manage filters and customizations to set scoping, descriptions, and limitations. Create new elements for better dashboard insights.',
|
||||
)}
|
||||
/>
|
||||
</Flex>
|
||||
</StyledContentFlex>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContentFlex vertical>
|
||||
<div
|
||||
|
||||
@@ -20,7 +20,7 @@ import { FC, ReactNode, useCallback, useState } from 'react';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { NativeFilterType, ChartCustomizationType } from '@superset-ui/core';
|
||||
import { styled } from '@apache-superset/core/theme';
|
||||
import { Collapse, Flex } from '@superset-ui/core/components';
|
||||
import { Collapse, EmptyState, Flex } from '@superset-ui/core/components';
|
||||
import type { DragEndEvent } from '@dnd-kit/core';
|
||||
import {
|
||||
DndContext,
|
||||
@@ -49,6 +49,10 @@ const StyledSidebarFlex = styled(Flex)`
|
||||
|
||||
const StyledHeaderFlex = styled(Flex)`
|
||||
padding: ${({ theme }) => theme.sizeUnit * 3}px;
|
||||
|
||||
& button {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
// min-height: 0 lets the flex item shrink below its content size so that
|
||||
@@ -264,6 +268,9 @@ const ConfigModalSidebar: FC<ConfigModalSidebarProps> = ({
|
||||
</div>
|
||||
);
|
||||
|
||||
const hasNoItems =
|
||||
filterOrderedIds.length === 0 && customizationOrderedIds.length === 0;
|
||||
|
||||
return (
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
@@ -279,54 +286,65 @@ const ConfigModalSidebar: FC<ConfigModalSidebarProps> = ({
|
||||
onAddCustomization={onAddCustomization}
|
||||
/>
|
||||
</StyledHeaderFlex>
|
||||
<StyledCollapse
|
||||
key={formValuesVersion}
|
||||
activeKey={activeCollapseKeys}
|
||||
onChange={keys => onCollapseChange(keys as string[])}
|
||||
ghost
|
||||
isDragging={isDragging}
|
||||
>
|
||||
<StyledCollapse.Panel key="filters" header={filtersHeader}>
|
||||
<ItemSectionContent
|
||||
currentItemId={currentItemId}
|
||||
items={filterOrderedIds}
|
||||
removedItems={filterRemovedItems}
|
||||
erroredItems={filterErroredItems}
|
||||
getItemTitle={getTitle}
|
||||
onChange={onChange}
|
||||
onRearrange={onRearrange}
|
||||
onRemove={onRemove}
|
||||
restoreItem={restoreItem}
|
||||
dataTestId="filter-title-container"
|
||||
deleteAltText={t('Remove filter')}
|
||||
dragType={FILTER_TYPE}
|
||||
isCurrentSection={isFilterId(currentItemId)}
|
||||
onCrossListDrop={handleFilterCrossListDrop}
|
||||
{hasNoItems ? (
|
||||
<Flex>
|
||||
<EmptyState
|
||||
size="small"
|
||||
title=""
|
||||
image="empty.svg"
|
||||
description={t('No filters or customizations created yet')}
|
||||
/>
|
||||
</StyledCollapse.Panel>
|
||||
|
||||
<StyledCollapse.Panel
|
||||
key="chartCustomizations"
|
||||
header={customizationsHeader}
|
||||
</Flex>
|
||||
) : (
|
||||
<StyledCollapse
|
||||
key={formValuesVersion}
|
||||
activeKey={activeCollapseKeys}
|
||||
onChange={keys => onCollapseChange(keys as string[])}
|
||||
ghost
|
||||
isDragging={isDragging}
|
||||
>
|
||||
<ItemSectionContent
|
||||
currentItemId={currentItemId}
|
||||
items={customizationOrderedIds}
|
||||
removedItems={customizationRemovedItems}
|
||||
erroredItems={customizationErroredItems}
|
||||
getItemTitle={getTitle}
|
||||
onChange={onChange}
|
||||
onRearrange={onRearrange}
|
||||
onRemove={onRemove}
|
||||
restoreItem={restoreItem}
|
||||
dataTestId="customization-title-container"
|
||||
deleteAltText={t('Remove customization')}
|
||||
dragType={CUSTOMIZATION_TYPE}
|
||||
isCurrentSection={isChartCustomizationId(currentItemId)}
|
||||
onCrossListDrop={handleCustomizationCrossListDrop}
|
||||
/>
|
||||
</StyledCollapse.Panel>
|
||||
</StyledCollapse>
|
||||
<StyledCollapse.Panel key="filters" header={filtersHeader}>
|
||||
<ItemSectionContent
|
||||
currentItemId={currentItemId}
|
||||
items={filterOrderedIds}
|
||||
removedItems={filterRemovedItems}
|
||||
erroredItems={filterErroredItems}
|
||||
getItemTitle={getTitle}
|
||||
onChange={onChange}
|
||||
onRearrange={onRearrange}
|
||||
onRemove={onRemove}
|
||||
restoreItem={restoreItem}
|
||||
dataTestId="filter-title-container"
|
||||
deleteAltText={t('Remove filter')}
|
||||
dragType={FILTER_TYPE}
|
||||
isCurrentSection={isFilterId(currentItemId)}
|
||||
onCrossListDrop={handleFilterCrossListDrop}
|
||||
/>
|
||||
</StyledCollapse.Panel>
|
||||
|
||||
<StyledCollapse.Panel
|
||||
key="chartCustomizations"
|
||||
header={customizationsHeader}
|
||||
>
|
||||
<ItemSectionContent
|
||||
currentItemId={currentItemId}
|
||||
items={customizationOrderedIds}
|
||||
removedItems={customizationRemovedItems}
|
||||
erroredItems={customizationErroredItems}
|
||||
getItemTitle={getTitle}
|
||||
onChange={onChange}
|
||||
onRearrange={onRearrange}
|
||||
onRemove={onRemove}
|
||||
restoreItem={restoreItem}
|
||||
dataTestId="customization-title-container"
|
||||
deleteAltText={t('Remove customization')}
|
||||
dragType={CUSTOMIZATION_TYPE}
|
||||
isCurrentSection={isChartCustomizationId(currentItemId)}
|
||||
onCrossListDrop={handleCustomizationCrossListDrop}
|
||||
/>
|
||||
</StyledCollapse.Panel>
|
||||
</StyledCollapse>
|
||||
)}
|
||||
</StyledSidebarFlex>
|
||||
</DndContext>
|
||||
);
|
||||
|
||||
@@ -772,3 +772,58 @@ test('renders a filter with a chart containing BigInt values', async () => {
|
||||
|
||||
expect(screen.getByText(FILTER_TYPE_REGEX)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('displays empty state when modal opens with no filters and createNewOnOpen is false', () => {
|
||||
defaultRender(defaultState(), { ...props, createNewOnOpen: false });
|
||||
|
||||
// Check left panel empty state
|
||||
expect(
|
||||
screen.getByText('No filters or customizations created yet'),
|
||||
).toBeInTheDocument();
|
||||
|
||||
// Check right panel empty state
|
||||
expect(
|
||||
screen.getByText(
|
||||
/Manage filters and customizations to set scoping, descriptions, and limitations/,
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
|
||||
// Verify no filter form is rendered (no "Untitled" filter created)
|
||||
expect(screen.queryByText(FILTER_TYPE_REGEX)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('does not auto-create a filter when createNewOnOpen is false', () => {
|
||||
defaultRender(defaultState(), { ...props, createNewOnOpen: false });
|
||||
|
||||
// The filter configuration form should not be visible
|
||||
expect(screen.queryByText(FILTER_NAME_REGEX)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(DATASET_REGEX)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('empty state disappears when a filter is added via dropdown', async () => {
|
||||
defaultRender(defaultState(), {
|
||||
...props,
|
||||
createNewOnOpen: false,
|
||||
});
|
||||
|
||||
// Verify empty state is shown initially
|
||||
expect(
|
||||
screen.getByText('No filters or customizations created yet'),
|
||||
).toBeInTheDocument();
|
||||
|
||||
// Add a filter via the dropdown
|
||||
const dropdownButton = screen.getByTestId('new-item-dropdown-button');
|
||||
fireEvent.mouseEnter(dropdownButton);
|
||||
const addFilterMenuItem = await screen.findByRole('menuitem', {
|
||||
name: /add filter/i,
|
||||
});
|
||||
fireEvent.click(addFilterMenuItem);
|
||||
|
||||
// Verify empty state is gone and filter form is shown
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByText('No filters or customizations created yet'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText(FILTER_TYPE_REGEX)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user