From a5e7a9b78375881881ff6f3b370068099f007a3c Mon Sep 17 00:00:00 2001
From: Ville Brofeldt <33317356+villebro@users.noreply.github.com>
Date: Fri, 4 Jun 2021 16:22:54 +0300
Subject: [PATCH] feat(native-filters): add markers and number formatter +
simple tests (#14981)
---
.../Range/RangeFilterPlugin.stories.tsx | 78 +++++++++++
.../Range/RangeFilterPlugin.test.tsx | 121 ++++++++++++++++++
.../components/Range/RangeFilterPlugin.tsx | 60 +++++++--
3 files changed, 248 insertions(+), 11 deletions(-)
create mode 100644 superset-frontend/src/filters/components/Range/RangeFilterPlugin.stories.tsx
create mode 100644 superset-frontend/src/filters/components/Range/RangeFilterPlugin.test.tsx
diff --git a/superset-frontend/src/filters/components/Range/RangeFilterPlugin.stories.tsx b/superset-frontend/src/filters/components/Range/RangeFilterPlugin.stories.tsx
new file mode 100644
index 0000000000000..fb442e86bc497
--- /dev/null
+++ b/superset-frontend/src/filters/components/Range/RangeFilterPlugin.stories.tsx
@@ -0,0 +1,78 @@
+/**
+ * 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 React from 'react';
+import { action } from '@storybook/addon-actions';
+import {
+ SuperChart,
+ getChartTransformPropsRegistry,
+ GenericDataType,
+} from '@superset-ui/core';
+import RangeFilterPlugin from './index';
+import transformProps from './transformProps';
+
+new RangeFilterPlugin().configure({ key: 'filter_range' }).register();
+
+getChartTransformPropsRegistry().registerValue('filter_range', transformProps);
+
+export default {
+ title: 'Filter Plugins',
+};
+
+export const range = ({ width, height }: { width: number; height: number }) => (
+
+);
diff --git a/superset-frontend/src/filters/components/Range/RangeFilterPlugin.test.tsx b/superset-frontend/src/filters/components/Range/RangeFilterPlugin.test.tsx
new file mode 100644
index 0000000000000..cf9420ecffed5
--- /dev/null
+++ b/superset-frontend/src/filters/components/Range/RangeFilterPlugin.test.tsx
@@ -0,0 +1,121 @@
+/**
+ * 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 { AppSection, GenericDataType } from '@superset-ui/core';
+import React from 'react';
+import { render } from 'spec/helpers/testing-library';
+import RangeFilterPlugin from './RangeFilterPlugin';
+import transformProps from './transformProps';
+
+const rangeProps = {
+ formData: {
+ datasource: '3__table',
+ groupby: ['SP_POP_TOTL'],
+ adhocFilters: [],
+ extraFilters: [],
+ extraFormData: {},
+ granularitySqla: 'ds',
+ metrics: [
+ {
+ aggregate: 'MIN',
+ column: {
+ column_name: 'SP_POP_TOTL',
+ id: 1,
+ type_generic: GenericDataType.NUMERIC,
+ },
+ expressionType: 'SIMPLE',
+ hasCustomLabel: true,
+ label: 'min',
+ },
+ {
+ aggregate: 'MAX',
+ column: {
+ column_name: 'SP_POP_TOTL',
+ id: 2,
+ type_generic: GenericDataType.NUMERIC,
+ },
+ expressionType: 'SIMPLE',
+ hasCustomLabel: true,
+ label: 'max',
+ },
+ ],
+ rowLimit: 1000,
+ showSearch: true,
+ defaultValue: [10, 70],
+ timeRangeEndpoints: ['inclusive', 'exclusive'],
+ urlParams: {},
+ vizType: 'filter_range',
+ inputRef: { current: null },
+ },
+ height: 20,
+ hooks: {},
+ filterState: { value: [10, 70] },
+ queriesData: [
+ {
+ rowcount: 1,
+ colnames: ['min', 'max'],
+ coltypes: [GenericDataType.NUMERIC, GenericDataType.NUMERIC],
+ data: [{ min: 10, max: 100 }],
+ applied_filters: [],
+ rejected_filters: [],
+ },
+ ],
+ width: 220,
+ behaviors: ['NATIVE_FILTER'],
+ isRefreshing: false,
+ appSection: AppSection.DASHBOARD,
+};
+
+describe('RangeFilterPlugin', () => {
+ const setDataMask = jest.fn();
+ const getWrapper = (props = {}) =>
+ render(
+ // @ts-ignore
+ ,
+ );
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should call setDataMask with correct filter', () => {
+ getWrapper();
+ expect(setDataMask).toHaveBeenCalledWith({
+ extraFormData: {
+ filters: [
+ {
+ col: 'SP_POP_TOTL',
+ op: '<=',
+ val: 70,
+ },
+ ],
+ },
+ filterState: {
+ label: 'x ≤ 70',
+ value: [10, 70],
+ },
+ });
+ });
+});
diff --git a/superset-frontend/src/filters/components/Range/RangeFilterPlugin.tsx b/superset-frontend/src/filters/components/Range/RangeFilterPlugin.tsx
index 083c41e67e3d4..8d88e516bc707 100644
--- a/superset-frontend/src/filters/components/Range/RangeFilterPlugin.tsx
+++ b/superset-frontend/src/filters/components/Range/RangeFilterPlugin.tsx
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { t } from '@superset-ui/core';
+import { getNumberFormatter, NumberFormats, t } from '@superset-ui/core';
import React, { useEffect, useState } from 'react';
import { Slider } from 'src/common/components';
import { PluginFilterRangeProps } from './types';
@@ -35,6 +35,8 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
inputRef,
filterState,
} = props;
+ const numberFormatter = getNumberFormatter(NumberFormats.SMART_NUMBER);
+
const [row] = data;
// @ts-ignore
const { min, max }: { min: number; max: number } = row;
@@ -43,15 +45,55 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
const [value, setValue] = useState<[number, number]>(
defaultValue ?? [min, max],
);
+ const [marks, setMarks] = useState<{ [key: number]: string }>({});
+
+ const getBounds = (
+ value: [number, number],
+ ): { lower: number | null; upper: number | null } => {
+ const [lowerRaw, upperRaw] = value;
+ return {
+ lower: lowerRaw > min ? lowerRaw : null,
+ upper: upperRaw < max ? upperRaw : null,
+ };
+ };
+
+ const getLabel = (lower: number | null, upper: number | null): string => {
+ if (lower !== null && upper !== null) {
+ return `${numberFormatter(lower)} ≤ x ≤ ${numberFormatter(upper)}`;
+ }
+ if (lower !== null) {
+ return `x ≥ ${numberFormatter(lower)}`;
+ }
+ if (upper !== null) {
+ return `x ≤ ${numberFormatter(upper)}`;
+ }
+ return '';
+ };
- const handleAfterChange = (value: [number, number]) => {
- const [lower, upper] = value;
+ const getMarks = (
+ lower: number | null,
+ upper: number | null,
+ ): { [key: number]: string } => {
+ const newMarks: { [key: number]: string } = {};
+ if (lower !== null) {
+ newMarks[lower] = numberFormatter(lower);
+ }
+ if (upper !== null) {
+ newMarks[upper] = numberFormatter(upper);
+ }
+ return newMarks;
+ };
+
+ const handleAfterChange = (value: [number, number]): void => {
setValue(value);
+ const { lower, upper } = getBounds(value);
+ setMarks(getMarks(lower, upper));
setDataMask({
extraFormData: getRangeExtraFormData(col, lower, upper),
filterState: {
- value,
+ value: lower !== null || upper !== null ? value : null,
+ label: getLabel(lower, upper),
},
});
};
@@ -64,12 +106,6 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
handleAfterChange(filterState.value ?? [min, max]);
}, [JSON.stringify(filterState.value)]);
- useEffect(() => {
- handleAfterChange(defaultValue ?? [min, max]);
- // I think after Config Modal update some filter it re-creates default value for all other filters
- // so we can process it like this `JSON.stringify` or start to use `Immer`
- }, [JSON.stringify(defaultValue)]);
-
return (
{Number.isNaN(Number(min)) || Number.isNaN(Number(max)) ? (
@@ -80,10 +116,12 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
range
min={min}
max={max}
- value={value}
+ value={value ?? [min, max]}
onAfterChange={handleAfterChange}
onChange={handleChange}
+ tipFormatter={value => numberFormatter(value)}
ref={inputRef}
+ marks={marks}
/>
)}