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)

Reply via email to