Skip to content

Commit

Permalink
feat: list view filters for Query History (#11702)
Browse files Browse the repository at this point in the history
  • Loading branch information
nytai authored Nov 30, 2020
1 parent 3cd94d6 commit 0117247
Show file tree
Hide file tree
Showing 16 changed files with 809 additions and 416 deletions.
422 changes: 220 additions & 202 deletions superset-frontend/package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions superset-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
"@superset-ui/preset-chart-xy": "^0.15.13",
"@vx/responsive": "^0.0.195",
"abortcontroller-polyfill": "^1.1.9",
"antd": "^4.6.6",
"antd": "^4.8.2",
"array-move": "^2.2.1",
"bootstrap": "^3.4.1",
"bootstrap-slider": "^10.0.0",
Expand All @@ -121,7 +121,7 @@
"lodash-es": "^4.17.14",
"mathjs": "^8.0.1",
"memoize-one": "^5.1.1",
"moment": "^2.20.1",
"moment": "^2.26.0",
"mousetrap": "^1.6.1",
"mustache": "^2.2.1",
"omnibar": "^2.1.1",
Expand Down
13 changes: 13 additions & 0 deletions superset-frontend/spec/helpers/shim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@ const g = global as any;
g.window = g.window || {};
g.window.location = { href: 'about:blank' };
g.window.performance = { now: () => new Date().getTime() };
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // Deprecated
removeListener: jest.fn(), // Deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});

g.$ = jQuery(g.window);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ const mockedProps = {
accessor: 'name',
Header: 'Name',
},
{
accessor: 'time',
Header: 'Time',
},
],
filters: [
{
Expand All @@ -85,10 +89,16 @@ const mockedProps = {
paginate: true,
operator: 'eq',
},
{
Header: 'Time',
id: 'time',
input: 'datetime_range',
operator: 'between',
},
],
data: [
{ id: 1, name: 'data 1' },
{ id: 2, name: 'data 2' },
{ id: 1, name: 'data 1', age: 10, time: '2020-11-18T07:53:45.354Z' },
{ id: 2, name: 'data 2', age: 1, time: '2020-11-18T07:53:45.354Z' },
],
count: 2,
pageSize: 1,
Expand Down Expand Up @@ -221,15 +231,17 @@ describe('ListView', () => {

expect(mockedProps.bulkActions[0].onSelect.mock.calls[0])
.toMatchInlineSnapshot(`
Array [
Array [
Object {
"id": 1,
"name": "data 1",
},
],
]
`);
Array [
Array [
Object {
"age": 10,
"id": 1,
"name": "data 1",
"time": "2020-11-18T07:53:45.354Z",
},
],
]
`);
});

it('handles bulk actions on all rows', () => {
Expand All @@ -250,19 +262,23 @@ describe('ListView', () => {

expect(mockedProps.bulkActions[0].onSelect.mock.calls[0])
.toMatchInlineSnapshot(`
Array [
Array [
Object {
"id": 1,
"name": "data 1",
},
Object {
"id": 2,
"name": "data 2",
},
],
]
`);
Array [
Array [
Object {
"age": 10,
"id": 1,
"name": "data 1",
"time": "2020-11-18T07:53:45.354Z",
},
Object {
"age": 1,
"id": 2,
"name": "data 2",
"time": "2020-11-18T07:53:45.354Z",
},
],
]
`);
});

it('allows deselecting all', async () => {
Expand Down
37 changes: 37 additions & 0 deletions superset-frontend/src/components/ListView/Filters/Base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* 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 { ReactNode } from 'react';
import { styled } from '@superset-ui/core';

export interface BaseFilter {
Header: ReactNode;
initialValue: any;
}

export const FilterContainer = styled.div`
display: inline-flex;
margin-right: 2em;
font-size: ${({ theme }) => theme.typography.sizes.s}px;
`;

export const FilterTitle = styled.label`
font-weight: bold;
line-height: 27px;
margin: 0 0.4em 0 0;
`;
75 changes: 75 additions & 0 deletions superset-frontend/src/components/ListView/Filters/DateRange.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* 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, { useState, useMemo } from 'react';
import moment, { Moment } from 'moment';
import { styled } from '@superset-ui/core';
import { RangePicker as AntRangePicker } from 'src/common/components/DatePicker';
import { FilterContainer, BaseFilter, FilterTitle } from './Base';

interface DateRangeFilterProps extends BaseFilter {
onSubmit: (val: number[]) => void;
name: string;
}

type ValueState = [number, number];

const RangePicker = styled(AntRangePicker)`
padding: 0 11px;
transform: translateX(-7px);
`;

const RangeFilterContainer = styled(FilterContainer)`
margin-right: 1em;
`;

export default function DateRangeFilter({
Header,
initialValue,
onSubmit,
}: DateRangeFilterProps) {
const [value, setValue] = useState<ValueState | null>(initialValue ?? null);
const momentValue = useMemo((): [Moment, Moment] | null => {
if (!value || (Array.isArray(value) && !value.length)) return null;
return [moment(value[0]), moment(value[1])];
}, [value]);

return (
<RangeFilterContainer>
<FilterTitle>{Header}:</FilterTitle>
<RangePicker
showTime
bordered={false}
value={momentValue}
onChange={momentRange => {
if (!momentRange) {
setValue(null);
onSubmit([]);
return;
}
const changeValue = [
momentRange[0]?.valueOf() ?? 0,
momentRange[1]?.valueOf() ?? 0,
] as ValueState;
setValue(changeValue);
onSubmit(changeValue);
}}
/>
</RangeFilterContainer>
);
}
61 changes: 61 additions & 0 deletions superset-frontend/src/components/ListView/Filters/Search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* 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, { useState } from 'react';
import SearchInput from 'src/components/SearchInput';
import { FilterContainer, BaseFilter } from './Base';

interface SearchHeaderProps extends BaseFilter {
Header: string;
onSubmit: (val: string) => void;
name: string;
}

export default function SearchFilter({
Header,
name,
initialValue,
onSubmit,
}: SearchHeaderProps) {
const [value, setValue] = useState(initialValue || '');
const handleSubmit = () => {
if (value) {
onSubmit(value);
}
};
const onClear = () => {
setValue('');
onSubmit('');
};

return (
<FilterContainer>
<SearchInput
data-test="filters-search"
placeholder={Header}
name={name}
value={value}
onChange={e => {
setValue(e.currentTarget.value);
}}
onSubmit={handleSubmit}
onClear={onClear}
/>
</FilterContainer>
);
}
Loading

0 comments on commit 0117247

Please sign in to comment.