From 8519a090865e258da23f9d1ad9e419de713a18ab Mon Sep 17 00:00:00 2001 From: "Michael S. Molina" <70410625+michael-s-molina@users.noreply.github.com> Date: Fri, 28 May 2021 04:29:16 -0300 Subject: [PATCH] chore: Improves the native filters UI/UX - iteration 5 (#14882) --- .../src/components/Form/FormItem.tsx | 2 +- .../FilterBar/FilterBar.test.tsx | 4 +- .../FiltersConfigModal/FilterTabs.tsx | 63 +- .../FiltersConfigForm/FiltersConfigForm.tsx | 750 +++++++++--------- .../FiltersConfigModal/FiltersConfigModal.tsx | 2 +- .../nativeFilters/FiltersConfigModal/utils.ts | 2 +- 6 files changed, 444 insertions(+), 379 deletions(-) diff --git a/superset-frontend/src/components/Form/FormItem.tsx b/superset-frontend/src/components/Form/FormItem.tsx index ab301a883e..9b529dbd5d 100644 --- a/superset-frontend/src/components/Form/FormItem.tsx +++ b/superset-frontend/src/components/Form/FormItem.tsx @@ -35,7 +35,7 @@ const StyledItem = styled(Form.Item)` &::after { display: inline-block; color: ${theme.colors.error.base}; - font-size: ${theme.typography.sizes.m}px; + font-size: ${theme.typography.sizes.s}px; content: '*'; } } diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx index 3cfb259a99..d666c975fa 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx @@ -76,8 +76,8 @@ const addFilterFlow = async () => { userEvent.click(screen.getByTestId(getTestId('collapsable'))); userEvent.click(screen.getByTestId(getTestId('create-filter'))); // select filter - userEvent.click(screen.getByText('Select filter')); - userEvent.click(screen.getByText('Time filter')); + userEvent.click(screen.getByText('Value')); + userEvent.click(screen.getByText('Time range')); userEvent.type(screen.getByTestId(getModalTestId('name-input')), FILTER_NAME); userEvent.click(screen.getByText('Save')); await screen.findByText('All Filters (1)'); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTabs.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTabs.tsx index 5020b56439..ccb0e009fb 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTabs.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTabs.tsx @@ -42,11 +42,9 @@ export const StyledFilterTitle = styled.span` export const StyledAddFilterBox = styled.div` color: ${({ theme }) => theme.colors.primary.dark1}; - text-align: left; - padding: ${({ theme }) => theme.gridUnit * 2}px 0; - margin: ${({ theme }) => theme.gridUnit * 3}px 0 0 - ${({ theme }) => -theme.gridUnit * 2}px; - border-top: 1px solid ${({ theme }) => theme.colors.grayscale.light1}; + padding: ${({ theme }) => theme.gridUnit * 2}px; + border-top: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; + cursor: pointer; &:hover { color: ${({ theme }) => theme.colors.primary.base}; @@ -89,12 +87,19 @@ const FilterTabsContainer = styled(LineEditableTabs)` & > .ant-tabs-content-holder { border-left: 1px solid ${theme.colors.grayscale.light2}; - margin-right: ${theme.gridUnit * 4}px; + padding-right: ${theme.gridUnit * 4}px; + overflow-x: hidden; + overflow-y: auto; } + & > .ant-tabs-content-holder ~ .ant-tabs-content-holder { border: none; } + &.ant-tabs-card > .ant-tabs-nav .ant-tabs-ink-bar { + visibility: hidden; + } + &.ant-tabs-left > .ant-tabs-content-holder > .ant-tabs-content @@ -104,9 +109,11 @@ const FilterTabsContainer = styled(LineEditableTabs)` } .ant-tabs-nav-list { - padding-top: ${theme.gridUnit * 4}px; - padding-right: ${theme.gridUnit * 2}px; - padding-bottom: ${theme.gridUnit * 4}px; + overflow-x: hidden; + overflow-y: auto; + padding-top: ${theme.gridUnit * 2}px; + padding-right: ${theme.gridUnit}px; + padding-bottom: ${theme.gridUnit * 3}px; padding-left: ${theme.gridUnit * 3}px; } @@ -135,6 +142,24 @@ const FilterTabsContainer = styled(LineEditableTabs)` justify-content: space-between; text-transform: unset; } + + .ant-tabs-nav-more { + display: none; + } + + .ant-tabs-extra-content { + width: 100%; + } + `} +`; + +const StyledHeader = styled.div` + ${({ theme }) => ` + color: ${theme.colors.grayscale.dark1}; + font-size: ${theme.typography.sizes.l}px; + padding-top: ${theme.gridUnit * 4}px; + padding-right: ${theme.gridUnit * 4}px; + padding-left: ${theme.gridUnit * 4}px; `} `; @@ -164,14 +189,18 @@ const FilterTabs: FC = ({ onChange={onChange} activeKey={currentFilterId} onEdit={onEdit} - addIcon={ - - {' '} - - {t('Add filter')} - - - } + hideAdd + tabBarExtraContent={{ + left: {t('Filters')}, + right: ( + onEdit('', 'add')}> + {' '} + + {t('Add filter')} + + + ), + }} > {filterIds.map(id => ( theme.gridUnit * 4}px; + & .ant-form-item-label { + padding-bottom: 0px; + } + & .ant-form-item-control-input { min-height: ${({ theme }) => theme.gridUnit * 10}px; } `; -export const StyledRowFormItem = styled(Form.Item)` +export const StyledRowFormItem = styled(FormItem)` margin-bottom: 0px; + padding-bottom: 0px; min-width: 50%; + & .ant-form-item-label { + padding-bottom: 0px; + } + .ant-form-item-control-input-content > div > div { height: auto; } + & .ant-form-item-control-input { min-height: ${({ theme }) => theme.gridUnit * 10}px; } @@ -118,7 +129,7 @@ export const StyledLabel = styled.span` text-transform: uppercase; `; -const CleanFormItem = styled(Form.Item)` +const CleanFormItem = styled(FormItem)` margin-bottom: 0; `; @@ -151,6 +162,13 @@ const StyledCollapse = styled(Collapse)` `; const StyledTabs = styled(Tabs)` + .ant-tabs-nav { + position: sticky; + top: 0px; + background: white; + z-index: 1; + } + .ant-tabs-nav-list { padding: 0px; } @@ -164,6 +182,9 @@ const StyledAsterisk = styled.span` color: ${({ theme }) => theme.colors.error.base}; font-family: SimSun, sans-serif; margin-right: ${({ theme }) => theme.gridUnit - 1}px; + &:before { + content: '*'; + } `; const FilterTabs = { @@ -208,6 +229,15 @@ const FILTERS_WITH_ADHOC_FILTERS = ['filter_select', 'filter_range']; const BASIC_CONTROL_ITEMS = ['enableEmptyFilter', 'multiSelect']; +const FILTER_TYPE_NAME_MAPPING = { + [t('Select filter')]: t('Value'), + [t('Range filter')]: t('Numerical range'), + [t('Time filter')]: t('Time range'), + [t('Time column')]: t('Time column'), + [t('Time grain')]: t('Time grain'), + [t('Group By')]: t('Group by'), +}; + /** * The configuration form for a specific filter. * Assigns field values to `filters[filterId]` in the form. @@ -418,396 +448,402 @@ const FiltersConfigForm = ( }; return ( - <> - setActiveTabKey(activeKey)} - centered + setActiveTabKey(activeKey)} + centered + > + - - - {t('Filter name')}} - initialValue={filterToEdit?.name} - rules={[{ required: !removed, message: t('Name is required') }]} - > - - - {t('Filter Type')}} - {...getFiltersConfigModalTestId('filter-type')} - > - + + {t('Filter Type')}} + {...getFiltersConfigModalTestId('filter-type')} + > + + + + )} + {Object.keys(controlItems) + .filter(key => !BASIC_CONTROL_ITEMS.includes(key)) + .map(key => controlItems[key])} + {hasDataset && hasAdditionalFilters && ( + { + if (checked) { + // execute after render + setTimeout( + () => + form.validateFields([ + ['filters', filterId, 'adhoc_filters'], + ]), + 0, + ); + } + }} + > + + c.filterable, + ) || [] + } + savedMetrics={datasetDetails?.metrics || []} + datasource={datasetDetails} + onChange={(filters: AdhocFilter[]) => { setNativeFilterFieldValues(form, filterId, { - defaultDataMask: dataMask, + adhoc_filters: filters, }); - form.validateFields([ - ['filters', filterId, 'defaultDataMask'], - ]); forceUpdate(); }} - filterId={filterId} - hasDataset={hasDataset} - form={form} - formData={newFormData} - enableNoResults={enableNoResults} + label={ + + + {t('Pre-filter')} + + } /> - ) : ( - t('Fill all required fields to enable "Default Value"') - )} - - - {Object.keys(controlItems) - .filter(key => BASIC_CONTROL_ITEMS.includes(key)) - .map(key => controlItems[key])} - - - {t('Apply changes instantly')} - - - - {((hasDataset && hasAdditionalFilters) || hasMetrics) && ( - - {isCascadingFilter && ( - { - if (checked) { - // execute after render - setTimeout( - () => - form.validateFields([ - ['filters', filterId, 'parentFilter'], - ]), - 0, - ); - } - }} + + {t('Time range')}} + initialValue={filterToEdit?.time_range || 'No filter'} > - {t('Parent filter')}} - initialValue={parentFilter} - data-test="parent-filter-input" - required - rules={[ + { + setNativeFilterFieldValues(form, filterId, { + time_range: timeRange, + }); + forceUpdate(); + }} + /> + + + )} + onSortChanged(checked || undefined)} + checked={ + typeof filterToEdit?.controlValues?.sortAscending === + 'boolean' + } + > + + {t('Sort type')}} + > + - - - )} - {Object.keys(controlItems) - .filter(key => !BASIC_CONTROL_ITEMS.includes(key)) - .map(key => controlItems[key])} - {hasDataset && hasAdditionalFilters && ( - { - if (checked) { - // execute after render - setTimeout( - () => - form.validateFields([ - ['filters', filterId, 'adhoc_filters'], - ]), - 0, - ); + onChange={({ value }: { value: boolean }) => + onSortChanged(value) } - }} - > - - c.filterable, - ) || [] - } - savedMetrics={datasetDetails?.metrics || []} - datasource={datasetDetails} - onChange={(filters: AdhocFilter[]) => { - setNativeFilterFieldValues(form, filterId, { - adhoc_filters: filters, - }); - forceUpdate(); - }} - label={ - - * - {t('Adhoc filters')} - - } - /> - - {t('Time range')}} - initialValue={filterToEdit?.time_range || 'No filter'} - > - { - setNativeFilterFieldValues(form, filterId, { - time_range: timeRange, - }); - forceUpdate(); - }} - /> - - - )} - onSortChanged(checked || undefined)} - checked={ - typeof filterToEdit?.controlValues?.sortAscending === - 'boolean' - } - > - + /> + + {hasMetrics && ( {t('Sort type')}} + name={['filters', filterId, 'sortMetric']} + initialValue={filterToEdit?.sortMetric} + label={{t('Sort Metric')}} + data-test="field-input" > -