Skip to content

Commit

Permalink
feat(native-filters): add markers and number formatter + simple tests (
Browse files Browse the repository at this point in the history
  • Loading branch information
villebro authored Jun 4, 2021
1 parent e03411c commit 18e00e5
Show file tree
Hide file tree
Showing 3 changed files with 248 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -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 }) => (
<SuperChart
chartType="filter_range"
width={width}
height={height}
queriesData={[{ data: [{ min: 10, max: 100 }] }]}
filterState={{ value: [10, 70] }}
formData={{
groupby: ['SP_POP_TOTL'],
adhoc_filters: [],
extra_filters: [],
viz_type: 'filter_range',
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',
},
],
}}
hooks={{
setDataMask: action('setDataMask'),
}}
/>
);
Original file line number Diff line number Diff line change
@@ -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
<RangeFilterPlugin
// @ts-ignore
{...transformProps({
...rangeProps,
formData: { ...rangeProps.formData, ...props },
})}
setDataMask={setDataMask}
/>,
);

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],
},
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand All @@ -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),
},
});
};
Expand All @@ -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 (
<Styles height={height} width={width}>
{Number.isNaN(Number(min)) || Number.isNaN(Number(max)) ? (
Expand All @@ -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}
/>
</div>
)}
Expand Down

0 comments on commit 18e00e5

Please sign in to comment.