Skip to content
This repository has been archived by the owner on Jan 16, 2025. It is now read-only.

[POC]Add more filter #15

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions superset-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"@fontsource/inter": "^4.0.0",
"@material-ui/core": "^4.12.4",
"@reduxjs/toolkit": "^1.9.3",
"@scarf/scarf": "^1.3.0",
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import DropdownContainer, {
Ref as DropdownContainerRef,
} from 'src/components/DropdownContainer';
import Icons from 'src/components/Icons';
import FilterExtension from 'src/katalon/CustomFilter/FilterExtension';
import { FiltersOutOfScopeCollapsible } from '../FiltersOutOfScopeCollapsible';
import { useFilterControlFactory } from '../useFilterControlFactory';
import { FiltersDropdownContent } from '../FiltersDropdownContent';
Expand Down Expand Up @@ -215,6 +216,7 @@ const FilterControls: FC<FilterControlsProps> = ({
if (isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS)) {
const nativeFiltersInScope = filtersInScope.map((filter, index) => ({
id: filter.id,
name: filter.name,
element: (
<div
className="filter-item-wrapper"
Expand Down Expand Up @@ -316,6 +318,8 @@ const FilterControls: FC<FilterControlsProps> = ({
}
}, [outlinedFilterId, lastUpdated, popoverRef, overflowedIds]);

const renderFilterExtension = () => <FilterExtension items={items} />;

return (
<>
{portalNodes
Expand All @@ -332,7 +336,9 @@ const FilterControls: FC<FilterControlsProps> = ({
{filterBarOrientation === FilterBarOrientation.VERTICAL &&
renderVerticalContent()}
{filterBarOrientation === FilterBarOrientation.HORIZONTAL &&
renderHorizontalContent()}
renderFilterExtension()}
{/* {filterBarOrientation === FilterBarOrientation.HORIZONTAL &&
renderHorizontalContent()} */}
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import ActionButtons from './ActionButtons';
import Horizontal from './Horizontal';
import Vertical from './Vertical';
import { useSelectFiltersInScope } from '../state';
import KatalonHorizontal from 'src/katalon/CustomFilter/KatalonHorizontal';

// FilterBar is just being hidden as it must still
// render fully due to encapsulated logics
Expand Down Expand Up @@ -287,7 +288,7 @@ const FilterBar: React.FC<FiltersBarProps> = ({

const filterBarComponent =
orientation === FilterBarOrientation.HORIZONTAL ? (
<Horizontal
<KatalonHorizontal
actions={actions}
canEdit={canEdit}
dashboardId={dashboardId}
Expand Down
154 changes: 154 additions & 0 deletions superset-frontend/src/katalon/CustomFilter/FilterExtension.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import React, { useState, MouseEvent } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { t } from '@superset-ui/core';
import {
Button,
Checkbox,
Grid,
ListSubheader,
Menu,
MenuItem,
} from '@material-ui/core';

interface FilterItem {
id: string;
name: string;
element: JSX.Element;
}

interface FilterExtensionProps {
items: FilterItem[];
}

const useStyles = makeStyles({
buttonAddMore: {
background: '#FFFFFF',
textTransform: 'none',
fontSize: 16,
fontWeight: 600,
},
filterTitle: {
color: '#797B7F',
fontSize: 14,
fontWeight: 700,
},
flexContainer: {
display: 'flex',
alignItems: 'center',
flexWrap: 'wrap',
gap: '3px',
},
});

function FilterExtension(props: FilterExtensionProps) {
const classes = useStyles();

const { items } = props;

const [addFilter, setAddFilter] = useState<FilterItem[]>([]);

const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);

const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};

const handleClose = () => {
setAnchorEl(null);
};

const handleAddOrRemove = (selectedFilter: FilterItem) => {
setAddFilter((prevFilters: FilterItem[]) => {
// Check if the filter is already added
const isFilterExisting = prevFilters.some(
filter => filter.id === selectedFilter.id,
);

if (isFilterExisting) {
// If the filter exists, remove it from the list
return prevFilters.filter(filter => filter.id !== selectedFilter.id);
}
return [...prevFilters, selectedFilter];
});
handleClose();
};

const renderPopperMenu = (invisbleFilter: FilterItem[]) => (
<Menu
elevation={2}
getContentAnchorEl={null}
id="list-advance-filter"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
>
<ListSubheader className={classes.filterTitle}>
{t('all filters').toUpperCase()}
</ListSubheader>
{invisbleFilter.length !== 0 &&
invisbleFilter.map(item => {
const isFilterExisting = addFilter.find(
filterComponent => filterComponent?.id === item?.id,
);
return (
<MenuItem onClick={() => handleAddOrRemove(item)} key={item?.id}>
<Grid
container
direction="row"
justifyContent="center"
alignItems="center"
spacing={1}
>
<Grid item xs={8}>
{item?.name}
</Grid>
<Grid item xs={4}>
<Checkbox color="primary" checked={!!isFilterExisting} />
</Grid>
</Grid>
</MenuItem>
);
})}
</Menu>
);

const renderAddMoreButton = () => {
// Take the list all filters,
// then device them into two parts
const visibleFilter: FilterItem[] = items.slice(0, 2);
const invisbleFilter: FilterItem[] = items.slice(3);

return (
<div className={classes.flexContainer}>
{visibleFilter.length !== 0 &&
visibleFilter.map((item, index) => (
<div key={index}>{item.element}</div>
))}
{addFilter.length !== 0 &&
addFilter.map((item, index) => <div key={index}>{item.element}</div>)}
<Button
aria-controls="add-more"
aria-haspopup="true"
onClick={handleClick}
className={classes.buttonAddMore}
>
+ Add more
</Button>
{renderPopperMenu(invisbleFilter)}
</div>
);
};

return <>{renderAddMoreButton()}</>;
}

export default FilterExtension;
167 changes: 167 additions & 0 deletions superset-frontend/src/katalon/CustomFilter/KatalonHorizontal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import React, { useMemo } from 'react';
import {
DataMaskStateWithId,
FeatureFlag,
isFeatureEnabled,
JsonObject,
styled,
t,
} from '@superset-ui/core';
import Icons from 'src/components/Icons';
import Loading from 'src/components/Loading';
import { useSelector } from 'react-redux';
import {
getFilterBarTestId,
useChartsVerboseMaps,
} from 'src/dashboard/components/nativeFilters/FilterBar/utils';
import { HorizontalBarProps } from 'src/dashboard/components/nativeFilters/FilterBar/types';
import { DashboardLayout, RootState } from 'src/dashboard/types';
import { crossFiltersSelector } from 'src/dashboard/components/nativeFilters/FilterBar/CrossFilters/selectors';
import { getUrlParam } from 'src/utils/urlUtils';
import { URL_PARAMS } from 'src/constants';
import FilterConfigurationLink from 'src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink';
import FilterBarSettings from 'src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings';
import FilterControls from 'src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls';

const HorizontalBar = styled.div`
${({ theme }) => `
padding: ${theme.gridUnit * 3}px ${theme.gridUnit * 2}px ${
theme.gridUnit * 3
}px ${theme.gridUnit * 4}px;
background: ${theme.colors.grayscale.light5};
box-shadow: inset 0px -2px 2px -1px ${theme.colors.grayscale.light2};
`}
`;

const HorizontalBarContent = styled.div`
${({ theme }) => `
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
justify-content: flex-start;
line-height: 0;

.loading {
margin: ${theme.gridUnit * 2}px auto ${theme.gridUnit * 2}px;
padding: 0;
}
`}
`;

const FilterBarEmptyStateContainer = styled.div`
${({ theme }) => `
font-weight: ${theme.typography.weights.bold};
color: ${theme.colors.grayscale.base};
font-size: ${theme.typography.sizes.s}px;
`}
`;

const FiltersLinkContainer = styled.div<{ hasFilters: boolean }>`
${({ theme, hasFilters }) => `
height: 24px;
display: flex;
align-items: center;
padding: 0 ${theme.gridUnit * 4}px 0 ${theme.gridUnit * 4}px;
border-right: ${
hasFilters ? `1px solid ${theme.colors.grayscale.light2}` : 0
};

button {
display: flex;
align-items: center;
> .anticon {
height: 24px;
padding-right: ${theme.gridUnit}px;
}
> .anticon + span, > .anticon {
margin-right: 0;
margin-left: 0;
}
}
`}
`;

const HorizontalFilterBar: React.FC<HorizontalBarProps> = ({
actions,
canEdit,
dashboardId,
dataMaskSelected,
filterValues,
isInitialized,
onSelectionChange,
}) => {
const dataMask = useSelector<RootState, DataMaskStateWithId>(
state => state.dataMask,
);
const chartConfiguration = useSelector<RootState, JsonObject>(
state => state.dashboardInfo.metadata?.chart_configuration,
);
const dashboardLayout = useSelector<RootState, DashboardLayout>(
state => state.dashboardLayout.present,
);
const isCrossFiltersEnabled = isFeatureEnabled(
FeatureFlag.DASHBOARD_CROSS_FILTERS,
);
const verboseMaps = useChartsVerboseMaps();

const selectedCrossFilters = isCrossFiltersEnabled
? crossFiltersSelector({
dataMask,
chartConfiguration,
dashboardLayout,
verboseMaps,
})
: [];
const hasFilters = filterValues.length > 0 || selectedCrossFilters.length > 0;

const actionsElement = useMemo(
() =>
isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS) ? actions : null,
[actions],
);

const isKatalonEmbeddedMode = getUrlParam(URL_PARAMS.isKatalonEmbeddedMode);

return (
<HorizontalBar {...getFilterBarTestId()}>
<HorizontalBarContent>
{!isInitialized ? (
<Loading position="inline-centered" />
) : (
<>
{!isKatalonEmbeddedMode && (
<>
<FilterBarSettings />
{canEdit &&
isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS) && (
<FiltersLinkContainer hasFilters={hasFilters}>
<FilterConfigurationLink
dashboardId={dashboardId}
createNewOnOpen={filterValues.length === 0}
>
<Icons.PlusSmall /> {t('Add/Edit Filters')}
</FilterConfigurationLink>
</FiltersLinkContainer>
)}
</>
)}
{!hasFilters && (
<FilterBarEmptyStateContainer data-test="horizontal-filterbar-empty">
{t('No filters are currently added to this dashboard.')}
</FilterBarEmptyStateContainer>
)}
{hasFilters && (
<FilterControls
dataMaskSelected={dataMaskSelected}
onFilterSelectionChange={onSelectionChange}
/>
)}
{actionsElement}
</>
)}
</HorizontalBarContent>
</HorizontalBar>
);
};
export default React.memo(HorizontalFilterBar);
Loading