Skip to content

Commit

Permalink
Use antd select
Browse files Browse the repository at this point in the history
  • Loading branch information
kgabryje committed Apr 21, 2024
1 parent cf21aef commit 6ec2feb
Show file tree
Hide file tree
Showing 11 changed files with 207 additions and 292 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/**
* 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, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';

Check failure on line 21 in superset-frontend/packages/superset-ui-chart-controls/src/components/ComparisonRangeLabel.tsx

View workflow job for this annotation

GitHub Actions / frontend-build

'@types/react-redux' should be listed in the project's dependencies. Run 'npm i -S @types/react-redux' to add it
import { isEmpty, isEqual } from 'lodash';
import {
BinaryAdhocFilter,
ComparisonTimeRangeType,
css,
fetchTimeRange,
SimpleAdhocFilter,
t,
} from '@superset-ui/core';
import { Tooltip } from './Tooltip';

const isTimeRangeEqual = (
left: BinaryAdhocFilter[],
right: BinaryAdhocFilter[],
) => isEqual(left, right);

const ComparisonRangeLabel = props => {
console.log(props);
const [labels, setLabels] = useState<string[]>([]);
const currentTimeRangeFilters = useSelector<any, BinaryAdhocFilter[]>(
state =>
state.explore.form_data.adhoc_filters.filter(
(adhoc_filter: SimpleAdhocFilter) =>
adhoc_filter.operator === 'TEMPORAL_RANGE',
),
isTimeRangeEqual,
);

const customTimeRangeComparisonFilters = useSelector<
any,
BinaryAdhocFilter[]
>(
state =>
state.explore.form_data.adhoc_custom.filter(
(adhoc_filter: SimpleAdhocFilter) =>
adhoc_filter.operator === 'TEMPORAL_RANGE',
),
isTimeRangeEqual,
);

const shift = useSelector<any, ComparisonTimeRangeType>(
state => state.explore.form_data.time_comparison,
);

const startDate = useSelector<any, string>(
state => state.explore.form_data.start_date_offset,
);

useEffect(() => {
if (shift === ComparisonTimeRangeType.Custom) {
const promises = customTimeRangeComparisonFilters.map(filter =>
fetchTimeRange(filter.comparator, filter.subject),
);
Promise.all(promises).then(res => {
setLabels(res.map(r => r.value ?? ''));
});
}
}, [customTimeRangeComparisonFilters, shift]);

useEffect(() => {
if (shift !== ComparisonTimeRangeType.Custom) {
const promises = currentTimeRangeFilters.map(filter =>
fetchTimeRange(filter.comparator, filter.subject, shift),
);
Promise.all(promises).then(res => {
setLabels(res.map(r => r.value ?? ''));
});
}
}, [currentTimeRangeFilters, shift]);

useEffect(() => {
if (isEmpty(currentTimeRangeFilters) || (isEmpty(shifts) && !startDate)) {
setLabels([]);
} else if (!isEmpty(shifts) || startDate) {
const promises = currentTimeRangeFilters.map(filter => {
const startDateShift = moment(
(filter as any).comparator.split(' : ')[0],
).diff(moment(startDate), 'days');
const newshift = startDateShift
? [`${startDateShift} days ago`]
: shifts
? shifts.slice(0, 1)
: undefined;

return fetchTimeRange(
filter.comparator,
filter.subject,
multi ? shifts : newshift,
);
});
Promise.all(promises).then(res => {
// access the value property inside the res and set the labels with it in the state
setLabels(res.map(r => r.value ?? ''));
});
}
}, [currentTimeRangeFilters, shifts, startDate]);

return label ? (
<Tooltip title={t('Actual time range for comparison')}>
<span
css={theme => css`
font-size: ${theme.typography.sizes.m}px;
color: ${theme.colors.grayscale.base};
`}
>
{label}
</span>
</Tooltip>
) : null;
};

export default ComparisonRangeLabel;
115 changes: 19 additions & 96 deletions superset-frontend/src/components/Select/AsyncSelect.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,99 +151,37 @@ const USERS = [
'Claire',
'Benedetta',
'Ilenia',
]
.sort()
.map(u => ({ label: u, value: u }));

const GROUPED_USERS = [
{
label: 'Male',
title: 'Male',
options: [
'John',
'Liam',
'Noah',
'Oliver',
'Elijah',
'Diego',
'Evan',
'Michael',
'Giovanni',
'Luca',
'Paolo',
'Mario',
'Marco',
'Luigi',
'Quarto',
'Quinto',
'Sesto',
'Franco',
'Sandro',
'Alehandro',
'Johnny',
'Igor',
'Thami',
'Munei',
'Guilherme',
'Umair',
'Ashfaq',
'Irfan',
'George',
'Naseer',
'Mohammad',
'Rick',
],
},
{
label: 'Female',
title: 'Female',
options: [
'Olivia',
'Emma',
'Ava',
'Charlotte',
'Francesca',
'Chiara',
'Sara',
'Valentina',
'Jessica',
'Angelica',
'Nikole',
'Amna',
'Saliya',
'Claire',
'Benedetta',
'Ilenia',
],
},
].map(group => ({
...group,
options: group.options.sort().map(u => ({ label: u, value: u })),
}));

type AsyncSelectStoryProps = AsyncSelectProps & {
withError: boolean;
withInitialValue: boolean;
responseTime: number;
data: { label: string; value: string }[];
};
].sort();

export const AsynchronousSelect = ({
fetchOnlyOnSearch,
withError,
withInitialValue,
responseTime,
data,
...rest
}: AsyncSelectStoryProps) => {
}: AsyncSelectProps & {
withError: boolean;
withInitialValue: boolean;
responseTime: number;
}) => {
const [requests, setRequests] = useState<ReactNode[]>([]);
const ref = useRef<AsyncSelectRef>(null);

const getResults = (username?: string) => {
let results = [...data];
let results: { label: string; value: string }[] = [];

if (username) {
results = data.filter(u => u.value.toLowerCase().includes(username));
if (!username) {
results = USERS.map(u => ({
label: u,
value: u,
}));
} else {
const foundUsers = USERS.filter(u => u.toLowerCase().includes(username));
if (foundUsers) {
results = foundUsers.map(u => ({ label: u, value: u }));
} else {
results = [];
}
}
return results;
};
Expand Down Expand Up @@ -352,7 +290,6 @@ AsynchronousSelect.args = {
withError: false,
withInitialValue: false,
tokenSeparators: ['\n', '\t', ';'],
data: USERS,
};

AsynchronousSelect.argTypes = {
Expand Down Expand Up @@ -387,17 +324,3 @@ AsynchronousSelect.argTypes = {
},
},
};

export const AsyncSelectWithGroups = (props: AsyncSelectStoryProps) => (
<AsynchronousSelect {...props} />
);

AsyncSelectWithGroups.args = {
allowClear: true,
allowNewOptions: true,
mode: 'multiple',
pageSize: 5,
tokenSeparators: ['\n', '\t', ';'],
data: GROUPED_USERS,
};
AsyncSelectWithGroups.argTypes = AsynchronousSelect.argTypes;
68 changes: 11 additions & 57 deletions superset-frontend/src/components/Select/AsyncSelect.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {
} from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import { AsyncSelect } from 'src/components';
import { OptionData, OptionGroup } from './types';

const ARIA_LABEL = 'Test';
const NEW_OPTION = 'Kyle';
Expand Down Expand Up @@ -63,31 +62,24 @@ const NULL_OPTION = { label: '<NULL>', value: null } as unknown as {
value: number;
};

const loadOptions = async (
search: string,
page: number,
pageSize: number,
options: OptionData | OptionGroup[] = OPTIONS,
) => {
const totalCount = options.length;
const loadOptions = async (search: string, page: number, pageSize: number) => {
const totalCount = OPTIONS.length;
const start = page * pageSize;
const deleteCount =
start + pageSize < totalCount ? pageSize : totalCount - start;
const searchValue = search.trim().toLowerCase();
const optionFilterProps = ['label', 'value', 'gender'];
const data = options
.filter((option: (typeof OPTIONS)[0]) =>
optionFilterProps.some(prop => {
const optionProp = option?.[prop]
? String(option[prop]).trim().toLowerCase()
: '';
return optionProp.includes(searchValue);
}),
)
.splice(start, deleteCount);
const data = OPTIONS.filter(option =>
optionFilterProps.some(prop => {
const optionProp = option?.[prop]
? String(option[prop]).trim().toLowerCase()
: '';
return optionProp.includes(searchValue);
}),
).splice(start, deleteCount);
return {
data,
totalCount: options.length,
totalCount: OPTIONS.length,
};
};

Expand Down Expand Up @@ -1009,44 +1001,6 @@ test('does not fire onChange if the same value is selected in single mode', asyn
expect(onChange).toHaveBeenCalledTimes(1);
});

test('correctly renders options group', async () => {
const groupedOptions = Object.values(
OPTIONS.slice(0, 10).reduce((acc, opt) => {
if (Array.isArray(acc[opt.gender]?.options)) {
acc[opt.gender].options.push(opt);
} else {
acc[opt.gender] = {
options: [opt],
title: opt.gender,
label: opt.gender,
};
}
return acc;
}, {}),
) as OptionGroup[];

render(
<AsyncSelect
{...defaultProps}
options={(search, page, pageSize) =>
loadOptions(search, page, pageSize, groupedOptions)
}
/>,
);
await open();

expect(
getElementsByClassName('.ant-select-item-option-grouped'),
).toHaveLength(10);
expect(getElementsByClassName('.ant-select-item-group')).toHaveLength(2);
expect(getElementsByClassName('.ant-select-item-group')[0]).toHaveTextContent(
'Female',
);
expect(getElementsByClassName('.ant-select-item-group')[1]).toHaveTextContent(
'Male',
);
});

/*
TODO: Add tests that require scroll interaction. Needs further investigation.
- Fetches more data when scrolling and more data is available
Expand Down
Loading

0 comments on commit 6ec2feb

Please sign in to comment.