This is an automated email from the ASF dual-hosted git repository. suddjian pushed a commit to branch feature/filter-p0 in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
commit d7e926edbbb47e8274f1cb5bb87500c37ca20d5d Author: David Aaron Suddjian <[email protected]> AuthorDate: Thu Sep 17 08:51:28 2020 -0700 finished filter indicators --- .../components/FiltersBadge/DetailsPanel.tsx | 49 +++++++++--- .../dashboard/components/FiltersBadge/Styles.tsx | 21 ++++-- .../dashboard/components/FiltersBadge/index.tsx | 55 +++++++++----- .../dashboard/components/FiltersBadge/selectors.js | 88 +++++++++------------- superset/connectors/base/models.py | 2 - superset/views/core.py | 1 - superset/views/utils.py | 1 - superset/viz.py | 15 ++-- 8 files changed, 131 insertions(+), 101 deletions(-) diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel.tsx index 723f6c0..3a8a15d 100644 --- a/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel.tsx +++ b/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel.tsx @@ -1,33 +1,62 @@ import React from 'react'; +import { + SearchOutlined, + MinusCircleFilled, + CheckCircleFilled, + ExclamationCircleFilled, +} from '@ant-design/icons'; import { Collapse } from '../../../common/components/index'; -import { SearchOutlined, MinusCircleFilled, CheckCircleFilled, ExclamationCircleFilled } from '@ant-design/icons'; import S from './Styles'; +import { APPLIED, INCOMPATIBLE, UNSET } from './selectors'; -const Indicator = ({ indicator: { name, value = [], path }, onClick }) => ( +export type Indicator = { + id: string; + name: string; + value: string[]; + status: typeof APPLIED | typeof UNSET | typeof INCOMPATIBLE; + path: string; +}; + +export interface IndicatorProps { + indicator: Indicator; + onClick: (path: string) => void; +} + +const Indicator = ({ + indicator: { name, value = [], path }, + onClick, +}: IndicatorProps) => ( <S.Item onClick={() => onClick(path)}> <S.ItemIcon> <SearchOutlined /> </S.ItemIcon> <S.Title bold>{name.toUpperCase()}</S.Title> - {value.length ? `: ${[].concat(value).join(', ')}` : ''} + {value.length ? `: ${value.join(', ')}` : ''} </S.Item> ); +export interface DetailsPanelProps { + appliedIndicators: Indicator[]; + incompatibleIndicators: Indicator[]; + unsetIndicators: Indicator[]; + onHighlightFilterSource: (path: string) => void; +} + const DetailsPanel = ({ appliedIndicators = [], incompatibleIndicators = [], unsetIndicators = [], onHighlightFilterSource, -}) => { - const total = appliedIndicators.length + incompatibleIndicators.length + unsetIndicators.length; +}: DetailsPanelProps) => { + const total = + appliedIndicators.length + + incompatibleIndicators.length + + unsetIndicators.length; return ( <S.Panel> <div>{`${total} Scoped Filters`}</div> <S.Reset> - <Collapse - ghost - defaultActiveKey={['applied', 'incompatible']} - > + <Collapse ghost defaultActiveKey={['applied', 'incompatible']}> {appliedIndicators.length ? ( <Collapse.Panel key="applied" @@ -96,4 +125,4 @@ const DetailsPanel = ({ ); }; -export default DetailsPanel; \ No newline at end of file +export default DetailsPanel; diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx index 7106bae..785a649 100644 --- a/superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx +++ b/superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx @@ -1,4 +1,4 @@ -import styled from '@superset-ui/style'; +import { styled } from '@superset-ui/core'; const Pill = styled.div` display: inline-block; @@ -9,21 +9,26 @@ const Pill = styled.div` padding: 0 8px; font-size: 14px; font-weight: normal; - + &:hover { cursor: pointer; filter: brightness(3); } - + svg { vertical-align: text-top; } `; +interface TitleProps { + bold?: boolean; + color?: string; +} + const Title = styled.span` - color: ${({ color }) => color || 'auto'}; + color: ${({ color }: TitleProps) => color || 'auto'}; font-weight: ${({ bold }) => (bold ? '600' : 'auto')}; - + & > .anticon * { color: ${({ color }) => color || 'auto'}; } @@ -46,7 +51,7 @@ const Item = styled.button` white-space: nowrap; position: relative; outline: none; - + &::-moz-focus-inner { border: 0; } @@ -70,7 +75,7 @@ const Panel = styled.div` min-width: 200px; max-width: 400px; overflow-x: hidden; - + * { color: #fff; } @@ -86,4 +91,4 @@ const S = { ItemIcon, }; -export default S; \ No newline at end of file +export default S; diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx index debf0e9..b6f31d8 100644 --- a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx +++ b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx @@ -1,15 +1,20 @@ import React from 'react'; -import { connect } from 'react-redux'; +import { connect, Dispatch, MapStateToProps } from 'react-redux'; import { bindActionCreators } from 'redux'; import { WarningFilled } from '@ant-design/icons'; -import {Popover, Icon, Collapse} from '../../../common/components'; import { ReactComponent as FilterIcon } from 'images/icons/filter.svg'; -import DetailsPanel from './DetailsPanel'; +import { Popover, Icon, Collapse } from '../../../common/components'; +import DetailsPanel, { Indicator } from './DetailsPanel'; import S from './Styles'; import { setDirectPathToChild } from '../../actions/dashboardState'; -import { selectIndicatorsForChart, INCOMPATIBLE, APPLIED, UNSET } from './selectors'; +import { + selectIndicatorsForChart, + INCOMPATIBLE, + APPLIED, + UNSET, +} from './selectors'; -const mapDispatchToProps = (dispatch) => { +const mapDispatchToProps = (dispatch: Dispatch<any>) => { return bindActionCreators( { onHighlightFilterSource: setDirectPathToChild, @@ -18,11 +23,20 @@ const mapDispatchToProps = (dispatch) => { ); }; +interface IndexProps { + chartId: string; +} + const mapStateToProps = ( - { datasources, dashboardFilters, charts }, - { chartId }, + { datasources, dashboardFilters, charts }: any, + { chartId }: IndexProps, ) => { - const indicators = selectIndicatorsForChart(chartId, dashboardFilters, datasources, charts); + const indicators = selectIndicatorsForChart( + chartId, + dashboardFilters, + datasources, + charts, + ); return { chartId, @@ -32,11 +46,20 @@ const mapStateToProps = ( const Index = ({ indicators, - onHighlightFilterSource + onHighlightFilterSource, +}: { + indicators: Indicator[]; + onHighlightFilterSource: (path: string) => void; }) => { - const appliedIndicators = indicators.filter((indicator) => indicator.status === APPLIED); - const unsetIndicators = indicators.filter((indicator) => indicator.status === UNSET); - const incompatibleIndicators = indicators.filter((indicator) => indicator.status === INCOMPATIBLE); + const appliedIndicators = indicators.filter( + indicator => indicator.status === APPLIED, + ); + const unsetIndicators = indicators.filter( + indicator => indicator.status === UNSET, + ); + const incompatibleIndicators = indicators.filter( + indicator => indicator.status === INCOMPATIBLE, + ); if (!appliedIndicators.length && !incompatibleIndicators.length) { return null; @@ -58,7 +81,8 @@ const Index = ({ color="rgba(0, 0, 0, 0.8)" > <S.Pill> - <Icon component={FilterIcon} /> {appliedIndicators.length + incompatibleIndicators.length} + <Icon component={FilterIcon} />{' '} + {appliedIndicators.length + incompatibleIndicators.length} {incompatibleIndicators.length ? ( <span> {' '} @@ -71,7 +95,4 @@ const Index = ({ ); }; -export default connect( - mapStateToProps, - mapDispatchToProps, -)(Index); \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(Index); diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/selectors.js b/superset-frontend/src/dashboard/components/FiltersBadge/selectors.js index 6bdd0b9..f56ad06 100644 --- a/superset-frontend/src/dashboard/components/FiltersBadge/selectors.js +++ b/superset-frontend/src/dashboard/components/FiltersBadge/selectors.js @@ -1,5 +1,5 @@ -import { getChartIdsInFilterScope } from '../../util/activeDashboardFilters'; import { isNil, get } from 'lodash'; +import { getChartIdsInFilterScope } from '../../util/activeDashboardFilters'; import { TIME_FILTER_MAP } from '../../../visualizations/FilterBox/FilterBox'; export const UNSET = 'UNSET'; @@ -11,33 +11,6 @@ const TIME_GRANULARITY_FIELDS = new Set([ TIME_FILTER_MAP.time_grain_sqla, ]); -/* -if (isDateFilter && TIME_GRANULARITY_FIELDS.includes(name)) { - const timeGranularityConfig = - (name === TIME_FILTER_MAP.time_grain_sqla - ? datasource.time_grain_sqla - : datasource.granularity) || []; - const timeGranularityDisplayMapping = timeGranularityConfig.reduce( - (map, [key, value]) => ({ - ...map, - [key]: value, - }), - {}, - ); - - indicator.values = indicator.values.map( - value => timeGranularityDisplayMapping[value] || value, - ); - } - - if (isEmpty(indicator.values)) { - indicators[1].push(indicator); - } else { - indicators[0].push(indicator); - } -}); - */ - const selectIndicatorValue = (columnKey, filter, datasource) => { if ( isNil(filter.columns[columnKey]) || @@ -68,40 +41,33 @@ const selectIndicatorValue = (columnKey, filter, datasource) => { return [].concat(filter.columns[columnKey]); }; -const selectIndicatorStatus = (columnKey, filter, chart) => { - if ( - isNil(filter.columns[columnKey]) || - (filter.isDateFilter && filter.columns[columnKey] === 'No filter') || - (Array.isArray(filter.columns[columnKey]) && - filter.columns[columnKey].length === 0) - ) { - return UNSET; - } - - if (get(chart, 'queryResponse.rejected_filters', []).includes(columnKey)) { - return INCOMPATIBLE; - } - - return APPLIED; -}; - const selectIndicatorsForChartFromFilter = ( chartId, filter, filterDataSource, - chart, + appliedColumns, + rejectedColumns, ) => { + // filters can be applied (if the filter is compatible with the datasource) + // or rejected (if the filter is incompatible) + // or the status can be unknown (if the filter has calculated parameters that we can't analyze) + const getStatus = column => { + if (appliedColumns.has(column)) return APPLIED; + if (rejectedColumns.has(column)) return INCOMPATIBLE; + return UNSET; + }; + return Object.keys(filter.columns) - .filter(key => - getChartIdsInFilterScope({ filterScope: filter.scopes[key] }).includes( + .filter(column => + getChartIdsInFilterScope({ filterScope: filter.scopes[column] }).includes( chartId, ), ) - .map(key => ({ - id: key, - name: filter.labels[key] || key, - value: selectIndicatorValue(key, filter, filterDataSource), - status: selectIndicatorStatus(key, filter, chart), + .map(column => ({ + id: column, + name: filter.labels[column] || column, + value: selectIndicatorValue(column, filter, filterDataSource), + status: getStatus(column), path: filter.directPathToFilter, })); }; @@ -112,6 +78,19 @@ export const selectIndicatorsForChart = ( datasources, charts, ) => { + const chart = charts[chartId]; + // for now we only need to know which columns are compatible/incompatible, + // so grab the columns from the applied/rejected filters + const appliedColumns = new Set( + get(chart, 'queryResponse.applied_filters', []).map( + filter => filter.column, + ), + ); + const rejectedColumns = new Set( + get(chart, 'queryResponse.rejected_filters', []).map( + filter => filter.column, + ), + ); return Object.values(filters) .filter(filter => filter.chartId !== chartId) .reduce( @@ -121,7 +100,8 @@ export const selectIndicatorsForChart = ( chartId, filter, datasources[filter.datasourceId] || {}, - charts[chartId], + appliedColumns, + rejectedColumns, ), ), [], diff --git a/superset/connectors/base/models.py b/superset/connectors/base/models.py index 724a9ce..52f6521 100644 --- a/superset/connectors/base/models.py +++ b/superset/connectors/base/models.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -import logging import json from enum import Enum from typing import Any, Dict, Hashable, List, Optional, Type, Union @@ -30,7 +29,6 @@ from superset.models.helpers import AuditMixinNullable, ImportMixin, QueryResult from superset.models.slice import Slice from superset.typing import FilterValue, FilterValues, QueryObjectDict from superset.utils import core as utils -logger = logging.getLogger(__name__) METRIC_FORM_DATA_PARAMS = [ "metric", "metrics", diff --git a/superset/views/core.py b/superset/views/core.py index 8899efb..ef94632 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -511,7 +511,6 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods TODO: break into one endpoint for each return shape""" - logger.info('test123') response_type = utils.ChartDataResultFormat.JSON.value responses: List[ Union[utils.ChartDataResultFormat, utils.ChartDataResultType] diff --git a/superset/views/utils.py b/superset/views/utils.py index f941834..eaecc5f 100644 --- a/superset/views/utils.py +++ b/superset/views/utils.py @@ -105,7 +105,6 @@ def get_viz( form_data: FormData, datasource_type: str, datasource_id: int, force: bool = False ) -> BaseViz: viz_type = form_data.get("viz_type", "table") - logger.info('testaa3') datasource = ConnectorRegistry.get_datasource( datasource_type, datasource_id, db.session ) diff --git a/superset/viz.py b/superset/viz.py index bdf1f0b..7ac6f58 100644 --- a/superset/viz.py +++ b/superset/viz.py @@ -345,12 +345,6 @@ class BaseViz: merge_extra_filters(self.form_data) utils.split_adhoc_filters_into_base_filters(self.form_data) - filters = self.form_data.get("filters") - filter_columns = [flt.get("col") for flt in filters] - columns = set(self.datasource.column_names) - self.applied_filters = [{"column": col} for col in filter_columns if col in columns] - self.rejected_filters = [{"reason": "not_in_datasource", "column": col} for col in filter_columns if col not in columns] - def query_obj(self) -> QueryObjectDict: """Building a query object""" form_data = self.form_data @@ -488,8 +482,13 @@ class BaseViz: if "df" in payload: del payload["df"] - payload["applied_filters"] = self.applied_filters - payload["rejected_filters"] = self.rejected_filters + filters = self.form_data.get("filters") + filter_columns = [flt.get("col") for flt in filters] + columns = set(self.datasource.column_names) + payload["applied_filters"] = [{"column": col} for col in filter_columns if col in columns] + payload["rejected_filters"] = [{"reason": "not_in_datasource", "column": col} for col in filter_columns if col not in columns] + logger.info(payload["applied_filters"]) + logger.info(payload["rejected_filters"]) return payload
