This is an automated email from the ASF dual-hosted git repository.
villebro 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 d6fc720 feat(native-filters): Time native filter (#12992)
d6fc720 is described below
commit d6fc720f4caf28b810258cd722221b6a31deecb9
Author: simcha90 <[email protected]>
AuthorDate: Sat Feb 13 12:09:54 2021 +0200
feat(native-filters): Time native filter (#12992)
* Add Time Filter component
* Improve Time Filter component
* Fix import errors
* Display Time Filter
* Remove console logs
* Change Control Panel
* Remove unnecessary files
* Use time range override
* test: fix tests
* feat: re run pipeline
* fix: fix some case for Time filter
* fix: merge with master
* use original time range
* fix height
* add cross filter behavior
* apply filters on initialization
* add applied filter to overrides
* add unit tests for merge_extra_form_data
Co-authored-by: Agata Stawarz-Pastewska
<[email protected]>
Co-authored-by: Ville Brofeldt <[email protected]>
---
.../nativeFilters/FilterBar/FilterBar.tsx | 24 ++++---
.../nativeFilters/FilterBar/FilterValue.tsx | 1 +
.../FilterConfigModal/FilterConfigForm.tsx | 4 +-
.../FilterConfigModal/FilterConfigModal.tsx | 1 +
.../nativeFilters/FilterConfigModal/utils.ts | 16 +----
.../dashboard/components/nativeFilters/types.ts | 1 +
.../DateFilterControl/DateFilterControl.tsx | 8 ++-
.../src/filters/components/Time/AntdTimeFilter.tsx | 70 +++++++++++++++++++++
.../components/{index.ts => Time/controlPanel.ts} | 10 ++-
.../filters/components/Time/images/thumbnail.png | Bin 0 -> 5658 bytes
.../src/filters/components/{ => Time}/index.ts | 24 ++++++-
.../{index.ts => Time/transformProps.ts} | 21 ++++++-
.../filters/components/{index.ts => Time/types.ts} | 28 ++++++++-
superset-frontend/src/filters/components/index.ts | 1 +
.../src/visualizations/presets/MainPreset.js | 2 +
superset/utils/core.py | 46 ++++++++++----
tests/utils_tests.py | 30 +++++++++
17 files changed, 241 insertions(+), 46 deletions(-)
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx
index ed1df92..6e4dada 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx
@@ -184,17 +184,23 @@ const FilterBar: React.FC<FiltersBarProps> = ({
extraFormData: ExtraFormData,
currentState: CurrentFilterState,
) => {
- setFilterData(prevFilterData => ({
- ...prevFilterData,
- [filter.id]: {
- extraFormData,
- currentState,
- },
- }));
+ let isInitialized = false;
+ setFilterData(prevFilterData => {
+ if (filter.id in prevFilterData) {
+ isInitialized = true;
+ }
+ return {
+ ...prevFilterData,
+ [filter.id]: {
+ extraFormData,
+ currentState,
+ },
+ };
+ });
const children = cascadeChildren[filter.id] || [];
- // force instant updating for parent filters
- if (filter.isInstant || children.length > 0) {
+ // force instant updating on initialization or for parent filters
+ if (!isInitialized || filter.isInstant || children.length > 0) {
setExtraFormData(filter.id, extraFormData, currentState);
}
};
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterValue.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterValue.tsx
index 6d25f87..5104ec1 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterValue.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterValue.tsx
@@ -76,6 +76,7 @@ const FilterValue: React.FC<FilterProps> = ({
defaultValue,
currentValue,
inverseSelection,
+ inputRef,
});
if (!areObjectsEqual(formData || {}, newFormData)) {
setFormData(newFormData);
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal/FilterConfigForm.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal/FilterConfigForm.tsx
index 181f440..09b6d90 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal/FilterConfigForm.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal/FilterConfigForm.tsx
@@ -249,8 +249,8 @@ export const FilterConfigForm:
React.FC<FilterConfigFormProps> = ({
formFilter?.column &&
formFilter?.defaultValueQueriesData && (
<SuperChart
- height={20}
- width={220}
+ height={25}
+ width={250}
formData={newFormData}
queriesData={formFilter?.defaultValueQueriesData}
chartType={formFilter?.filterType}
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal/FilterConfigModal.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal/FilterConfigModal.tsx
index 8e7b4f3..ea08d62 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal/FilterConfigModal.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal/FilterConfigModal.tsx
@@ -506,6 +506,7 @@ export function FilterConfigModal({
return (
<StyledModal
visible={isOpen}
+ maskClosable={false}
title={t('Filter configuration and scoping')}
width="55%"
destroyOnClose
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal/utils.ts
b/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal/utils.ts
index b1740b0..06cec67 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal/utils.ts
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal/utils.ts
@@ -156,6 +156,7 @@ export const findFilterScope = (
export const FilterTypeNames = {
[FilterType.filter_select]: t('Select'),
[FilterType.filter_range]: t('Range'),
+ [FilterType.filter_time]: t('Time'),
};
export const setFilterFieldValues = (
@@ -177,18 +178,3 @@ export const setFilterFieldValues = (
export const isScopingAll = (scope: Scope) =>
!scope || (scope.rootPath[0] === DASHBOARD_ROOT_ID &&
!scope.excluded.length);
-
-type AppendFormData = {
- filters: {
- val?: number | string | null;
- }[];
-};
-
-export const extractDefaultValue = {
- [FilterType.filter_select]: (appendFormData: AppendFormData) =>
- appendFormData.filters?.[0]?.val,
- [FilterType.filter_range]: (appendFormData: AppendFormData) => ({
- min: appendFormData.filters?.[0].val,
- max: appendFormData.filters?.[1].val,
- }),
-};
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/types.ts
b/superset-frontend/src/dashboard/components/nativeFilters/types.ts
index ecce3a6..12e8820 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/types.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/types.ts
@@ -30,6 +30,7 @@ export interface Scope {
export enum FilterType {
filter_select = 'filter_select',
filter_range = 'filter_range',
+ filter_time = 'filter_time',
}
/** The target of a filter is the datasource/column being filtered */
diff --git
a/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterControl.tsx
b/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterControl.tsx
index 3e4833d..0e84bc3 100644
---
a/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterControl.tsx
+++
b/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterControl.tsx
@@ -245,6 +245,12 @@ export default function DateFilterControl(props:
DateFilterLabelProps) {
setShow(false);
}
+ function onOpen() {
+ setTimeRangeValue(value);
+ setFrame(guessFrame(value));
+ setShow(true);
+ }
+
function onHide() {
setTimeRangeValue(value);
setFrame(guessFrame(value));
@@ -355,7 +361,7 @@ export default function DateFilterControl(props:
DateFilterLabelProps) {
<Label
className="pointer"
data-test="time-range-trigger"
- onClick={() => setShow(true)}
+ onClick={onOpen}
>
{actualTimeRange}
</Label>
diff --git a/superset-frontend/src/filters/components/Time/AntdTimeFilter.tsx
b/superset-frontend/src/filters/components/Time/AntdTimeFilter.tsx
new file mode 100644
index 0000000..c00b988
--- /dev/null
+++ b/superset-frontend/src/filters/components/Time/AntdTimeFilter.tsx
@@ -0,0 +1,70 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { styled } from '@superset-ui/core';
+import React, { useState, useEffect } from 'react';
+import DateFilterControl from
'src/explore/components/controls/DateFilterControl/DateFilterControl';
+import { AntdPluginFilterStylesProps } from '../types';
+import { AntdPluginFilterTimeProps } from './types';
+
+const DEFAULT_VALUE = 'Last week';
+
+const Styles = styled.div<AntdPluginFilterStylesProps>`
+ height: ${({ height }) => height}px;
+ width: ${({ width }) => width}px;
+ overflow-x: scroll;
+`;
+
+export default function AntdTimeFilter(props: AntdPluginFilterTimeProps) {
+ const { formData, setExtraFormData, width } = props;
+ const { defaultValue, currentValue } = formData;
+
+ const [value, setValue] = useState<string>(defaultValue ?? DEFAULT_VALUE);
+
+ const handleTimeRangeChange = (timeRange: string): void => {
+ setExtraFormData({
+ // @ts-ignore
+ extraFormData: {
+ override_form_data: {
+ time_range: timeRange,
+ },
+ },
+ currentState: { value: timeRange },
+ });
+ setValue(timeRange);
+ };
+
+ useEffect(() => {
+ handleTimeRangeChange(currentValue ?? DEFAULT_VALUE);
+ }, [currentValue]);
+
+ useEffect(() => {
+ handleTimeRangeChange(defaultValue ?? DEFAULT_VALUE);
+ }, [defaultValue]);
+
+ return (
+ // @ts-ignore
+ <Styles width={width}>
+ <DateFilterControl
+ value={value}
+ name="time_range"
+ onChange={handleTimeRangeChange}
+ />
+ </Styles>
+ );
+}
diff --git a/superset-frontend/src/filters/components/index.ts
b/superset-frontend/src/filters/components/Time/controlPanel.ts
similarity index 76%
copy from superset-frontend/src/filters/components/index.ts
copy to superset-frontend/src/filters/components/Time/controlPanel.ts
index fadaf1c..1b5f12b 100644
--- a/superset-frontend/src/filters/components/index.ts
+++ b/superset-frontend/src/filters/components/Time/controlPanel.ts
@@ -16,5 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
-export { default as AntdSelectFilterPlugin } from './Select';
-export { default as AntdRangeFilterPlugin } from './Range';
+import { ControlPanelConfig } from '@superset-ui/chart-controls';
+
+const config: ControlPanelConfig = {
+ // For control input types, see:
superset-frontend/src/explore/components/controls/index.js
+ controlPanelSections: [],
+};
+
+export default config;
diff --git a/superset-frontend/src/filters/components/Time/images/thumbnail.png
b/superset-frontend/src/filters/components/Time/images/thumbnail.png
new file mode 100644
index 0000000..7afef30
Binary files /dev/null and
b/superset-frontend/src/filters/components/Time/images/thumbnail.png differ
diff --git a/superset-frontend/src/filters/components/index.ts
b/superset-frontend/src/filters/components/Time/index.ts
similarity index 55%
copy from superset-frontend/src/filters/components/index.ts
copy to superset-frontend/src/filters/components/Time/index.ts
index fadaf1c..f6e2bd6 100644
--- a/superset-frontend/src/filters/components/index.ts
+++ b/superset-frontend/src/filters/components/Time/index.ts
@@ -16,5 +16,25 @@
* specific language governing permissions and limitations
* under the License.
*/
-export { default as AntdSelectFilterPlugin } from './Select';
-export { default as AntdRangeFilterPlugin } from './Range';
+import { Behavior, ChartMetadata, ChartPlugin, t } from '@superset-ui/core';
+import controlPanel from './controlPanel';
+import transformProps from './transformProps';
+import thumbnail from './images/thumbnail.png';
+
+export default class TimeFilterPlugin extends ChartPlugin {
+ constructor() {
+ const metadata = new ChartMetadata({
+ name: t('Time range filter plugin'),
+ description: 'Custom time filter plugin',
+ behaviors: [Behavior.CROSS_FILTER, Behavior.NATIVE_FILTER],
+ thumbnail,
+ });
+
+ super({
+ controlPanel,
+ loadChart: () => import('./AntdTimeFilter'),
+ metadata,
+ transformProps,
+ });
+ }
+}
diff --git a/superset-frontend/src/filters/components/index.ts
b/superset-frontend/src/filters/components/Time/transformProps.ts
similarity index 64%
copy from superset-frontend/src/filters/components/index.ts
copy to superset-frontend/src/filters/components/Time/transformProps.ts
index fadaf1c..fc3acb0 100644
--- a/superset-frontend/src/filters/components/index.ts
+++ b/superset-frontend/src/filters/components/Time/transformProps.ts
@@ -16,5 +16,22 @@
* specific language governing permissions and limitations
* under the License.
*/
-export { default as AntdSelectFilterPlugin } from './Select';
-export { default as AntdRangeFilterPlugin } from './Range';
+import { ChartProps } from '@superset-ui/core';
+import { DEFAULT_FORM_DATA } from './types';
+
+export default function transformProps(chartProps: ChartProps) {
+ const { formData, height, hooks, queriesData, width } = chartProps;
+ const { setExtraFormData } = hooks;
+ const { data } = queriesData[0];
+
+ return {
+ data,
+ formData: {
+ ...DEFAULT_FORM_DATA,
+ ...formData,
+ },
+ height,
+ setExtraFormData,
+ width,
+ };
+}
diff --git a/superset-frontend/src/filters/components/index.ts
b/superset-frontend/src/filters/components/Time/types.ts
similarity index 53%
copy from superset-frontend/src/filters/components/index.ts
copy to superset-frontend/src/filters/components/Time/types.ts
index fadaf1c..7676e0f 100644
--- a/superset-frontend/src/filters/components/index.ts
+++ b/superset-frontend/src/filters/components/Time/types.ts
@@ -16,5 +16,29 @@
* specific language governing permissions and limitations
* under the License.
*/
-export { default as AntdSelectFilterPlugin } from './Select';
-export { default as AntdRangeFilterPlugin } from './Range';
+import {
+ QueryFormData,
+ DataRecord,
+ SetExtraFormDataHook,
+} from '@superset-ui/core';
+import { AntdPluginFilterStylesProps } from '../types';
+
+interface PluginFilterTimeCustomizeProps {
+ defaultValue?: string | null;
+ currentValue?: string | null;
+}
+
+export type AntdPluginFilterSelectQueryFormData = QueryFormData &
+ AntdPluginFilterStylesProps &
+ PluginFilterTimeCustomizeProps;
+
+export type AntdPluginFilterTimeProps = AntdPluginFilterStylesProps & {
+ data: DataRecord[];
+ setExtraFormData: SetExtraFormDataHook;
+ formData: AntdPluginFilterSelectQueryFormData;
+};
+
+export const DEFAULT_FORM_DATA: PluginFilterTimeCustomizeProps = {
+ defaultValue: null,
+ currentValue: null,
+};
diff --git a/superset-frontend/src/filters/components/index.ts
b/superset-frontend/src/filters/components/index.ts
index fadaf1c..0f9582a 100644
--- a/superset-frontend/src/filters/components/index.ts
+++ b/superset-frontend/src/filters/components/index.ts
@@ -18,3 +18,4 @@
*/
export { default as AntdSelectFilterPlugin } from './Select';
export { default as AntdRangeFilterPlugin } from './Range';
+export { default as TimeFilterPlugin } from './Time';
diff --git a/superset-frontend/src/visualizations/presets/MainPreset.js
b/superset-frontend/src/visualizations/presets/MainPreset.js
index 0aec9ec..0bee414 100644
--- a/superset-frontend/src/visualizations/presets/MainPreset.js
+++ b/superset-frontend/src/visualizations/presets/MainPreset.js
@@ -62,6 +62,7 @@ import {
import {
AntdSelectFilterPlugin,
AntdRangeFilterPlugin,
+ TimeFilterPlugin,
} from 'src/filters/components/';
import FilterBoxChartPlugin from '../FilterBox/FilterBoxChartPlugin';
import TimeTableChartPlugin from '../TimeTable/TimeTableChartPlugin';
@@ -113,6 +114,7 @@ export default class MainPreset extends Preset {
}),
new AntdSelectFilterPlugin().configure({ key: 'filter_select' }),
new AntdRangeFilterPlugin().configure({ key: 'filter_range' }),
+ new TimeFilterPlugin().configure({ key: 'filter_time' }),
],
});
}
diff --git a/superset/utils/core.py b/superset/utils/core.py
index edbbe63..cf90963 100644
--- a/superset/utils/core.py
+++ b/superset/utils/core.py
@@ -1027,8 +1027,41 @@ def to_adhoc(
return result
+def merge_extra_form_data(form_data: Dict[str, Any]) -> None:
+ """
+ Merge extra form data (appends and overrides) into the main payload
+ and add applied time extras to the payload.
+ """
+ time_extras = {
+ "time_range": "__time_range",
+ "granularity_sqla": "__time_col",
+ "time_grain_sqla": "__time_grain",
+ "druid_time_origin": "__time_origin",
+ "granularity": "__granularity",
+ }
+ applied_time_extras = form_data.get("applied_time_extras", {})
+ form_data["applied_time_extras"] = applied_time_extras
+ extra_form_data = form_data.pop("extra_form_data", {})
+ append_form_data = extra_form_data.pop("append_form_data", {})
+ append_filters = append_form_data.get("filters", None)
+ override_form_data = extra_form_data.pop("override_form_data", {})
+ for key, value in override_form_data.items():
+ form_data[key] = value
+ # mark as temporal overrides as applied time extras
+ time_extra = time_extras.get(key)
+ if time_extra:
+ applied_time_extras[time_extra] = value
+
+ adhoc_filters = form_data.get("adhoc_filters", [])
+ form_data["adhoc_filters"] = adhoc_filters
+ if append_filters:
+ adhoc_filters.extend(
+ [to_adhoc({"isExtra": True, **fltr}) for fltr in append_filters if
fltr]
+ )
+
+
def merge_extra_filters( # pylint: disable=too-many-branches
- form_data: Dict[str, Any]
+ form_data: Dict[str, Any],
) -> None:
# extra_filters are temporary/contextual filters (using the legacy
constructs)
# that are external to the slice definition. We use those for dynamic
@@ -1038,16 +1071,7 @@ def merge_extra_filters( # pylint:
disable=too-many-branches
form_data["applied_time_extras"] = applied_time_extras
adhoc_filters = form_data.get("adhoc_filters", [])
form_data["adhoc_filters"] = adhoc_filters
- # extra_overrides contains additional props to be added/overridden in the
form_data
- # and will deprecate `extra_filters`. For now only `filters` is supported,
- # but additional props will be added later (time grains, groupbys etc)
- extra_form_data = form_data.pop("extra_form_data", {})
- append_form_data = extra_form_data.pop("append_form_data", {})
- append_filters = append_form_data.get("filters", None)
- if append_filters:
- adhoc_filters.extend(
- [to_adhoc({"isExtra": True, **fltr}) for fltr in append_filters if
fltr]
- )
+ merge_extra_form_data(form_data)
if "extra_filters" in form_data:
# __form and __to are special extra_filters that target time
# boundaries. The rest of extra_filters are simple
diff --git a/tests/utils_tests.py b/tests/utils_tests.py
index ab2f6ea..571590d 100644
--- a/tests/utils_tests.py
+++ b/tests/utils_tests.py
@@ -57,6 +57,7 @@ from superset.utils.core import (
JSONEncodedDict,
memoized,
merge_extra_filters,
+ merge_extra_form_data,
merge_request_params,
parse_ssl_cert,
parse_js_uri_path_item,
@@ -902,6 +903,35 @@ class TestUtils(SupersetTestCase):
layout, filter_scopes, default_filters, box_plot.id
) == [{"col": "region", "op": "==", "val": "North America"}]
+ def test_merge_extra_filters_with_no_extras(self):
+ form_data = {
+ "time_range": "Last 10 days",
+ }
+ merge_extra_form_data(form_data)
+ self.assertEqual(
+ form_data,
+ {
+ "time_range": "Last 10 days",
+ "applied_time_extras": {},
+ "adhoc_filters": [],
+ },
+ )
+
+ def test_merge_extra_filters_with_extras(self):
+ form_data = {
+ "time_range": "Last 10 days",
+ "extra_form_data": {
+ "append_form_data": {
+ "filters": [{"col": "foo", "op": "IN", "val": "bar"}]
+ },
+ "override_form_data": {"time_range": "Last 100 years",},
+ },
+ }
+ merge_extra_form_data(form_data)
+ assert form_data["applied_time_extras"] == {"__time_range": "Last 100
years"}
+ assert form_data["time_range"] == "Last 100 years"
+ assert len(form_data["adhoc_filters"]) == 1
+
def test_ssl_certificate_parse(self):
parsed_certificate = parse_ssl_cert(ssl_certificate)
self.assertEqual(parsed_certificate.serial_number,
12355228710836649848)