mirror of
https://github.com/valitydev/redash.git
synced 2024-11-06 09:05:17 +00:00
Multiselect dropdown slowness (fix) (#5221)
* created util to estimate reasonable width for dropdown
* removed unused import
* improved calculation of item percentile
* added getItemOfPercentileLength to relevant spots
* added getItemOfPercentileLength to relevant spots
* Added missing import
* created custom select element
* added check for property path
* removed uses of percentile util
* gave up on getting element reference
* finished testing Select component
* removed unused imports
* removed older uses of Option component
* added canvas calculation
* removed minWidth from Select
* improved calculation
* added fallbacks
* added estimated offset
* removed leftovers 😅
* replaced to percentiles to max value
* switched to memo and renamed component
* proper useMemo syntax
* Update client/app/components/Select.tsx
Co-authored-by: Gabriel Dutra <nesk.frz@gmail.com>
* created custom restrictive types
* added quick const
* fixed style
* fixed generics
* added pos absolute to fix percy
* removed custom select from ParameterMappingInput
* applied prettier
* Revert "added pos absolute to fix percy"
This reverts commit 4daf1d4bef9edf93cd9bb1f404bd022472ff17a2.
* Pin Percy version to 0.24.3
* Update client/app/components/ParameterMappingInput.jsx
Co-authored-by: Gabriel Dutra <nesk.frz@gmail.com>
* renamed Select.jsx to SelectWithVirtualScroll
Co-authored-by: Gabriel Dutra <nesk.frz@gmail.com>
This commit is contained in:
parent
cae088f35b
commit
12f71925c2
@ -25,8 +25,6 @@ import CheckOutlinedIcon from "@ant-design/icons/CheckOutlined";
|
||||
|
||||
import "./ParameterMappingInput.less";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
export const MappingType = {
|
||||
DashboardAddNew: "dashboard-add-new",
|
||||
DashboardMapToExisting: "dashboard-map-to-existing",
|
||||
@ -208,19 +206,9 @@ export class ParameterMappingInput extends React.Component {
|
||||
|
||||
renderDashboardMapToExisting() {
|
||||
const { mapping, existingParamNames } = this.props;
|
||||
const options = map(existingParamNames, paramName => ({ label: paramName, value: paramName }));
|
||||
|
||||
return (
|
||||
<Select
|
||||
value={mapping.mapTo}
|
||||
onChange={mapTo => this.updateParamMapping({ mapTo })}
|
||||
dropdownMatchSelectWidth={false}>
|
||||
{map(existingParamNames, name => (
|
||||
<Option value={name} key={name}>
|
||||
{name}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
return <Select value={mapping.mapTo} onChange={mapTo => this.updateParamMapping({ mapTo })} options={options} />;
|
||||
}
|
||||
|
||||
renderStaticValue() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { isEqual, isEmpty } from "lodash";
|
||||
import { isEqual, isEmpty, map } from "lodash";
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import Select from "antd/lib/select";
|
||||
import SelectWithVirtualScroll from "@/components/SelectWithVirtualScroll";
|
||||
import Input from "antd/lib/input";
|
||||
import InputNumber from "antd/lib/input-number";
|
||||
import DateParameter from "@/components/dynamic-parameters/DateParameter";
|
||||
@ -10,8 +10,6 @@ import QueryBasedParameterInput from "./QueryBasedParameterInput";
|
||||
|
||||
import "./ParameterValueInput.less";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const multipleValuesProps = {
|
||||
maxTagCount: 3,
|
||||
maxTagTextLength: 10,
|
||||
@ -98,25 +96,20 @@ class ParameterValueInput extends React.Component {
|
||||
const enumOptionsArray = enumOptions.split("\n").filter(v => v !== "");
|
||||
// Antd Select doesn't handle null in multiple mode
|
||||
const normalize = val => (parameter.multiValuesOptions && val === null ? [] : val);
|
||||
|
||||
return (
|
||||
<Select
|
||||
<SelectWithVirtualScroll
|
||||
className={this.props.className}
|
||||
mode={parameter.multiValuesOptions ? "multiple" : "default"}
|
||||
optionFilterProp="children"
|
||||
value={normalize(value)}
|
||||
onChange={this.onSelect}
|
||||
dropdownMatchSelectWidth={false}
|
||||
options={map(enumOptionsArray, opt => ({ label: String(opt), value: opt }))}
|
||||
showSearch
|
||||
showArrow
|
||||
style={{ minWidth: 60 }}
|
||||
notFoundContent={isEmpty(enumOptionsArray) ? "No options available" : null}
|
||||
{...multipleValuesProps}>
|
||||
{enumOptionsArray.map(option => (
|
||||
<Option key={option} value={option}>
|
||||
{option}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
{...multipleValuesProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { find, isArray, get, first, map, intersection, isEqual, isEmpty } from "lodash";
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import Select from "antd/lib/select";
|
||||
|
||||
const { Option } = Select;
|
||||
import SelectWithVirtualScroll from "@/components/SelectWithVirtualScroll";
|
||||
|
||||
export default class QueryBasedParameterInput extends React.Component {
|
||||
static propTypes = {
|
||||
@ -83,25 +81,20 @@ export default class QueryBasedParameterInput extends React.Component {
|
||||
const { loading, options } = this.state;
|
||||
return (
|
||||
<span>
|
||||
<Select
|
||||
<SelectWithVirtualScroll
|
||||
className={className}
|
||||
disabled={loading}
|
||||
loading={loading}
|
||||
mode={mode}
|
||||
value={this.state.value}
|
||||
onChange={onSelect}
|
||||
dropdownMatchSelectWidth={false}
|
||||
options={map(options, ({ value, name }) => ({ label: String(name), value }))}
|
||||
optionFilterProp="children"
|
||||
showSearch
|
||||
showArrow
|
||||
notFoundContent={isEmpty(options) ? "No options available" : null}
|
||||
{...otherProps}>
|
||||
{options.map(option => (
|
||||
<Option value={option.value} key={option.value}>
|
||||
{option.name}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
{...otherProps}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
38
client/app/components/SelectWithVirtualScroll.tsx
Normal file
38
client/app/components/SelectWithVirtualScroll.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { maxBy } from "lodash";
|
||||
import AntdSelect, { SelectProps, LabeledValue } from "antd/lib/select";
|
||||
import { calculateTextWidth } from "@/lib/calculateTextWidth";
|
||||
|
||||
const MIN_LEN_FOR_VIRTUAL_SCROLL = 400;
|
||||
|
||||
interface VirtualScrollLabeledValue extends LabeledValue {
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface VirtualScrollSelectProps extends SelectProps<string> {
|
||||
options: Array<VirtualScrollLabeledValue>;
|
||||
}
|
||||
function SelectWithVirtualScroll({ options, ...props }: VirtualScrollSelectProps): JSX.Element {
|
||||
const dropdownMatchSelectWidth = useMemo<number | boolean>(() => {
|
||||
if (options && options.length > MIN_LEN_FOR_VIRTUAL_SCROLL) {
|
||||
const largestOpt = maxBy(options, "label.length");
|
||||
|
||||
if (largestOpt) {
|
||||
const offset = 40;
|
||||
const optionText = largestOpt.label;
|
||||
const width = calculateTextWidth(optionText);
|
||||
if (width) {
|
||||
return width + offset;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [options]);
|
||||
|
||||
return <AntdSelect<string> dropdownMatchSelectWidth={dropdownMatchSelectWidth} options={options} {...props} />;
|
||||
}
|
||||
|
||||
export default SelectWithVirtualScroll;
|
20
client/app/lib/calculateTextWidth.ts
Normal file
20
client/app/lib/calculateTextWidth.ts
Normal file
@ -0,0 +1,20 @@
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.style.display = "none";
|
||||
document.body.appendChild(canvas);
|
||||
|
||||
export function calculateTextWidth(text: string, container = document.body) {
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (ctx) {
|
||||
const containerStyle = window.getComputedStyle(container);
|
||||
ctx.font = `${containerStyle.fontSize} ${containerStyle.fontFamily}`;
|
||||
const textMetrics = ctx.measureText(text);
|
||||
let actualWidth = textMetrics.width;
|
||||
if ("actualBoundingBoxLeft" in textMetrics) {
|
||||
// only available on evergreen browsers
|
||||
actualWidth = Math.abs(textMetrics.actualBoundingBoxLeft) + Math.abs(textMetrics.actualBoundingBoxRight);
|
||||
}
|
||||
return actualWidth;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
85
package-lock.json
generated
85
package-lock.json
generated
@ -4107,12 +4107,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
|
||||
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/color-name": "^1.1.1",
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
@ -4270,9 +4269,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz",
|
||||
"integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
|
||||
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@ -4297,12 +4296,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
|
||||
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/color-name": "^1.1.1",
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
@ -4355,13 +4353,21 @@
|
||||
"dev": true
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz",
|
||||
"integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^1.0.0"
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
@ -4668,9 +4674,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@percy/agent": {
|
||||
"version": "0.26.2",
|
||||
"resolved": "https://registry.npmjs.org/@percy/agent/-/agent-0.26.2.tgz",
|
||||
"integrity": "sha512-egwfhCOZnPDKh67Ldi3jN72fReaa3gU3OiXwhibT9NWkMuKh4O6m1zP55gYt0f8qDc5T9ZVfB0fbvsvJuTqujg==",
|
||||
"version": "0.24.3",
|
||||
"resolved": "https://registry.npmjs.org/@percy/agent/-/agent-0.24.3.tgz",
|
||||
"integrity": "sha512-gSx1qqtTMLix/ZzGo9taz02zKR9CGAt5megYW3jPW+DhyTaHSHoQgGcEI41el7T7BqB4FbfS0rkeG2B991mNcw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@oclif/command": "1.5.19",
|
||||
@ -4798,13 +4804,30 @@
|
||||
}
|
||||
},
|
||||
"@percy/cypress": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@percy/cypress/-/cypress-2.3.1.tgz",
|
||||
"integrity": "sha512-/kswOdqO/w6q7VLPeZENRd9c8aIb+W6oNHZDNkRZ9eIa1O+iMIqHj2aB+Bcbc6WhtlRZM/65ekrGPR7QyK9Y7A==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@percy/cypress/-/cypress-2.3.2.tgz",
|
||||
"integrity": "sha512-pRBUft9gOM5Jduyu0VKfucHy8QTDTIw8y+Zu7JNrNHWa0JcfOJcpQbhZJ6AGmA5xzad05S6wjQ8CnG8y3iaj/w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@percy/agent": "~0",
|
||||
"axios": "^0.19.0"
|
||||
"axios": "^0.20.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": {
|
||||
"version": "0.20.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz",
|
||||
"integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"follow-redirects": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.13.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz",
|
||||
"integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@plotly/d3-sankey": {
|
||||
@ -5464,6 +5487,12 @@
|
||||
"robust-orientation": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz",
|
||||
"integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==",
|
||||
"dev": true
|
||||
},
|
||||
"aggregate-error": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
|
||||
@ -8262,9 +8291,9 @@
|
||||
}
|
||||
},
|
||||
"color-string": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz",
|
||||
"integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==",
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz",
|
||||
"integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "^1.0.0",
|
||||
@ -14155,12 +14184,6 @@
|
||||
"debug": "4"
|
||||
},
|
||||
"dependencies": {
|
||||
"agent-base": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz",
|
||||
"integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
|
||||
|
@ -86,8 +86,8 @@
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/preset-typescript": "^7.10.4",
|
||||
"@cypress/code-coverage": "^3.8.1",
|
||||
"@percy/agent": "^0.26.2",
|
||||
"@percy/cypress": "^2.3.1",
|
||||
"@percy/agent": "0.24.3",
|
||||
"@percy/cypress": "^2.3.2",
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"@types/lodash": "^4.14.157",
|
||||
|
Loading…
Reference in New Issue
Block a user