mirror of https://github.com/apache/superset.git
refactor: Changes the list views to use the new Select component (#16393)
* chore: Change the list views to use the new Select component * Fix Cypress tests * Enables search for all controls * Adjusts controls width * Removes 'Me' and keeps the logged user on top * Fixes tests * Uses the borderless version for the filters * Fixes the tests * Reverts the Select theme to the default * Rebases and fixes js error * Fixes failing test * Removes unused withTheme
This commit is contained in:
parent
596e1cdf9b
commit
b6d78bf4f2
|
@ -27,44 +27,34 @@ describe('chart card view filters', () => {
|
|||
|
||||
it('should filter by owners correctly', () => {
|
||||
// filter by owners
|
||||
cy.get('.Select__control').first().click();
|
||||
cy.get('.Select__menu').contains('alpha user').click();
|
||||
cy.get('[data-test="filters-select"]').first().click();
|
||||
cy.get('.rc-virtual-list').contains('alpha user').click();
|
||||
cy.get('[data-test="styled-card"]').should('not.exist');
|
||||
cy.get('.Select__control').first().click();
|
||||
cy.get('.Select__menu').contains('gamma user').click();
|
||||
cy.get('[data-test="filters-select"]').first().click();
|
||||
cy.get('.rc-virtual-list').contains('gamma user').click();
|
||||
cy.get('[data-test="styled-card"]').should('not.exist');
|
||||
});
|
||||
|
||||
it('should filter by me correctly', () => {
|
||||
// filter by me
|
||||
cy.get('.Select__control').first().click();
|
||||
cy.get('.Select__menu').contains('me').click();
|
||||
cy.get('[data-test="styled-card"]').its('length').should('be.gt', 0);
|
||||
cy.get('.Select__control').eq(1).click();
|
||||
cy.get('.Select__menu').contains('me').click();
|
||||
cy.get('[data-test="styled-card"]').its('length').should('be.gt', 0);
|
||||
});
|
||||
|
||||
it('should filter by created by correctly', () => {
|
||||
// filter by created by
|
||||
cy.get('.Select__control').eq(1).click();
|
||||
cy.get('.Select__menu').contains('alpha user').click();
|
||||
cy.get('[data-test="filters-select"]').eq(1).click();
|
||||
cy.get('.rc-virtual-list').contains('alpha user').click();
|
||||
cy.get('.ant-card').should('not.exist');
|
||||
cy.get('.Select__control').eq(1).click();
|
||||
cy.get('.Select__menu').contains('gamma user').click();
|
||||
cy.get('[data-test="filters-select"]').eq(1).click();
|
||||
cy.get('.rc-virtual-list').contains('gamma user').click();
|
||||
cy.get('[data-test="styled-card"]').should('not.exist');
|
||||
});
|
||||
|
||||
xit('should filter by viz type correctly', () => {
|
||||
// filter by viz type
|
||||
cy.get('.Select__control').eq(2).click();
|
||||
cy.get('.Select__menu').contains('area').click({ timeout: 5000 });
|
||||
cy.get('[data-test="filters-select"]').eq(2).click();
|
||||
cy.get('.rc-virtual-list').contains('area').click({ timeout: 5000 });
|
||||
cy.get('[data-test="styled-card"]').its('length').should('be.gt', 0);
|
||||
cy.get('[data-test="styled-card"]')
|
||||
.contains("World's Pop Growth")
|
||||
.should('be.visible');
|
||||
cy.get('.Select__control').eq(2).click();
|
||||
cy.get('.Select__control').eq(2).type('world_map{enter}');
|
||||
cy.get('[data-test="filters-select"]').eq(2).click();
|
||||
cy.get('[data-test="filters-select"]').eq(2).type('world_map{enter}');
|
||||
cy.get('[data-test="styled-card"]').should('have.length', 1);
|
||||
cy.get('[data-test="styled-card"]')
|
||||
.contains('% Rural')
|
||||
|
@ -73,14 +63,16 @@ describe('chart card view filters', () => {
|
|||
|
||||
it('should filter by datasource correctly', () => {
|
||||
// filter by datasource
|
||||
cy.get('.Select__control').eq(3).click();
|
||||
cy.get('.Select__menu').contains('unicode_test').click();
|
||||
cy.get('[data-test="filters-select"]').eq(3).click();
|
||||
cy.get('.rc-virtual-list').contains('unicode_test').click();
|
||||
cy.get('[data-test="styled-card"]').should('have.length', 1);
|
||||
cy.get('[data-test="styled-card"]')
|
||||
.contains('Unicode Cloud')
|
||||
.should('be.visible');
|
||||
cy.get('.Select__control').eq(2).click();
|
||||
cy.get('.Select__control').eq(2).type('energy_usage{enter}{enter}');
|
||||
cy.get('[data-test="filters-select"]').eq(2).click();
|
||||
cy.get('[data-test="filters-select"]')
|
||||
.eq(2)
|
||||
.type('energy_usage{enter}{enter}');
|
||||
cy.get('[data-test="styled-card"]').its('length').should('be.gt', 0);
|
||||
});
|
||||
});
|
||||
|
@ -94,57 +86,49 @@ describe('chart list view filters', () => {
|
|||
|
||||
it('should filter by owners correctly', () => {
|
||||
// filter by owners
|
||||
cy.get('.Select__control').first().click();
|
||||
cy.get('.Select__menu').contains('alpha user').click();
|
||||
cy.get('[data-test="filters-select"]').first().click();
|
||||
cy.get('.rc-virtual-list').contains('alpha user').click();
|
||||
cy.get('[data-test="table-row"]').should('not.exist');
|
||||
cy.get('.Select__control').first().click();
|
||||
cy.get('.Select__menu').contains('gamma user').click();
|
||||
cy.get('[data-test="filters-select"]').first().click();
|
||||
cy.get('.rc-virtual-list').contains('gamma user').click();
|
||||
cy.get('[data-test="table-row"]').should('not.exist');
|
||||
});
|
||||
|
||||
it('should filter by me correctly', () => {
|
||||
// filter by me
|
||||
cy.get('.Select__control').first().click();
|
||||
cy.get('.Select__menu').contains('me').click();
|
||||
cy.get('[data-test="table-row"]').its('length').should('be.gt', 0);
|
||||
cy.get('.Select__control').eq(1).click();
|
||||
cy.get('.Select__menu').contains('me').click();
|
||||
cy.get('[data-test="table-row"]').its('length').should('be.gt', 0);
|
||||
});
|
||||
|
||||
it('should filter by created by correctly', () => {
|
||||
// filter by created by
|
||||
cy.get('.Select__control').eq(1).click();
|
||||
cy.get('.Select__menu').contains('alpha user').click();
|
||||
cy.get('[data-test="filters-select"]').eq(1).click();
|
||||
cy.get('.rc-virtual-list').contains('alpha user').click();
|
||||
cy.get('[data-test="table-row"]').should('not.exist');
|
||||
cy.get('.Select__control').eq(1).click();
|
||||
cy.get('.Select__menu').contains('gamma user').click();
|
||||
cy.get('[data-test="filters-select"]').eq(1).click();
|
||||
cy.get('.rc-virtual-list').contains('gamma user').click();
|
||||
cy.get('[data-test="table-row"]').should('not.exist');
|
||||
});
|
||||
|
||||
// this is flaky, but seems to fail along with the card view test of the same name
|
||||
xit('should filter by viz type correctly', () => {
|
||||
// filter by viz type
|
||||
cy.get('.Select__control').eq(2).click();
|
||||
cy.get('.Select__menu').contains('area').click({ timeout: 5000 });
|
||||
cy.get('[data-test="filters-select"]').eq(2).click();
|
||||
cy.get('.rc-virtual-list').contains('area').click({ timeout: 5000 });
|
||||
cy.get('[data-test="table-row"]').its('length').should('be.gt', 0);
|
||||
cy.get('[data-test="table-row"]')
|
||||
.contains("World's Pop Growth")
|
||||
.should('exist');
|
||||
cy.get('.Select__control').eq(2).click();
|
||||
cy.get('.Select__control').eq(2).type('world_map{enter}');
|
||||
cy.get('[data-test="filters-select"]').eq(2).click();
|
||||
cy.get('[data-test="filters-select"]').eq(2).type('world_map{enter}');
|
||||
cy.get('[data-test="table-row"]').should('have.length', 1);
|
||||
cy.get('[data-test="table-row"]').contains('% Rural').should('exist');
|
||||
});
|
||||
|
||||
it('should filter by datasource correctly', () => {
|
||||
// filter by datasource
|
||||
cy.get('.Select__control').eq(3).click();
|
||||
cy.get('.Select__menu').contains('unicode_test').click();
|
||||
cy.get('[data-test="filters-select"]').eq(3).click();
|
||||
cy.get('.rc-virtual-list').contains('unicode_test').click();
|
||||
cy.get('[data-test="table-row"]').should('have.length', 1);
|
||||
cy.get('[data-test="table-row"]').contains('Unicode Cloud').should('exist');
|
||||
cy.get('.Select__control').eq(3).click();
|
||||
cy.get('.Select__control').eq(3).type('energy_usage{enter}{enter}');
|
||||
cy.get('[data-test="filters-select"]').eq(3).click();
|
||||
cy.get('[data-test="filters-select"]')
|
||||
.eq(3)
|
||||
.type('energy_usage{enter}{enter}');
|
||||
cy.get('[data-test="table-row"]').its('length').should('be.gt', 0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -27,44 +27,34 @@ describe('dashboard filters card view', () => {
|
|||
|
||||
it('should filter by owners correctly', () => {
|
||||
// filter by owners
|
||||
cy.get('.Select__control').first().click();
|
||||
cy.get('.Select__menu').contains('alpha user').click();
|
||||
cy.get('[data-test="filters-select"]').first().click();
|
||||
cy.get('.rc-virtual-list').contains('alpha user').click();
|
||||
cy.get('[data-test="styled-card"]').should('not.exist');
|
||||
cy.get('.Select__control').first().click();
|
||||
cy.get('.Select__menu').contains('gamma user').click();
|
||||
cy.get('[data-test="filters-select"]').first().click();
|
||||
cy.get('.rc-virtual-list').contains('gamma user').click();
|
||||
cy.get('[data-test="styled-card"]').should('not.exist');
|
||||
});
|
||||
|
||||
it('should filter by me correctly', () => {
|
||||
// filter by me
|
||||
cy.get('.Select__control').first().click();
|
||||
cy.get('.Select__menu').contains('me').click();
|
||||
cy.get('[data-test="styled-card"]').its('length').should('be.gt', 0);
|
||||
cy.get('.Select__control').eq(1).click();
|
||||
cy.get('.Select__menu').contains('me').click();
|
||||
cy.get('[data-test="styled-card"]').its('length').should('be.gt', 0);
|
||||
});
|
||||
|
||||
it('should filter by created by correctly', () => {
|
||||
// filter by created by
|
||||
cy.get('.Select__control').eq(1).click();
|
||||
cy.get('.Select__menu').contains('alpha user').click();
|
||||
cy.get('[data-test="filters-select"]').eq(1).click();
|
||||
cy.get('.rc-virtual-list').contains('alpha user').click();
|
||||
cy.get('.ant-card').should('not.exist');
|
||||
cy.get('.Select__control').eq(1).click();
|
||||
cy.get('.Select__menu').contains('gamma user').click();
|
||||
cy.get('[data-test="filters-select"]').eq(1).click();
|
||||
cy.get('.rc-virtual-list').contains('gamma user').click();
|
||||
cy.get('.ant-card').should('not.exist');
|
||||
});
|
||||
|
||||
it('should filter by published correctly', () => {
|
||||
// filter by published
|
||||
cy.get('.Select__control').eq(2).click();
|
||||
cy.get('.Select__menu').contains('Published').click({ timeout: 5000 });
|
||||
cy.get('[data-test="filters-select"]').eq(2).click();
|
||||
cy.get('.rc-virtual-list').contains('Published').click({ timeout: 5000 });
|
||||
cy.get('[data-test="styled-card"]').should('have.length', 3);
|
||||
cy.get('[data-test="styled-card"]')
|
||||
.contains('USA Births Names')
|
||||
.should('be.visible');
|
||||
cy.get('.Select__control').eq(1).click();
|
||||
cy.get('.Select__control').eq(1).type('unpub{enter}');
|
||||
cy.get('[data-test="filters-select"]').eq(1).click();
|
||||
cy.get('[data-test="filters-select"]').eq(1).type('unpub{enter}');
|
||||
cy.get('[data-test="styled-card"]').should('have.length', 3);
|
||||
});
|
||||
});
|
||||
|
@ -78,44 +68,34 @@ describe('dashboard filters list view', () => {
|
|||
|
||||
it('should filter by owners correctly', () => {
|
||||
// filter by owners
|
||||
cy.get('.Select__control').first().click();
|
||||
cy.get('.Select__menu').contains('alpha user').click();
|
||||
cy.get('[data-test="filters-select"]').first().click();
|
||||
cy.get('.rc-virtual-list').contains('alpha user').click();
|
||||
cy.get('[data-test="table-row"]').should('not.exist');
|
||||
cy.get('.Select__control').first().click();
|
||||
cy.get('.Select__menu').contains('gamma user').click();
|
||||
cy.get('[data-test="filters-select"]').first().click();
|
||||
cy.get('.rc-virtual-list').contains('gamma user').click();
|
||||
cy.get('[data-test="table-row"]').should('not.exist');
|
||||
});
|
||||
|
||||
it('should filter by me correctly', () => {
|
||||
// filter by me
|
||||
cy.get('.Select__control').first().click();
|
||||
cy.get('.Select__menu').contains('me').click();
|
||||
cy.get('[data-test="table-row"]').its('length').should('be.gt', 0);
|
||||
cy.get('.Select__control').eq(1).click();
|
||||
cy.get('.Select__menu').contains('me').click();
|
||||
cy.get('[data-test="table-row"]').its('length').should('be.gt', 0);
|
||||
});
|
||||
|
||||
it('should filter by created by correctly', () => {
|
||||
// filter by created by
|
||||
cy.get('.Select__control').eq(1).click();
|
||||
cy.get('.Select__menu').contains('alpha user').click();
|
||||
cy.get('[data-test="filters-select"]').eq(1).click();
|
||||
cy.get('.rc-virtual-list').contains('alpha user').click();
|
||||
cy.get('[data-test="table-row"]').should('not.exist');
|
||||
cy.get('.Select__control').eq(1).click();
|
||||
cy.get('.Select__menu').contains('gamma user').click();
|
||||
cy.get('[data-test="filters-select"]').eq(1).click();
|
||||
cy.get('.rc-virtual-list').contains('gamma user').click();
|
||||
cy.get('[data-test="table-row"]').should('not.exist');
|
||||
});
|
||||
|
||||
it('should filter by published correctly', () => {
|
||||
// filter by published
|
||||
cy.get('.Select__control').eq(2).click();
|
||||
cy.get('.Select__menu').contains('Published').click();
|
||||
cy.get('[data-test="filters-select"]').eq(2).click();
|
||||
cy.get('.rc-virtual-list').contains('Published').click();
|
||||
cy.get('[data-test="table-row"]').should('have.length', 3);
|
||||
cy.get('[data-test="table-row"]')
|
||||
.contains('USA Births Names')
|
||||
.should('be.visible');
|
||||
cy.get('.Select__control').eq(2).click();
|
||||
cy.get('.Select__control').eq(2).type('unpub{enter}');
|
||||
cy.get('[data-test="filters-select"]').eq(2).click();
|
||||
cy.get('[data-test="filters-select"]').eq(2).type('unpub{enter}');
|
||||
cy.get('[data-test="table-row"]').should('have.length', 3);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,6 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { DatePicker as AntdDatePicker } from 'antd';
|
||||
import { styled } from '@superset-ui/core';
|
||||
|
||||
const AntdRangePicker = AntdDatePicker.RangePicker;
|
||||
|
||||
export const RangePicker = styled(AntdRangePicker)`
|
||||
border-radius: ${({ theme }) => theme.gridUnit}px;
|
||||
`;
|
||||
|
||||
export const { RangePicker } = AntdDatePicker;
|
||||
export const DatePicker = AntdDatePicker;
|
||||
|
|
|
@ -16,24 +16,21 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { styled, withTheme, SupersetThemeProps, t } from '@superset-ui/core';
|
||||
import { PartialThemeConfig, Select } from 'src/components/Select';
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { styled, t } from '@superset-ui/core';
|
||||
import { Select } from 'src/components';
|
||||
import { FormLabel } from 'src/components/Form';
|
||||
import { SELECT_WIDTH } from './utils';
|
||||
import { CardSortSelectOption, FetchDataConfig, SortColumn } from './types';
|
||||
import { filterSelectStyles } from './utils';
|
||||
|
||||
const SortTitle = styled.label`
|
||||
font-weight: bold;
|
||||
line-height: 27px;
|
||||
margin: 0 0.4em 0 0;
|
||||
`;
|
||||
|
||||
const SortContainer = styled.div`
|
||||
display: inline-flex;
|
||||
font-size: ${({ theme }) => theme.typography.sizes.s}px;
|
||||
padding-top: ${({ theme }) => theme.gridUnit}px;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
width: ${SELECT_WIDTH}px;
|
||||
`;
|
||||
|
||||
interface CardViewSelectSortProps {
|
||||
onChange: (conf: FetchDataConfig) => any;
|
||||
options: Array<CardSortSelectOption>;
|
||||
|
@ -42,43 +39,6 @@ interface CardViewSelectSortProps {
|
|||
pageSize: number;
|
||||
}
|
||||
|
||||
interface StyledSelectProps {
|
||||
onChange: (value: CardSortSelectOption) => void;
|
||||
options: CardSortSelectOption[];
|
||||
selectStyles: any;
|
||||
theme: SupersetThemeProps['theme'];
|
||||
value: CardSortSelectOption;
|
||||
}
|
||||
|
||||
function StyledSelect({
|
||||
onChange,
|
||||
options,
|
||||
selectStyles,
|
||||
theme,
|
||||
value,
|
||||
}: StyledSelectProps) {
|
||||
const filterSelectTheme: PartialThemeConfig = {
|
||||
spacing: {
|
||||
baseUnit: 1,
|
||||
fontSize: theme.typography.sizes.s,
|
||||
minWidth: '5em',
|
||||
},
|
||||
};
|
||||
return (
|
||||
<Select
|
||||
data-test="card-sort-select"
|
||||
clearable={false}
|
||||
onChange={onChange}
|
||||
options={options}
|
||||
stylesConfig={selectStyles}
|
||||
themeConfig={filterSelectTheme}
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledCardSortSelect = withTheme(StyledSelect);
|
||||
|
||||
export const CardSortSelect = ({
|
||||
initialSort,
|
||||
onChange,
|
||||
|
@ -87,25 +47,45 @@ export const CardSortSelect = ({
|
|||
pageSize,
|
||||
}: CardViewSelectSortProps) => {
|
||||
const defaultSort =
|
||||
initialSort && options.find(({ id }) => id === initialSort[0].id);
|
||||
const [selectedOption, setSelectedOption] = useState<CardSortSelectOption>(
|
||||
defaultSort || options[0],
|
||||
(initialSort && options.find(({ id }) => id === initialSort[0].id)) ||
|
||||
options[0];
|
||||
|
||||
const [value, setValue] = useState({
|
||||
label: defaultSort.label,
|
||||
value: defaultSort.value,
|
||||
});
|
||||
|
||||
const formattedOptions = useMemo(
|
||||
() => options.map(option => ({ label: option.label, value: option.value })),
|
||||
[options],
|
||||
);
|
||||
|
||||
const handleOnChange = (selected: CardSortSelectOption) => {
|
||||
setSelectedOption(selected);
|
||||
const sortBy = [{ id: selected.id, desc: selected.desc }];
|
||||
onChange({ pageIndex, pageSize, sortBy, filters: [] });
|
||||
const handleOnChange = (selected: { label: string; value: string }) => {
|
||||
setValue(selected);
|
||||
const originalOption = options.find(
|
||||
({ value }) => value === selected.value,
|
||||
);
|
||||
if (originalOption) {
|
||||
const sortBy = [
|
||||
{
|
||||
id: originalOption.id,
|
||||
desc: originalOption.desc,
|
||||
},
|
||||
];
|
||||
onChange({ pageIndex, pageSize, sortBy, filters: [] });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SortContainer>
|
||||
<SortTitle>{t('Sort:')}</SortTitle>
|
||||
<StyledCardSortSelect
|
||||
<Select
|
||||
ariaLabel={t('Sort')}
|
||||
header={<FormLabel>{t('Sort')}</FormLabel>}
|
||||
labelInValue
|
||||
onChange={(value: CardSortSelectOption) => handleOnChange(value)}
|
||||
options={options}
|
||||
selectStyles={filterSelectStyles}
|
||||
value={selectedOption}
|
||||
options={formattedOptions}
|
||||
showSearch
|
||||
value={value}
|
||||
/>
|
||||
</SortContainer>
|
||||
);
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
import { ReactNode } from 'react';
|
||||
import { styled } from '@superset-ui/core';
|
||||
import { SELECT_WIDTH } from 'src/components/ListView/utils';
|
||||
|
||||
export interface BaseFilter {
|
||||
Header: ReactNode;
|
||||
|
@ -26,12 +27,7 @@ export interface BaseFilter {
|
|||
|
||||
export const FilterContainer = styled.div`
|
||||
display: inline-flex;
|
||||
margin-right: 2em;
|
||||
font-size: ${({ theme }) => theme.typography.sizes.s}px;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const FilterTitle = styled.label`
|
||||
font-weight: bold;
|
||||
margin: 0 0.4em 0 0;
|
||||
width: ${SELECT_WIDTH}px;
|
||||
`;
|
||||
|
|
|
@ -19,8 +19,9 @@
|
|||
import React, { useState, useMemo } from 'react';
|
||||
import moment, { Moment } from 'moment';
|
||||
import { styled } from '@superset-ui/core';
|
||||
import { RangePicker as AntRangePicker } from 'src/components/DatePicker';
|
||||
import { FilterContainer, BaseFilter, FilterTitle } from './Base';
|
||||
import { RangePicker } from 'src/components/DatePicker';
|
||||
import { FormLabel } from 'src/components/Form';
|
||||
import { BaseFilter } from './Base';
|
||||
|
||||
interface DateRangeFilterProps extends BaseFilter {
|
||||
onSubmit: (val: number[]) => void;
|
||||
|
@ -29,13 +30,12 @@ interface DateRangeFilterProps extends BaseFilter {
|
|||
|
||||
type ValueState = [number, number];
|
||||
|
||||
const RangePicker = styled(AntRangePicker)`
|
||||
padding: 0 11px;
|
||||
transform: translateX(-7px);
|
||||
`;
|
||||
|
||||
const RangeFilterContainer = styled(FilterContainer)`
|
||||
margin-right: 1em;
|
||||
const RangeFilterContainer = styled.div`
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
width: 360px;
|
||||
`;
|
||||
|
||||
export default function DateRangeFilter({
|
||||
|
@ -51,10 +51,9 @@ export default function DateRangeFilter({
|
|||
|
||||
return (
|
||||
<RangeFilterContainer>
|
||||
<FilterTitle>{Header}:</FilterTitle>
|
||||
<FormLabel>{Header}</FormLabel>
|
||||
<RangePicker
|
||||
showTime
|
||||
bordered={false}
|
||||
value={momentValue}
|
||||
onChange={momentRange => {
|
||||
if (!momentRange) {
|
||||
|
|
|
@ -17,8 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import SearchInput from 'src/components/SearchInput';
|
||||
import { FilterContainer, BaseFilter } from './Base';
|
||||
import { t, styled } from '@superset-ui/core';
|
||||
import Icons from 'src/components/Icons';
|
||||
import { AntdInput as Input } from 'src/common/components';
|
||||
import { SELECT_WIDTH } from 'src/components/ListView/utils';
|
||||
import { FormLabel } from 'src/components/Form';
|
||||
import { BaseFilter } from './Base';
|
||||
|
||||
interface SearchHeaderProps extends BaseFilter {
|
||||
Header: string;
|
||||
|
@ -26,6 +30,18 @@ interface SearchHeaderProps extends BaseFilter {
|
|||
name: string;
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
width: ${SELECT_WIDTH}px;
|
||||
`;
|
||||
|
||||
const SearchIcon = styled(Icons.Search)`
|
||||
color: ${({ theme }) => theme.colors.grayscale.light1};
|
||||
`;
|
||||
|
||||
const StyledInput = styled(Input)`
|
||||
border-radius: ${({ theme }) => theme.gridUnit}px;
|
||||
`;
|
||||
|
||||
export default function SearchFilter({
|
||||
Header,
|
||||
name,
|
||||
|
@ -38,28 +54,27 @@ export default function SearchFilter({
|
|||
onSubmit(value.trim());
|
||||
}
|
||||
};
|
||||
const onClear = () => {
|
||||
setValue('');
|
||||
onSubmit('');
|
||||
};
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(e.currentTarget.value);
|
||||
if (e.currentTarget.value === '') {
|
||||
onClear();
|
||||
onSubmit('');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FilterContainer>
|
||||
<SearchInput
|
||||
<Container>
|
||||
<FormLabel>{Header}</FormLabel>
|
||||
<StyledInput
|
||||
allowClear
|
||||
data-test="filters-search"
|
||||
placeholder={Header}
|
||||
placeholder={t('Type a value')}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
onSubmit={handleSubmit}
|
||||
onClear={onClear}
|
||||
onPressEnter={handleSubmit}
|
||||
onBlur={handleSubmit}
|
||||
prefix={<SearchIcon iconSize="l" />}
|
||||
/>
|
||||
</FilterContainer>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,138 +16,76 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { withTheme, SupersetThemeProps } from '@superset-ui/core';
|
||||
import {
|
||||
Select,
|
||||
PaginatedSelect,
|
||||
PartialThemeConfig,
|
||||
} from 'src/components/Select';
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { t } from '@superset-ui/core';
|
||||
import { Select } from 'src/components';
|
||||
import { Filter, SelectOption } from 'src/components/ListView/types';
|
||||
import { filterSelectStyles } from 'src/components/ListView/utils';
|
||||
import { FilterContainer, BaseFilter, FilterTitle } from './Base';
|
||||
import { FormLabel } from 'src/components/Form';
|
||||
import { FilterContainer, BaseFilter } from './Base';
|
||||
|
||||
interface SelectFilterProps extends BaseFilter {
|
||||
emptyLabel?: string;
|
||||
fetchSelects?: Filter['fetchSelects'];
|
||||
name?: string;
|
||||
onSelect: (selected: any) => any;
|
||||
onSelect: (selected: SelectOption | undefined) => void;
|
||||
paginate?: boolean;
|
||||
selects: Filter['selects'];
|
||||
theme: SupersetThemeProps['theme'];
|
||||
}
|
||||
|
||||
const CLEAR_SELECT_FILTER_VALUE = 'CLEAR_SELECT_FILTER_VALUE';
|
||||
|
||||
function SelectFilter({
|
||||
Header,
|
||||
emptyLabel = 'None',
|
||||
name,
|
||||
fetchSelects,
|
||||
initialValue,
|
||||
onSelect,
|
||||
paginate = false,
|
||||
selects = [],
|
||||
theme,
|
||||
}: SelectFilterProps) {
|
||||
const filterSelectTheme: PartialThemeConfig = {
|
||||
spacing: {
|
||||
baseUnit: 2,
|
||||
fontSize: theme.typography.sizes.s,
|
||||
minWidth: '5em',
|
||||
},
|
||||
};
|
||||
const [selectedOption, setSelectedOption] = useState(initialValue);
|
||||
|
||||
const clearFilterSelect = {
|
||||
label: emptyLabel,
|
||||
value: CLEAR_SELECT_FILTER_VALUE,
|
||||
};
|
||||
|
||||
const options = [clearFilterSelect, ...selects];
|
||||
let initialOption = clearFilterSelect;
|
||||
|
||||
// Set initial value if not async
|
||||
if (!fetchSelects) {
|
||||
const matchingOption = options.find(x => x.value === initialValue);
|
||||
|
||||
if (matchingOption) {
|
||||
initialOption = matchingOption;
|
||||
}
|
||||
}
|
||||
|
||||
const [selectedOption, setSelectedOption] = useState(initialOption);
|
||||
const onChange = (selected: SelectOption | null) => {
|
||||
if (selected === null) return;
|
||||
const onChange = (selected: SelectOption) => {
|
||||
onSelect(
|
||||
selected.value === CLEAR_SELECT_FILTER_VALUE ? undefined : selected.value,
|
||||
selected ? { label: selected.label, value: selected.value } : undefined,
|
||||
);
|
||||
setSelectedOption(selected);
|
||||
};
|
||||
|
||||
const fetchAndFormatSelects = async (
|
||||
inputValue: string,
|
||||
loadedOptions: SelectOption[],
|
||||
{ page }: { page: number },
|
||||
) => {
|
||||
// only include clear filter when filter value does not exist
|
||||
let result = inputValue || page > 0 ? [] : [clearFilterSelect];
|
||||
let hasMore = paginate;
|
||||
if (fetchSelects) {
|
||||
const selectValues = await fetchSelects(inputValue, page);
|
||||
// update matching option at initial load
|
||||
if (!selectValues.length) {
|
||||
hasMore = false;
|
||||
}
|
||||
result = [...result, ...selectValues];
|
||||
|
||||
const matchingOption = result.find(x => x.value === initialValue);
|
||||
|
||||
if (matchingOption) {
|
||||
setSelectedOption(matchingOption);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
options: result,
|
||||
hasMore,
|
||||
additional: {
|
||||
page: page + 1,
|
||||
},
|
||||
};
|
||||
const onClear = () => {
|
||||
onSelect(undefined);
|
||||
setSelectedOption(undefined);
|
||||
};
|
||||
|
||||
const fetchAndFormatSelects = useMemo(
|
||||
() => async (inputValue: string, page: number, pageSize: number) => {
|
||||
if (fetchSelects) {
|
||||
const selectValues = await fetchSelects(inputValue, page, pageSize);
|
||||
return {
|
||||
data: selectValues.data,
|
||||
totalCount: selectValues.totalCount,
|
||||
};
|
||||
}
|
||||
return {
|
||||
data: [],
|
||||
totalCount: 0,
|
||||
};
|
||||
},
|
||||
[fetchSelects],
|
||||
);
|
||||
|
||||
return (
|
||||
<FilterContainer>
|
||||
<FilterTitle>{Header}:</FilterTitle>
|
||||
{fetchSelects ? (
|
||||
<PaginatedSelect
|
||||
data-test="filters-select"
|
||||
defaultOptions
|
||||
themeConfig={filterSelectTheme}
|
||||
stylesConfig={filterSelectStyles}
|
||||
// @ts-ignore
|
||||
value={selectedOption}
|
||||
// @ts-ignore
|
||||
onChange={onChange}
|
||||
// @ts-ignore
|
||||
loadOptions={fetchAndFormatSelects}
|
||||
placeholder={emptyLabel}
|
||||
clearable={false}
|
||||
additional={{
|
||||
page: 0,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Select
|
||||
data-test="filters-select"
|
||||
themeConfig={filterSelectTheme}
|
||||
stylesConfig={filterSelectStyles}
|
||||
value={selectedOption}
|
||||
options={options}
|
||||
onChange={onChange}
|
||||
clearable={false}
|
||||
/>
|
||||
)}
|
||||
<Select
|
||||
allowClear
|
||||
ariaLabel={typeof Header === 'string' ? Header : name || t('Filter')}
|
||||
labelInValue
|
||||
data-test="filters-select"
|
||||
header={<FormLabel>{Header}</FormLabel>}
|
||||
onChange={onChange}
|
||||
onClear={onClear}
|
||||
options={fetchSelects ? fetchAndFormatSelects : selects}
|
||||
placeholder={t('Select or type a value')}
|
||||
showSearch
|
||||
value={selectedOption}
|
||||
/>
|
||||
</FilterContainer>
|
||||
);
|
||||
}
|
||||
export default withTheme(SelectFilter);
|
||||
export default SelectFilter;
|
||||
|
|
|
@ -17,12 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { styled, withTheme } from '@superset-ui/core';
|
||||
import { withTheme } from '@superset-ui/core';
|
||||
|
||||
import {
|
||||
FilterValue,
|
||||
Filters,
|
||||
InternalFilter,
|
||||
SelectOption,
|
||||
} from 'src/components/ListView/types';
|
||||
import SearchFilter from './Search';
|
||||
import SelectFilter from './Select';
|
||||
|
@ -34,42 +35,28 @@ interface UIFiltersProps {
|
|||
updateFilterValue: (id: number, value: FilterValue['value']) => void;
|
||||
}
|
||||
|
||||
const FilterWrapper = styled.div`
|
||||
display: inline-block;
|
||||
`;
|
||||
|
||||
function UIFilters({
|
||||
filters,
|
||||
internalFilters = [],
|
||||
updateFilterValue,
|
||||
}: UIFiltersProps) {
|
||||
return (
|
||||
<FilterWrapper>
|
||||
<>
|
||||
{filters.map(
|
||||
(
|
||||
{
|
||||
Header,
|
||||
fetchSelects,
|
||||
id,
|
||||
input,
|
||||
paginate,
|
||||
selects,
|
||||
unfilteredLabel,
|
||||
},
|
||||
index,
|
||||
) => {
|
||||
({ Header, fetchSelects, id, input, paginate, selects }, index) => {
|
||||
const initialValue =
|
||||
internalFilters[index] && internalFilters[index].value;
|
||||
if (input === 'select') {
|
||||
return (
|
||||
<SelectFilter
|
||||
Header={Header}
|
||||
emptyLabel={unfilteredLabel}
|
||||
fetchSelects={fetchSelects}
|
||||
initialValue={initialValue}
|
||||
key={id}
|
||||
name={id}
|
||||
onSelect={(value: any) => updateFilterValue(index, value)}
|
||||
onSelect={(option: SelectOption | undefined) =>
|
||||
updateFilterValue(index, option)
|
||||
}
|
||||
paginate={paginate}
|
||||
selects={selects}
|
||||
/>
|
||||
|
@ -100,7 +87,7 @@ function UIFilters({
|
|||
return null;
|
||||
},
|
||||
)}
|
||||
</FilterWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -377,8 +377,8 @@ describe('ListView', () => {
|
|||
expect(wrapper.find(ListViewFilters)).toExist();
|
||||
});
|
||||
|
||||
it('fetched async filter values on mount', () => {
|
||||
expect(fetchSelectsMock).toHaveBeenCalled();
|
||||
it('does not fetch async filter values on mount', () => {
|
||||
expect(fetchSelectsMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls fetchData on filter', () => {
|
||||
|
@ -387,7 +387,7 @@ describe('ListView', () => {
|
|||
.find('[data-test="filters-select"]')
|
||||
.first()
|
||||
.props()
|
||||
.onChange({ value: 'bar' });
|
||||
.onChange({ label: 'bar', value: 'bar' });
|
||||
});
|
||||
|
||||
act(() => {
|
||||
|
@ -395,13 +395,15 @@ describe('ListView', () => {
|
|||
.find('[data-test="filters-search"]')
|
||||
.first()
|
||||
.props()
|
||||
.onChange({ currentTarget: { value: 'something' } });
|
||||
.onChange({
|
||||
currentTarget: { label: 'something', value: 'something' },
|
||||
});
|
||||
});
|
||||
|
||||
wrapper.update();
|
||||
|
||||
act(() => {
|
||||
wrapper.find('[data-test="search-input"]').last().props().onBlur();
|
||||
wrapper.find('[data-test="filters-search"]').last().props().onBlur();
|
||||
});
|
||||
|
||||
expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
|
@ -411,7 +413,10 @@ describe('ListView', () => {
|
|||
Object {
|
||||
"id": "id",
|
||||
"operator": "eq",
|
||||
"value": "bar",
|
||||
"value": Object {
|
||||
"label": "bar",
|
||||
"value": "bar",
|
||||
},
|
||||
},
|
||||
],
|
||||
"pageIndex": 0,
|
||||
|
@ -433,7 +438,10 @@ describe('ListView', () => {
|
|||
Object {
|
||||
"id": "id",
|
||||
"operator": "eq",
|
||||
"value": "bar",
|
||||
"value": Object {
|
||||
"label": "bar",
|
||||
"value": "bar",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"id": "name",
|
||||
|
@ -462,7 +470,7 @@ describe('ListView', () => {
|
|||
});
|
||||
|
||||
await act(async () => {
|
||||
wrapper2.find('[data-test="card-sort-select"]').first().props().onChange({
|
||||
wrapper2.find('[aria-label="Sort"]').first().props().onChange({
|
||||
desc: false,
|
||||
id: 'something',
|
||||
label: 'Alphabetical',
|
||||
|
|
|
@ -50,13 +50,11 @@ const ListViewStyles = styled.div`
|
|||
display: flex;
|
||||
padding-bottom: ${({ theme }) => theme.gridUnit * 4}px;
|
||||
|
||||
.header-left {
|
||||
& .controls {
|
||||
display: flex;
|
||||
flex: 5;
|
||||
}
|
||||
.header-right {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
flex-wrap: wrap;
|
||||
column-gap: ${({ theme }) => theme.gridUnit * 6}px;
|
||||
row-gap: ${({ theme }) => theme.gridUnit * 4}px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,6 +134,7 @@ const bulkSelectColumnConfig = {
|
|||
|
||||
const ViewModeContainer = styled.div`
|
||||
padding-right: ${({ theme }) => theme.gridUnit * 4}px;
|
||||
margin-top: ${({ theme }) => theme.gridUnit * 5 + 1}px;
|
||||
display: inline-block;
|
||||
|
||||
.toggle-button {
|
||||
|
@ -301,10 +300,10 @@ function ListView<T extends object = any>({
|
|||
<ListViewStyles>
|
||||
<div data-test={className} className={`superset-list-view ${className}`}>
|
||||
<div className="header">
|
||||
<div className="header-left">
|
||||
{cardViewEnabled && (
|
||||
<ViewModeToggle mode={viewMode} setMode={setViewMode} />
|
||||
)}
|
||||
{cardViewEnabled && (
|
||||
<ViewModeToggle mode={viewMode} setMode={setViewMode} />
|
||||
)}
|
||||
<div className="controls">
|
||||
{filterable && (
|
||||
<FilterControls
|
||||
filters={filters}
|
||||
|
@ -312,8 +311,6 @@ function ListView<T extends object = any>({
|
|||
updateFilterValue={applyFilterValue}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="header-right">
|
||||
{viewMode === 'card' && cardSortSelectOptions && (
|
||||
<CardSortSelect
|
||||
initialSort={initialSort}
|
||||
|
|
|
@ -53,10 +53,10 @@ export interface Filter {
|
|||
selects?: SelectOption[];
|
||||
onFilterOpen?: () => void;
|
||||
fetchSelects?: (
|
||||
filterValue?: string,
|
||||
pageIndex?: number,
|
||||
pageSize?: number,
|
||||
) => Promise<SelectOption[]>;
|
||||
filterValue: string,
|
||||
page: number,
|
||||
pageSize: number,
|
||||
) => Promise<{ data: SelectOption[]; totalCount: number }>;
|
||||
paginate?: boolean;
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,15 @@ export interface FilterValue {
|
|||
id: string;
|
||||
urlDisplay?: string;
|
||||
operator?: string;
|
||||
value: string | boolean | number | null | undefined | string[] | number[];
|
||||
value:
|
||||
| string
|
||||
| boolean
|
||||
| number
|
||||
| null
|
||||
| undefined
|
||||
| string[]
|
||||
| number[]
|
||||
| { label: string; value: string | number };
|
||||
}
|
||||
|
||||
export interface FetchDataConfig {
|
||||
|
|
|
@ -55,6 +55,8 @@ const RisonParam: QueryParamConfig<string, any> = {
|
|||
: rison.decode(dataStr),
|
||||
};
|
||||
|
||||
export const SELECT_WIDTH = 200;
|
||||
|
||||
export class ListViewError extends Error {
|
||||
name = 'ListViewError';
|
||||
}
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
/**
|
||||
* 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, { SearchInputProps } from '.';
|
||||
|
||||
export default {
|
||||
title: 'SearchInput',
|
||||
component: SearchInput,
|
||||
};
|
||||
|
||||
export const InteractiveSearchInput = ({
|
||||
value,
|
||||
...rest
|
||||
}: SearchInputProps) => {
|
||||
const [currentValue, setCurrentValue] = useState(value);
|
||||
return (
|
||||
<div style={{ width: 230 }}>
|
||||
<SearchInput
|
||||
{...rest}
|
||||
value={currentValue}
|
||||
onChange={e => setCurrentValue(e.target.value)}
|
||||
onClear={() => setCurrentValue('')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
InteractiveSearchInput.args = {
|
||||
value: 'Test',
|
||||
placeholder: 'Enter some text',
|
||||
name: 'search-input',
|
||||
};
|
||||
|
||||
InteractiveSearchInput.argTypes = {
|
||||
onSubmit: { action: 'onSubmit' },
|
||||
onClear: { action: 'onClear' },
|
||||
onChange: { action: 'onChange' },
|
||||
};
|
||||
|
||||
InteractiveSearchInput.story = {
|
||||
parameters: {
|
||||
knobs: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
};
|
|
@ -1,93 +0,0 @@
|
|||
/**
|
||||
* 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 { mount } from 'enzyme';
|
||||
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
|
||||
|
||||
import SearchInput from 'src/components/SearchInput';
|
||||
|
||||
describe('SearchInput', () => {
|
||||
const defaultProps = {
|
||||
onSubmit: jest.fn(),
|
||||
onClear: jest.fn(),
|
||||
onChange: jest.fn(),
|
||||
value: '',
|
||||
};
|
||||
|
||||
const factory = overrideProps => {
|
||||
const props = { ...defaultProps, ...(overrideProps || {}) };
|
||||
return mount(
|
||||
<ThemeProvider theme={supersetTheme}>
|
||||
<SearchInput {...props} />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
};
|
||||
|
||||
let wrapper;
|
||||
|
||||
beforeAll(() => {
|
||||
wrapper = factory();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
defaultProps.onSubmit.mockClear();
|
||||
defaultProps.onClear.mockClear();
|
||||
defaultProps.onChange.mockClear();
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(React.isValidElement(<SearchInput {...defaultProps} />)).toBe(true);
|
||||
});
|
||||
|
||||
const typeSearchInput = value => {
|
||||
wrapper
|
||||
.find('[data-test="search-input"]')
|
||||
.first()
|
||||
.props()
|
||||
.onChange({ currentTarget: { value } });
|
||||
};
|
||||
|
||||
it('submits on enter', () => {
|
||||
typeSearchInput('foo');
|
||||
|
||||
wrapper
|
||||
.find('[data-test="search-input"]')
|
||||
.first()
|
||||
.props()
|
||||
.onKeyDown({ key: 'Enter' });
|
||||
|
||||
expect(defaultProps.onChange).toHaveBeenCalled();
|
||||
expect(defaultProps.onSubmit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('submits on search icon click', () => {
|
||||
typeSearchInput('bar');
|
||||
|
||||
wrapper.find('[data-test="search-submit"]').first().props().onClick();
|
||||
|
||||
expect(defaultProps.onSubmit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('clears on clear icon click', () => {
|
||||
const wrapper2 = factory({ value: 'fizz' });
|
||||
wrapper2.find('[data-test="search-clear"]').first().props().onClick();
|
||||
|
||||
expect(defaultProps.onClear).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -1,108 +0,0 @@
|
|||
/**
|
||||
* 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, useTheme } from '@superset-ui/core';
|
||||
import React from 'react';
|
||||
import Icons from 'src/components/Icons';
|
||||
|
||||
export interface SearchInputProps {
|
||||
onSubmit: () => void;
|
||||
onClear: () => void;
|
||||
value: string;
|
||||
onChange: React.EventHandler<React.ChangeEvent<HTMLInputElement>>;
|
||||
placeholder?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
const SearchInputWrapper = styled.div`
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const StyledInput = styled.input`
|
||||
width: 200px;
|
||||
height: ${({ theme }) => theme.gridUnit * 8}px;
|
||||
background-image: none;
|
||||
border: 1px solid ${({ theme }) => theme.colors.secondary.light2};
|
||||
border-radius: 4px;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
padding: 4px 28px;
|
||||
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const commonStyles = `
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
`;
|
||||
const SearchIcon = styled(Icons.Search)`
|
||||
${commonStyles};
|
||||
top: 4px;
|
||||
left: 2px;
|
||||
`;
|
||||
|
||||
const ClearIcon = styled(Icons.CancelX)`
|
||||
${commonStyles};
|
||||
right: 0px;
|
||||
top: 4px;
|
||||
`;
|
||||
|
||||
export default function SearchInput({
|
||||
onChange,
|
||||
onClear,
|
||||
onSubmit,
|
||||
placeholder = 'Search',
|
||||
name,
|
||||
value,
|
||||
}: SearchInputProps) {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<SearchInputWrapper>
|
||||
<SearchIcon
|
||||
iconColor={theme.colors.grayscale.base}
|
||||
data-test="search-submit"
|
||||
role="button"
|
||||
onClick={() => onSubmit()}
|
||||
/>
|
||||
<StyledInput
|
||||
data-test="search-input"
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'Enter') {
|
||||
onSubmit();
|
||||
}
|
||||
}}
|
||||
onBlur={() => onSubmit()}
|
||||
placeholder={placeholder}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
name={name}
|
||||
/>
|
||||
{value && (
|
||||
<ClearIcon
|
||||
data-test="search-clear"
|
||||
role="button"
|
||||
iconColor={theme.colors.grayscale.base}
|
||||
onClick={() => onClear()}
|
||||
/>
|
||||
)}
|
||||
</SearchInputWrapper>
|
||||
);
|
||||
}
|
|
@ -56,6 +56,8 @@ interface AlertListProps {
|
|||
isReportEnabled: boolean;
|
||||
user: {
|
||||
userId: string | number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
};
|
||||
}
|
||||
const deleteAlerts = makeApi<number[], { message: string }>({
|
||||
|
@ -381,7 +383,7 @@ function AlertList({
|
|||
createErrorHandler(errMsg =>
|
||||
t('An error occurred while fetching created by values: %s', errMsg),
|
||||
),
|
||||
user.userId,
|
||||
user,
|
||||
),
|
||||
paginate: true,
|
||||
},
|
||||
|
|
|
@ -46,6 +46,8 @@ interface AnnotationLayersListProps {
|
|||
addSuccessToast: (msg: string) => void;
|
||||
user: {
|
||||
userId: string | number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -301,7 +303,7 @@ function AnnotationLayersList({
|
|||
errMsg,
|
||||
),
|
||||
),
|
||||
user.userId,
|
||||
user,
|
||||
),
|
||||
paginate: true,
|
||||
},
|
||||
|
|
|
@ -77,42 +77,40 @@ const CONFIRM_OVERWRITE_MESSAGE = t(
|
|||
setupPlugins();
|
||||
const registry = getChartMetadataRegistry();
|
||||
|
||||
const createFetchDatasets = (handleError: (err: Response) => void) => async (
|
||||
const createFetchDatasets = async (
|
||||
filterValue = '',
|
||||
pageIndex?: number,
|
||||
pageSize?: number,
|
||||
page: number,
|
||||
pageSize: number,
|
||||
) => {
|
||||
// add filters if filterValue
|
||||
const filters = filterValue
|
||||
? { filters: [{ col: 'table_name', opr: 'sw', value: filterValue }] }
|
||||
: {};
|
||||
try {
|
||||
const queryParams = rison.encode({
|
||||
columns: ['datasource_name', 'datasource_id'],
|
||||
keys: ['none'],
|
||||
order_column: 'table_name',
|
||||
order_direction: 'asc',
|
||||
...(pageIndex ? { page: pageIndex } : {}),
|
||||
...(pageSize ? { page_size: pageSize } : {}),
|
||||
...filters,
|
||||
});
|
||||
const queryParams = rison.encode({
|
||||
columns: ['datasource_name', 'datasource_id'],
|
||||
keys: ['none'],
|
||||
order_column: 'table_name',
|
||||
order_direction: 'asc',
|
||||
page,
|
||||
page_size: pageSize,
|
||||
...filters,
|
||||
});
|
||||
|
||||
const { json = {} } = await SupersetClient.get({
|
||||
endpoint: `/api/v1/dataset/?q=${queryParams}`,
|
||||
});
|
||||
const { json = {} } = await SupersetClient.get({
|
||||
endpoint: `/api/v1/dataset/?q=${queryParams}`,
|
||||
});
|
||||
|
||||
const datasets = json?.result?.map(
|
||||
({ table_name: tableName, id }: { table_name: string; id: number }) => ({
|
||||
label: tableName,
|
||||
value: id,
|
||||
}),
|
||||
);
|
||||
const datasets = json?.result?.map(
|
||||
({ table_name: tableName, id }: { table_name: string; id: number }) => ({
|
||||
label: tableName,
|
||||
value: id,
|
||||
}),
|
||||
);
|
||||
|
||||
return uniqBy<SelectOption>(datasets, 'value');
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
}
|
||||
return [];
|
||||
return {
|
||||
data: uniqBy<SelectOption>(datasets, 'value'),
|
||||
totalCount: json?.count,
|
||||
};
|
||||
};
|
||||
|
||||
interface ChartListProps {
|
||||
|
@ -120,6 +118,8 @@ interface ChartListProps {
|
|||
addSuccessToast: (msg: string) => void;
|
||||
user: {
|
||||
userId: string | number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -417,113 +417,110 @@ function ChartList(props: ChartListProps) {
|
|||
],
|
||||
);
|
||||
|
||||
const favoritesFilter: Filter = {
|
||||
Header: t('Favorite'),
|
||||
id: 'id',
|
||||
urlDisplay: 'favorite',
|
||||
input: 'select',
|
||||
operator: FilterOperator.chartIsFav,
|
||||
unfilteredLabel: t('Any'),
|
||||
selects: [
|
||||
{ label: t('Yes'), value: true },
|
||||
{ label: t('No'), value: false },
|
||||
],
|
||||
};
|
||||
const favoritesFilter: Filter = useMemo(
|
||||
() => ({
|
||||
Header: t('Favorite'),
|
||||
id: 'id',
|
||||
urlDisplay: 'favorite',
|
||||
input: 'select',
|
||||
operator: FilterOperator.chartIsFav,
|
||||
unfilteredLabel: t('Any'),
|
||||
selects: [
|
||||
{ label: t('Yes'), value: true },
|
||||
{ label: t('No'), value: false },
|
||||
],
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const filters: Filters = [
|
||||
{
|
||||
Header: t('Owner'),
|
||||
id: 'owners',
|
||||
input: 'select',
|
||||
operator: FilterOperator.relationManyMany,
|
||||
unfilteredLabel: t('All'),
|
||||
fetchSelects: createFetchRelated(
|
||||
'chart',
|
||||
'owners',
|
||||
createErrorHandler(errMsg =>
|
||||
addDangerToast(
|
||||
t(
|
||||
'An error occurred while fetching chart owners values: %s',
|
||||
errMsg,
|
||||
const filters: Filters = useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: t('Owner'),
|
||||
id: 'owners',
|
||||
input: 'select',
|
||||
operator: FilterOperator.relationManyMany,
|
||||
unfilteredLabel: t('All'),
|
||||
fetchSelects: createFetchRelated(
|
||||
'chart',
|
||||
'owners',
|
||||
createErrorHandler(errMsg =>
|
||||
addDangerToast(
|
||||
t(
|
||||
'An error occurred while fetching chart owners values: %s',
|
||||
errMsg,
|
||||
),
|
||||
),
|
||||
),
|
||||
props.user,
|
||||
),
|
||||
props.user.userId,
|
||||
),
|
||||
paginate: true,
|
||||
},
|
||||
{
|
||||
Header: t('Created by'),
|
||||
id: 'created_by',
|
||||
input: 'select',
|
||||
operator: FilterOperator.relationOneMany,
|
||||
unfilteredLabel: t('All'),
|
||||
fetchSelects: createFetchRelated(
|
||||
'chart',
|
||||
'created_by',
|
||||
createErrorHandler(errMsg =>
|
||||
addDangerToast(
|
||||
t(
|
||||
'An error occurred while fetching chart created by values: %s',
|
||||
errMsg,
|
||||
paginate: true,
|
||||
},
|
||||
{
|
||||
Header: t('Created by'),
|
||||
id: 'created_by',
|
||||
input: 'select',
|
||||
operator: FilterOperator.relationOneMany,
|
||||
unfilteredLabel: t('All'),
|
||||
fetchSelects: createFetchRelated(
|
||||
'chart',
|
||||
'created_by',
|
||||
createErrorHandler(errMsg =>
|
||||
addDangerToast(
|
||||
t(
|
||||
'An error occurred while fetching chart created by values: %s',
|
||||
errMsg,
|
||||
),
|
||||
),
|
||||
),
|
||||
props.user,
|
||||
),
|
||||
props.user.userId,
|
||||
),
|
||||
paginate: true,
|
||||
},
|
||||
{
|
||||
Header: t('Viz type'),
|
||||
id: 'viz_type',
|
||||
input: 'select',
|
||||
operator: FilterOperator.equals,
|
||||
unfilteredLabel: t('All'),
|
||||
selects: registry
|
||||
.keys()
|
||||
.filter(k => nativeFilterGate(registry.get(k)?.behaviors || []))
|
||||
.map(k => ({ label: registry.get(k)?.name || k, value: k }))
|
||||
.sort((a, b) => {
|
||||
if (!a.label || !b.label) {
|
||||
paginate: true,
|
||||
},
|
||||
{
|
||||
Header: t('Viz type'),
|
||||
id: 'viz_type',
|
||||
input: 'select',
|
||||
operator: FilterOperator.equals,
|
||||
unfilteredLabel: t('All'),
|
||||
selects: registry
|
||||
.keys()
|
||||
.filter(k => nativeFilterGate(registry.get(k)?.behaviors || []))
|
||||
.map(k => ({ label: registry.get(k)?.name || k, value: k }))
|
||||
.sort((a, b) => {
|
||||
if (!a.label || !b.label) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (a.label > b.label) {
|
||||
return 1;
|
||||
}
|
||||
if (a.label < b.label) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (a.label > b.label) {
|
||||
return 1;
|
||||
}
|
||||
if (a.label < b.label) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}),
|
||||
},
|
||||
{
|
||||
Header: t('Dataset'),
|
||||
id: 'datasource_id',
|
||||
input: 'select',
|
||||
operator: FilterOperator.equals,
|
||||
unfilteredLabel: t('All'),
|
||||
fetchSelects: createFetchDatasets(
|
||||
createErrorHandler(errMsg =>
|
||||
addDangerToast(
|
||||
t(
|
||||
'An error occurred while fetching chart dataset values: %s',
|
||||
errMsg,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
paginate: true,
|
||||
},
|
||||
...(props.user.userId ? [favoritesFilter] : []),
|
||||
{
|
||||
Header: t('Search'),
|
||||
id: 'slice_name',
|
||||
input: 'search',
|
||||
operator: FilterOperator.chartAllText,
|
||||
},
|
||||
];
|
||||
}),
|
||||
},
|
||||
{
|
||||
Header: t('Dataset'),
|
||||
id: 'datasource_id',
|
||||
input: 'select',
|
||||
operator: FilterOperator.equals,
|
||||
unfilteredLabel: t('All'),
|
||||
fetchSelects: createFetchDatasets,
|
||||
paginate: true,
|
||||
},
|
||||
...(props.user.userId ? [favoritesFilter] : []),
|
||||
{
|
||||
Header: t('Search'),
|
||||
id: 'slice_name',
|
||||
input: 'search',
|
||||
operator: FilterOperator.chartAllText,
|
||||
},
|
||||
],
|
||||
[addDangerToast, favoritesFilter, props.user],
|
||||
);
|
||||
|
||||
const sortTypes = [
|
||||
{
|
||||
|
|
|
@ -45,6 +45,8 @@ interface CssTemplatesListProps {
|
|||
addSuccessToast: (msg: string) => void;
|
||||
user: {
|
||||
userId: string | number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -287,7 +289,7 @@ function CssTemplatesList({
|
|||
errMsg,
|
||||
),
|
||||
),
|
||||
user.userId,
|
||||
user,
|
||||
),
|
||||
paginate: true,
|
||||
},
|
||||
|
|
|
@ -71,6 +71,8 @@ interface DashboardListProps {
|
|||
addSuccessToast: (msg: string) => void;
|
||||
user: {
|
||||
userId: string | number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -404,81 +406,87 @@ function DashboardList(props: DashboardListProps) {
|
|||
],
|
||||
);
|
||||
|
||||
const favoritesFilter: Filter = {
|
||||
Header: t('Favorite'),
|
||||
id: 'id',
|
||||
urlDisplay: 'favorite',
|
||||
input: 'select',
|
||||
operator: FilterOperator.dashboardIsFav,
|
||||
unfilteredLabel: t('Any'),
|
||||
selects: [
|
||||
{ label: t('Yes'), value: true },
|
||||
{ label: t('No'), value: false },
|
||||
],
|
||||
};
|
||||
|
||||
const filters: Filters = [
|
||||
{
|
||||
Header: t('Owner'),
|
||||
id: 'owners',
|
||||
const favoritesFilter: Filter = useMemo(
|
||||
() => ({
|
||||
Header: t('Favorite'),
|
||||
id: 'id',
|
||||
urlDisplay: 'favorite',
|
||||
input: 'select',
|
||||
operator: FilterOperator.relationManyMany,
|
||||
unfilteredLabel: t('All'),
|
||||
fetchSelects: createFetchRelated(
|
||||
'dashboard',
|
||||
'owners',
|
||||
createErrorHandler(errMsg =>
|
||||
addDangerToast(
|
||||
t(
|
||||
'An error occurred while fetching dashboard owner values: %s',
|
||||
errMsg,
|
||||
),
|
||||
),
|
||||
),
|
||||
props.user.userId,
|
||||
),
|
||||
paginate: true,
|
||||
},
|
||||
{
|
||||
Header: t('Created by'),
|
||||
id: 'created_by',
|
||||
input: 'select',
|
||||
operator: FilterOperator.relationOneMany,
|
||||
unfilteredLabel: t('All'),
|
||||
fetchSelects: createFetchRelated(
|
||||
'dashboard',
|
||||
'created_by',
|
||||
createErrorHandler(errMsg =>
|
||||
addDangerToast(
|
||||
t(
|
||||
'An error occurred while fetching dashboard created by values: %s',
|
||||
errMsg,
|
||||
),
|
||||
),
|
||||
),
|
||||
props.user.userId,
|
||||
),
|
||||
paginate: true,
|
||||
},
|
||||
{
|
||||
Header: t('Status'),
|
||||
id: 'published',
|
||||
input: 'select',
|
||||
operator: FilterOperator.equals,
|
||||
operator: FilterOperator.dashboardIsFav,
|
||||
unfilteredLabel: t('Any'),
|
||||
selects: [
|
||||
{ label: t('Published'), value: true },
|
||||
{ label: t('Draft'), value: false },
|
||||
{ label: t('Yes'), value: true },
|
||||
{ label: t('No'), value: false },
|
||||
],
|
||||
},
|
||||
...(props.user.userId ? [favoritesFilter] : []),
|
||||
{
|
||||
Header: t('Search'),
|
||||
id: 'dashboard_title',
|
||||
input: 'search',
|
||||
operator: FilterOperator.titleOrSlug,
|
||||
},
|
||||
];
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const filters: Filters = useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: t('Owner'),
|
||||
id: 'owners',
|
||||
input: 'select',
|
||||
operator: FilterOperator.relationManyMany,
|
||||
unfilteredLabel: t('All'),
|
||||
fetchSelects: createFetchRelated(
|
||||
'dashboard',
|
||||
'owners',
|
||||
createErrorHandler(errMsg =>
|
||||
addDangerToast(
|
||||
t(
|
||||
'An error occurred while fetching dashboard owner values: %s',
|
||||
errMsg,
|
||||
),
|
||||
),
|
||||
),
|
||||
props.user,
|
||||
),
|
||||
paginate: true,
|
||||
},
|
||||
{
|
||||
Header: t('Created by'),
|
||||
id: 'created_by',
|
||||
input: 'select',
|
||||
operator: FilterOperator.relationOneMany,
|
||||
unfilteredLabel: t('All'),
|
||||
fetchSelects: createFetchRelated(
|
||||
'dashboard',
|
||||
'created_by',
|
||||
createErrorHandler(errMsg =>
|
||||
addDangerToast(
|
||||
t(
|
||||
'An error occurred while fetching dashboard created by values: %s',
|
||||
errMsg,
|
||||
),
|
||||
),
|
||||
),
|
||||
props.user,
|
||||
),
|
||||
paginate: true,
|
||||
},
|
||||
{
|
||||
Header: t('Status'),
|
||||
id: 'published',
|
||||
input: 'select',
|
||||
operator: FilterOperator.equals,
|
||||
unfilteredLabel: t('Any'),
|
||||
selects: [
|
||||
{ label: t('Published'), value: true },
|
||||
{ label: t('Draft'), value: false },
|
||||
],
|
||||
},
|
||||
...(props.user.userId ? [favoritesFilter] : []),
|
||||
{
|
||||
Header: t('Search'),
|
||||
id: 'dashboard_title',
|
||||
input: 'search',
|
||||
operator: FilterOperator.titleOrSlug,
|
||||
},
|
||||
],
|
||||
[addDangerToast, favoritesFilter, props.user],
|
||||
);
|
||||
|
||||
const sortTypes = [
|
||||
{
|
||||
|
|
|
@ -161,13 +161,13 @@ describe('DatabaseList', () => {
|
|||
.find('[name="expose_in_sqllab"]')
|
||||
.first()
|
||||
.props()
|
||||
.onSelect(true);
|
||||
.onSelect({ label: 'Yes', value: true });
|
||||
|
||||
filtersWrapper
|
||||
.find('[name="allow_run_async"]')
|
||||
.first()
|
||||
.props()
|
||||
.onSelect(false);
|
||||
.onSelect({ label: 'Yes', value: false });
|
||||
|
||||
filtersWrapper
|
||||
.find('[name="database_name"]')
|
||||
|
|
|
@ -118,12 +118,14 @@ describe('DatasetList', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('fetches owner filter values', () => {
|
||||
expect(fetchMock.calls(/dataset\/related\/owners/)).toHaveLength(1);
|
||||
it('does not fetch owner filter values on mount', async () => {
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(fetchMock.calls(/dataset\/related\/owners/)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('fetches schema filter values', () => {
|
||||
expect(fetchMock.calls(/dataset\/distinct\/schema/)).toHaveLength(1);
|
||||
it('does not fetch schema filter values on mount', async () => {
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(fetchMock.calls(/dataset\/distinct\/schema/)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('shows/hides bulk actions when bulk actions is clicked', async () => {
|
||||
|
|
|
@ -98,6 +98,8 @@ interface DatasetListProps {
|
|||
addSuccessToast: (msg: string) => void;
|
||||
user: {
|
||||
userId: string | number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -414,7 +416,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
|||
errMsg,
|
||||
),
|
||||
),
|
||||
user.userId,
|
||||
user,
|
||||
),
|
||||
paginate: true,
|
||||
},
|
||||
|
|
|
@ -374,7 +374,7 @@ function QueryList({ addDangerToast, addSuccessToast }: QueryListProps) {
|
|||
'user',
|
||||
createErrorHandler(errMsg =>
|
||||
addDangerToast(
|
||||
t('An error occurred while fetching database values: %s', errMsg),
|
||||
t('An error occurred while fetching user values: %s', errMsg),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -271,7 +271,7 @@ describe('RTL', () => {
|
|||
|
||||
it('renders an export button in the actions bar', async () => {
|
||||
// Grab Export action button and mock mouse hovering over it
|
||||
const exportActionButton = screen.getAllByRole('button')[18];
|
||||
const exportActionButton = screen.getAllByTestId('export-action')[0];
|
||||
userEvent.hover(exportActionButton);
|
||||
|
||||
// Wait for the tooltip to pop up
|
||||
|
|
|
@ -141,7 +141,10 @@ export function useListViewResource<D extends object = any>(
|
|||
.map(({ id, operator: opr, value }) => ({
|
||||
col: id,
|
||||
opr,
|
||||
value,
|
||||
value:
|
||||
value && typeof value === 'object' && 'value' in value
|
||||
? value.value
|
||||
: value,
|
||||
}));
|
||||
|
||||
const queryParams = rison.encode({
|
||||
|
|
|
@ -37,32 +37,52 @@ const createFetchResourceMethod = (method: string) => (
|
|||
resource: string,
|
||||
relation: string,
|
||||
handleError: (error: Response) => void,
|
||||
userId?: string | number,
|
||||
) => async (filterValue = '', pageIndex?: number, pageSize?: number) => {
|
||||
user?: { userId: string | number; firstName: string; lastName: string },
|
||||
) => async (filterValue = '', page: number, pageSize: number) => {
|
||||
const resourceEndpoint = `/api/v1/${resource}/${method}/${relation}`;
|
||||
const options =
|
||||
userId && pageIndex === 0 ? [{ label: 'me', value: userId }] : [];
|
||||
try {
|
||||
const queryParams = rison.encode({
|
||||
...(pageIndex ? { page: pageIndex } : {}),
|
||||
...(pageSize ? { page_size: pageSize } : {}),
|
||||
...(filterValue ? { filter: filterValue } : {}),
|
||||
});
|
||||
const { json = {} } = await SupersetClient.get({
|
||||
endpoint: `${resourceEndpoint}?q=${queryParams}`,
|
||||
});
|
||||
const data = json?.result?.map(
|
||||
({ text: label, value }: { text: string; value: any }) => ({
|
||||
label,
|
||||
value,
|
||||
}),
|
||||
);
|
||||
const queryParams = rison.encode({
|
||||
filter: filterValue,
|
||||
page,
|
||||
page_size: pageSize,
|
||||
});
|
||||
const { json = {} } = await SupersetClient.get({
|
||||
endpoint: `${resourceEndpoint}?q=${queryParams}`,
|
||||
});
|
||||
|
||||
return options.concat(data);
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
let fetchedLoggedUser = false;
|
||||
const loggedUser = user
|
||||
? {
|
||||
label: `${user.firstName} ${user.lastName}`,
|
||||
value: user.userId,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const data: { label: string; value: string | number }[] = [];
|
||||
json?.result?.forEach(
|
||||
({ text, value }: { text: string; value: string | number }) => {
|
||||
if (
|
||||
loggedUser &&
|
||||
value === loggedUser.value &&
|
||||
text === loggedUser.label
|
||||
) {
|
||||
fetchedLoggedUser = true;
|
||||
} else {
|
||||
data.push({
|
||||
label: text,
|
||||
value,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (loggedUser && (!filterValue || fetchedLoggedUser)) {
|
||||
data.unshift(loggedUser);
|
||||
}
|
||||
return [];
|
||||
|
||||
return {
|
||||
data,
|
||||
totalCount: json?.count,
|
||||
};
|
||||
};
|
||||
|
||||
export const PAGE_SIZE = 5;
|
||||
|
|
Loading…
Reference in New Issue