mirror of
https://github.com/apache/superset.git
synced 2026-07-18 20:55:47 +00:00
Compare commits
19 Commits
file-handl
...
msyavuz/ch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e83afad78 | ||
|
|
ab19bc2f01 | ||
|
|
dbe27d599a | ||
|
|
eac612552b | ||
|
|
92c062a6da | ||
|
|
cffd58a319 | ||
|
|
cacd1b1638 | ||
|
|
bb7b875d40 | ||
|
|
ddd82a05bb | ||
|
|
87d1210033 | ||
|
|
cd573ea7c6 | ||
|
|
0980c525c9 | ||
|
|
dc1bbda946 | ||
|
|
5078dc3613 | ||
|
|
d4a53a1b13 | ||
|
|
0863d17598 | ||
|
|
b11ae1ecab | ||
|
|
fa92bb90db | ||
|
|
c9012bc591 |
@@ -54,7 +54,6 @@ module.exports = {
|
|||||||
['@babel/plugin-transform-runtime', { corejs: 3 }],
|
['@babel/plugin-transform-runtime', { corejs: 3 }],
|
||||||
// only used in packages/superset-ui-core/src/chart/components/reactify.tsx
|
// only used in packages/superset-ui-core/src/chart/components/reactify.tsx
|
||||||
['babel-plugin-typescript-to-proptypes', { loose: true }],
|
['babel-plugin-typescript-to-proptypes', { loose: true }],
|
||||||
'react-hot-loader/babel',
|
|
||||||
[
|
[
|
||||||
'@emotion/babel-plugin',
|
'@emotion/babel-plugin',
|
||||||
{
|
{
|
||||||
|
|||||||
27407
superset-frontend/package-lock.json
generated
27407
superset-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -138,7 +138,7 @@
|
|||||||
"@visx/xychart": "^3.5.1",
|
"@visx/xychart": "^3.5.1",
|
||||||
"ag-grid-community": "34.2.0",
|
"ag-grid-community": "34.2.0",
|
||||||
"ag-grid-react": "34.2.0",
|
"ag-grid-react": "34.2.0",
|
||||||
"antd": "^5.26.0",
|
"antd": "^5.26.3",
|
||||||
"chrono-node": "^2.7.8",
|
"chrono-node": "^2.7.8",
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"content-disposition": "^0.5.4",
|
"content-disposition": "^0.5.4",
|
||||||
@@ -179,15 +179,17 @@
|
|||||||
"ol": "^7.5.2",
|
"ol": "^7.5.2",
|
||||||
"polished": "^4.3.1",
|
"polished": "^4.3.1",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
|
"query-string": "^7.1.3",
|
||||||
"re-resizable": "^6.10.1",
|
"re-resizable": "^6.10.1",
|
||||||
"react": "^17.0.2",
|
"react": "^18.2.0",
|
||||||
|
"react-ace": "^10.1.0",
|
||||||
"react-checkbox-tree": "^1.8.0",
|
"react-checkbox-tree": "^1.8.0",
|
||||||
"react-diff-viewer-continued": "^3.4.0",
|
"react-diff-viewer-continued": "^3.4.0",
|
||||||
"react-dnd": "^11.1.3",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^11.1.3",
|
"react-dnd-html5-backend": "^11.1.3",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-draggable": "^4.4.6",
|
||||||
"react-google-recaptcha": "^3.1.0",
|
"react-google-recaptcha": "^3.1.0",
|
||||||
"react-hot-loader": "^4.13.1",
|
|
||||||
"react-intersection-observer": "^9.16.0",
|
"react-intersection-observer": "^9.16.0",
|
||||||
"react-json-tree": "^0.20.0",
|
"react-json-tree": "^0.20.0",
|
||||||
"react-lines-ellipsis": "^0.16.1",
|
"react-lines-ellipsis": "^0.16.1",
|
||||||
@@ -237,10 +239,8 @@
|
|||||||
"@babel/runtime": "^7.28.4",
|
"@babel/runtime": "^7.28.4",
|
||||||
"@babel/runtime-corejs3": "^7.28.2",
|
"@babel/runtime-corejs3": "^7.28.2",
|
||||||
"@babel/types": "^7.26.9",
|
"@babel/types": "^7.26.9",
|
||||||
"@cypress/react": "^8.0.2",
|
|
||||||
"@emotion/babel-plugin": "^11.13.5",
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
"@emotion/jest": "^11.13.0",
|
"@emotion/jest": "^11.13.0",
|
||||||
"@hot-loader/react-dom": "^17.0.2",
|
|
||||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||||
"@mihkeleidast/storybook-addon-source": "^1.0.1",
|
"@mihkeleidast/storybook-addon-source": "^1.0.1",
|
||||||
"@playwright/test": "^1.56.0",
|
"@playwright/test": "^1.56.0",
|
||||||
@@ -256,12 +256,11 @@
|
|||||||
"@storybook/react-webpack5": "8.1.11",
|
"@storybook/react-webpack5": "8.1.11",
|
||||||
"@svgr/webpack": "^8.1.0",
|
"@svgr/webpack": "^8.1.0",
|
||||||
"@swc/core": "^1.14.0",
|
"@swc/core": "^1.14.0",
|
||||||
"@swc/plugin-emotion": "^12.0.0",
|
"@swc/plugin-emotion": "^13.1.0",
|
||||||
"@swc/plugin-transform-imports": "^10.0.0",
|
"@swc/plugin-transform-imports": "^11.1.0",
|
||||||
"@testing-library/dom": "^8.20.1",
|
"@testing-library/dom": "^8.20.1",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^12.1.5",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@testing-library/react-hooks": "^8.0.1",
|
|
||||||
"@testing-library/user-event": "^12.8.3",
|
"@testing-library/user-event": "^12.8.3",
|
||||||
"@types/content-disposition": "^0.5.9",
|
"@types/content-disposition": "^0.5.9",
|
||||||
"@types/dom-to-image": "^2.6.7",
|
"@types/dom-to-image": "^2.6.7",
|
||||||
@@ -271,8 +270,9 @@
|
|||||||
"@types/math-expression-evaluator": "^1.3.3",
|
"@types/math-expression-evaluator": "^1.3.3",
|
||||||
"@types/mousetrap": "^1.6.15",
|
"@types/mousetrap": "^1.6.15",
|
||||||
"@types/node": "^24.8.1",
|
"@types/node": "^24.8.1",
|
||||||
"@types/react": "^17.0.83",
|
"@types/react": "^18.2.0",
|
||||||
"@types/react-dom": "^17.0.26",
|
"@types/react-dom": "^18.2.0",
|
||||||
|
"@types/react-gravatar": "^2.6.14",
|
||||||
"@types/react-json-tree": "^0.13.0",
|
"@types/react-json-tree": "^0.13.0",
|
||||||
"@types/react-loadable": "^5.5.11",
|
"@types/react-loadable": "^5.5.11",
|
||||||
"@types/react-redux": "^7.1.10",
|
"@types/react-redux": "^7.1.10",
|
||||||
@@ -378,6 +378,10 @@
|
|||||||
"npm": "^10.8.1"
|
"npm": "^10.8.1"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
|
"react-sortable-hoc": {
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0"
|
||||||
|
},
|
||||||
"core-js": "^3.38.1",
|
"core-js": "^3.38.1",
|
||||||
"d3-color": "^3.1.0",
|
"d3-color": "^3.1.0",
|
||||||
"puppeteer": "^22.4.1",
|
"puppeteer": "^22.4.1",
|
||||||
|
|||||||
@@ -23,8 +23,7 @@
|
|||||||
"@types/lodash": "^4.17.20",
|
"@types/lodash": "^4.17.20",
|
||||||
"@testing-library/dom": "^8.20.1",
|
"@testing-library/dom": "^8.20.1",
|
||||||
"@testing-library/jest-dom": "*",
|
"@testing-library/jest-dom": "*",
|
||||||
"@testing-library/react": "^12.1.5",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@testing-library/react-hooks": "*",
|
|
||||||
"@testing-library/user-event": "*",
|
"@testing-library/user-event": "*",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"@types/react-loadable": "*",
|
"@types/react-loadable": "*",
|
||||||
@@ -32,12 +31,12 @@
|
|||||||
"@types/tinycolor2": "*"
|
"@types/tinycolor2": "*"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
"react": "^18.2.0",
|
||||||
"@emotion/cache": "^11.4.0",
|
"@emotion/cache": "^11.4.0",
|
||||||
"@emotion/react": "^11.4.1",
|
"@emotion/react": "^11.4.1",
|
||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"nanoid": "^5.0.9",
|
"nanoid": "^5.0.9",
|
||||||
"react": "^17.0.2",
|
"react-dom": "^18.2.0",
|
||||||
"react-dom": "^17.0.2",
|
|
||||||
"react-loadable": "^5.5.0",
|
"react-loadable": "^5.5.0",
|
||||||
"tinycolor2": "*",
|
"tinycolor2": "*",
|
||||||
"@fontsource/fira-code": "^5.2.6",
|
"@fontsource/fira-code": "^5.2.6",
|
||||||
|
|||||||
@@ -35,15 +35,14 @@
|
|||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@testing-library/dom": "^8.20.1",
|
"@testing-library/dom": "^8.20.1",
|
||||||
"@testing-library/jest-dom": "*",
|
"@testing-library/jest-dom": "*",
|
||||||
"@testing-library/react": "^12.1.5",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@testing-library/react-hooks": "*",
|
|
||||||
"@testing-library/user-event": "*",
|
"@testing-library/user-event": "*",
|
||||||
"ace-builds": "^1.4.14",
|
"ace-builds": "^1.4.14",
|
||||||
"brace": "^0.11.1",
|
"brace": "^0.11.1",
|
||||||
"memoize-one": "^5.1.1",
|
"memoize-one": "^5.1.1",
|
||||||
"react": "^17.0.2",
|
"react": "^18.2.0",
|
||||||
"react-ace": "^10.1.0",
|
"react-ace": "^10.1.0",
|
||||||
"react-dom": "^17.0.2"
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -97,16 +97,15 @@
|
|||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@testing-library/dom": "^8.20.1",
|
"@testing-library/dom": "^8.20.1",
|
||||||
"@testing-library/jest-dom": "*",
|
"@testing-library/jest-dom": "*",
|
||||||
"@testing-library/react": "^12.1.5",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@testing-library/react-hooks": "*",
|
|
||||||
"@testing-library/user-event": "*",
|
"@testing-library/user-event": "*",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"@types/react-loadable": "*",
|
"@types/react-loadable": "*",
|
||||||
"@types/react-window": "^1.8.8",
|
"@types/react-window": "^1.8.8",
|
||||||
"@types/tinycolor2": "*",
|
"@types/tinycolor2": "*",
|
||||||
"nanoid": "^5.0.9",
|
"nanoid": "^5.0.9",
|
||||||
"react": "^17.0.2",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^18.2.0",
|
||||||
"react-loadable": "^5.5.0",
|
"react-loadable": "^5.5.0",
|
||||||
"tinycolor2": "*"
|
"tinycolor2": "*"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { useJsonValidation } from './useJsonValidation';
|
import { useJsonValidation } from './useJsonValidation';
|
||||||
|
|
||||||
describe('useJsonValidation', () => {
|
describe('useJsonValidation', () => {
|
||||||
|
|||||||
@@ -19,12 +19,12 @@
|
|||||||
import {
|
import {
|
||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
RefObject,
|
|
||||||
forwardRef,
|
forwardRef,
|
||||||
ComponentType,
|
ComponentType,
|
||||||
ForwardRefExoticComponent,
|
ForwardRefExoticComponent,
|
||||||
PropsWithoutRef,
|
PropsWithoutRef,
|
||||||
RefAttributes,
|
RefAttributes,
|
||||||
|
ForwardedRef,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import { Loading } from '../Loading';
|
import { Loading } from '../Loading';
|
||||||
@@ -54,7 +54,7 @@ function DefaultPlaceholder({
|
|||||||
* first (if provided) and re-render once import is complete.
|
* first (if provided) and re-render once import is complete.
|
||||||
*/
|
*/
|
||||||
export function AsyncEsmComponent<
|
export function AsyncEsmComponent<
|
||||||
P = PlaceholderProps,
|
P = Record<string, unknown>,
|
||||||
M = ComponentType<P> | { default: ComponentType<P> },
|
M = ComponentType<P> | { default: ComponentType<P> },
|
||||||
>(
|
>(
|
||||||
/**
|
/**
|
||||||
@@ -98,8 +98,8 @@ export function AsyncEsmComponent<
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AsyncComponent: AsyncComponent = forwardRef(function AsyncComponent(
|
const AsyncComponent: AsyncComponent = forwardRef(function AsyncComponent(
|
||||||
props: FullProps,
|
props: PropsWithoutRef<FullProps>,
|
||||||
ref: RefObject<ComponentType<FullProps>>,
|
ref: ForwardedRef<ComponentType<FullProps>>,
|
||||||
) {
|
) {
|
||||||
const [loaded, setLoaded] = useState(component !== undefined);
|
const [loaded, setLoaded] = useState(component !== undefined);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import type {
|
|||||||
ButtonVariantType,
|
ButtonVariantType,
|
||||||
ButtonColorType,
|
ButtonColorType,
|
||||||
} from 'antd/es/button';
|
} from 'antd/es/button';
|
||||||
import { IconType } from '@superset-ui/core/components/Icons/types';
|
|
||||||
import type { TooltipPlacement } from '../Tooltip/types';
|
import type { TooltipPlacement } from '../Tooltip/types';
|
||||||
|
|
||||||
export type { AntdButtonProps, ButtonType, ButtonVariantType, ButtonColorType };
|
export type { AntdButtonProps, ButtonType, ButtonVariantType, ButtonColorType };
|
||||||
@@ -49,5 +48,4 @@ export type ButtonProps = Omit<AntdButtonProps, 'css'> & {
|
|||||||
buttonStyle?: ButtonStyle;
|
buttonStyle?: ButtonStyle;
|
||||||
cta?: boolean;
|
cta?: boolean;
|
||||||
showMarginRight?: boolean;
|
showMarginRight?: boolean;
|
||||||
icon?: IconType;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export const Component = (props: DropdownContainerProps) => {
|
|||||||
const [overflowingState, setOverflowingState] = useState<OverflowingState>();
|
const [overflowingState, setOverflowingState] = useState<OverflowingState>();
|
||||||
const containerRef = useRef<DropdownRef>(null);
|
const containerRef = useRef<DropdownRef>(null);
|
||||||
const onOverflowingStateChange = useCallback(
|
const onOverflowingStateChange = useCallback(
|
||||||
value => {
|
(value: OverflowingState) => {
|
||||||
if (!isEqual(overflowingState, value)) {
|
if (!isEqual(overflowingState, value)) {
|
||||||
setItems(generateItems(value));
|
setItems(generateItems(value));
|
||||||
setOverflowingState(value);
|
setOverflowingState(value);
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import type { CSSProperties, ReactElement, RefObject, ReactNode } from 'react';
|
import type { CSSProperties, ReactElement, RefObject, ReactNode } from 'react';
|
||||||
import { IconType } from '../Icons';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Container item.
|
* Container item.
|
||||||
@@ -70,7 +69,7 @@ export interface DropdownContainerProps {
|
|||||||
/**
|
/**
|
||||||
* Icon of the dropdown trigger.
|
* Icon of the dropdown trigger.
|
||||||
*/
|
*/
|
||||||
dropdownTriggerIcon?: IconType;
|
dropdownTriggerIcon?: ReactNode;
|
||||||
/**
|
/**
|
||||||
* Text of the dropdown trigger.
|
* Text of the dropdown trigger.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import type { ReactNode, SyntheticEvent } from 'react';
|
import type { ReactNode, SyntheticEvent } from 'react';
|
||||||
import type { IconType } from '@superset-ui/core/components';
|
|
||||||
|
|
||||||
export type EmptyStateSize = 'small' | 'medium' | 'large';
|
export type EmptyStateSize = 'small' | 'medium' | 'large';
|
||||||
|
|
||||||
@@ -26,7 +25,7 @@ export type EmptyStateProps = {
|
|||||||
description?: ReactNode;
|
description?: ReactNode;
|
||||||
image?: ReactNode | string;
|
image?: ReactNode | string;
|
||||||
buttonText?: ReactNode;
|
buttonText?: ReactNode;
|
||||||
buttonIcon?: IconType;
|
buttonIcon?: ReactNode;
|
||||||
buttonAction?: (event: SyntheticEvent) => void;
|
buttonAction?: (event: SyntheticEvent) => void;
|
||||||
size?: EmptyStateSize;
|
size?: EmptyStateSize;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
|
|||||||
@@ -16,17 +16,4 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { Form as AntdForm } from 'antd';
|
export { Form } from 'antd';
|
||||||
import { FormProps } from './types';
|
|
||||||
|
|
||||||
function CustomForm(props: FormProps) {
|
|
||||||
return <AntdForm {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Form = Object.assign(CustomForm, {
|
|
||||||
useForm: AntdForm.useForm,
|
|
||||||
Item: AntdForm.Item,
|
|
||||||
List: AntdForm.List,
|
|
||||||
ErrorList: AntdForm.ErrorList,
|
|
||||||
Provider: AntdForm.Provider,
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
export type { FormProps, FormInstance, FormItemProps } from 'antd/es/form';
|
export type { FormProps, FormInstance, FormItemProps } from 'antd';
|
||||||
|
|
||||||
export interface LabeledErrorBoundInputProps {
|
export interface LabeledErrorBoundInputProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { FC } from 'react';
|
import { FC, PropsWithChildren } from 'react';
|
||||||
import { styled, useTheme, css } from '@apache-superset/core/ui';
|
import { styled, useTheme, css } from '@apache-superset/core/ui';
|
||||||
import { Skeleton } from '../Skeleton';
|
import { Skeleton } from '../Skeleton';
|
||||||
import { Card } from '../Card';
|
import { Card } from '../Card';
|
||||||
@@ -134,7 +134,7 @@ const ThinSkeleton = styled(Skeleton)`
|
|||||||
|
|
||||||
const paragraphConfig = { rows: 1, width: 150 };
|
const paragraphConfig = { rows: 1, width: 150 };
|
||||||
|
|
||||||
const AnchorLink: FC<LinkProps> = ({ to, children }) => (
|
const AnchorLink: FC<PropsWithChildren<LinkProps>> = ({ to, children }) => (
|
||||||
<a href={to}>{children}</a>
|
<a href={to}>{children}</a>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,12 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import type { ReactNode, ComponentType, ReactElement } from 'react';
|
import type {
|
||||||
|
ReactNode,
|
||||||
|
ComponentType,
|
||||||
|
ReactElement,
|
||||||
|
PropsWithChildren,
|
||||||
|
} from 'react';
|
||||||
import type { BackgroundPosition } from './ImageLoader';
|
import type { BackgroundPosition } from './ImageLoader';
|
||||||
|
|
||||||
export interface LinkProps {
|
export interface LinkProps {
|
||||||
@@ -27,7 +32,7 @@ export interface ListViewCardProps {
|
|||||||
title?: ReactNode;
|
title?: ReactNode;
|
||||||
subtitle?: ReactNode;
|
subtitle?: ReactNode;
|
||||||
url?: string;
|
url?: string;
|
||||||
linkComponent?: ComponentType<LinkProps>;
|
linkComponent?: ComponentType<PropsWithChildren<LinkProps>>;
|
||||||
imgURL?: string | null;
|
imgURL?: string | null;
|
||||||
imgFallbackURL?: string;
|
imgFallbackURL?: string;
|
||||||
imgPosition?: BackgroundPosition;
|
imgPosition?: BackgroundPosition;
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ const MetadataBar = ({ items, tooltipPlacement = 'top' }: MetadataBarProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onResize = useCallback(
|
const onResize = useCallback(
|
||||||
width => {
|
(width: number) => {
|
||||||
// Calculates the breakpoint width to collapse the bar.
|
// Calculates the breakpoint width to collapse the bar.
|
||||||
// The last item does not have a space, so we subtract SPACE_BETWEEN_ITEMS from the total.
|
// The last item does not have a space, so we subtract SPACE_BETWEEN_ITEMS from the total.
|
||||||
const breakpoint =
|
const breakpoint =
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export function FormModal({
|
|||||||
}, [onSave, resetForm]);
|
}, [onSave, resetForm]);
|
||||||
|
|
||||||
const handleFormSubmit = useCallback(
|
const handleFormSubmit = useCallback(
|
||||||
async values => {
|
async (values: Object) => {
|
||||||
try {
|
try {
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
await formSubmitHandler(values);
|
await formSubmitHandler(values);
|
||||||
@@ -113,7 +113,7 @@ export function FormModal({
|
|||||||
onValuesChange={onFormChange}
|
onValuesChange={onFormChange}
|
||||||
onFieldsChange={onFormChange}
|
onFieldsChange={onFormChange}
|
||||||
>
|
>
|
||||||
{typeof children === 'function' ? children(form) : children}
|
{children}
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { render, screen, fireEvent } from '@superset-ui/core/spec';
|
import { render, screen, fireEvent } from '@superset-ui/core/spec';
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { TableInstance, useTable } from 'react-table';
|
import { TableInstance, useTable } from 'react-table';
|
||||||
import TableCollection from '.';
|
import TableCollection from '.';
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
UseResizeColumnsColumnOptions,
|
UseResizeColumnsColumnOptions,
|
||||||
UseResizeColumnsColumnProps,
|
UseResizeColumnsColumnProps,
|
||||||
} from 'react-table';
|
} from 'react-table';
|
||||||
|
import { ColumnsType } from 'antd/es/table';
|
||||||
|
|
||||||
import { SortOrder } from '../Table';
|
import { SortOrder } from '../Table';
|
||||||
|
|
||||||
@@ -87,11 +88,11 @@ export function mapColumns<T extends object>(
|
|||||||
columns: EnhancedColumnInstance<T>[],
|
columns: EnhancedColumnInstance<T>[],
|
||||||
headerGroups: EnhancedHeaderGroup<T>[],
|
headerGroups: EnhancedHeaderGroup<T>[],
|
||||||
columnsForWrapText?: string[],
|
columnsForWrapText?: string[],
|
||||||
) {
|
): ColumnsType<object> {
|
||||||
return columns.map(column => {
|
return columns.map(column => {
|
||||||
const { isSorted, isSortedDesc } = getSortingInfo(headerGroups, column.id);
|
const { isSorted, isSortedDesc } = getSortingInfo(headerGroups, column.id);
|
||||||
return {
|
return {
|
||||||
title: column.Header,
|
title: column.Header as ReactNode,
|
||||||
dataIndex: column.id?.includes('.') ? column.id.split('.') : column.id,
|
dataIndex: column.id?.includes('.') ? column.id.split('.') : column.id,
|
||||||
hidden: column.hidden,
|
hidden: column.hidden,
|
||||||
key: column.id,
|
key: column.id,
|
||||||
@@ -121,7 +122,7 @@ export function mapColumns<T extends object>(
|
|||||||
column,
|
column,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return val;
|
return String(val);
|
||||||
},
|
},
|
||||||
className: column.className,
|
className: column.className,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -96,8 +96,8 @@ const StyledPlus = styled.span`
|
|||||||
|
|
||||||
export default function TruncatedList<ListItemType>({
|
export default function TruncatedList<ListItemType>({
|
||||||
items,
|
items,
|
||||||
renderVisibleItem = item => item,
|
renderVisibleItem = item => item as ReactNode,
|
||||||
renderTooltipItem = item => item,
|
renderTooltipItem = item => item as ReactNode,
|
||||||
getKey = item => item as unknown as Key,
|
getKey = item => item as unknown as Key,
|
||||||
maxLinks = 20,
|
maxLinks = 20,
|
||||||
}: TruncatedListProps<ListItemType>) {
|
}: TruncatedListProps<ListItemType>) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { useChangeEffect } from './useChangeEffect';
|
import { useChangeEffect } from './useChangeEffect';
|
||||||
|
|
||||||
test('call callback the first time with undefined and value', () => {
|
test('call callback the first time with undefined and value', () => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { useComponentDidMount } from './useComponentDidMount';
|
import { useComponentDidMount } from './useComponentDidMount';
|
||||||
|
|
||||||
test('the effect should only be executed on the first render', () => {
|
test('the effect should only be executed on the first render', () => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { useComponentDidUpdate } from './useComponentDidUpdate';
|
import { useComponentDidUpdate } from './useComponentDidUpdate';
|
||||||
|
|
||||||
test('the effect should not be executed on the first render', () => {
|
test('the effect should not be executed on the first render', () => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { useElementOnScreen } from './useElementOnScreen';
|
import { useElementOnScreen } from './useElementOnScreen';
|
||||||
|
|
||||||
const observeMock = jest.fn();
|
const observeMock = jest.fn();
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { usePrevious } from './usePrevious';
|
import { usePrevious } from './usePrevious';
|
||||||
|
|
||||||
test('get undefined on the first render when initialValue is not defined', () => {
|
test('get undefined on the first render when initialValue is not defined', () => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react';
|
||||||
import useCSSTextTruncation from './useCSSTextTruncation';
|
import useCSSTextTruncation from './useCSSTextTruncation';
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { RefObject } from 'react';
|
import { RefObject } from 'react';
|
||||||
import useChildElementTruncation from './useChildElementTruncation';
|
import useChildElementTruncation from './useChildElementTruncation';
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
MouseEventHandler,
|
MouseEventHandler,
|
||||||
ReactElement,
|
ReactElement,
|
||||||
ComponentType,
|
ComponentType,
|
||||||
|
PropsWithChildren,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import type { Editor } from 'brace';
|
import type { Editor } from 'brace';
|
||||||
import type { QueryData } from '../chart/types/QueryResponse';
|
import type { QueryData } from '../chart/types/QueryResponse';
|
||||||
@@ -249,7 +250,7 @@ export type Extensions = Partial<{
|
|||||||
'navbar.right-menu.item.icon': ComponentType<RightMenuItemIconProps>;
|
'navbar.right-menu.item.icon': ComponentType<RightMenuItemIconProps>;
|
||||||
'navbar.right': ComponentType;
|
'navbar.right': ComponentType;
|
||||||
'report-modal.dropdown.item.icon': ComponentType;
|
'report-modal.dropdown.item.icon': ComponentType;
|
||||||
'root.context.provider': ComponentType;
|
'root.context.provider': ComponentType<PropsWithChildren>;
|
||||||
'welcome.message': ComponentType;
|
'welcome.message': ComponentType;
|
||||||
'welcome.banner': ComponentType;
|
'welcome.banner': ComponentType;
|
||||||
'welcome.main.replacement': ComponentType;
|
'welcome.main.replacement': ComponentType;
|
||||||
|
|||||||
@@ -46,8 +46,8 @@
|
|||||||
"gh-pages": "^6.3.0",
|
"gh-pages": "^6.3.0",
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
"memoize-one": "^5.2.1",
|
"memoize-one": "^5.2.1",
|
||||||
"react": "^17.0.2",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^18.2.0",
|
||||||
"react-loadable": "^5.5.0",
|
"react-loadable": "^5.5.0",
|
||||||
"react-resizable": "^3.0.5"
|
"react-resizable": "^3.0.5"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,8 +32,8 @@
|
|||||||
"@emotion/react": "^11.4.1",
|
"@emotion/react": "^11.4.1",
|
||||||
"@superset-ui/chart-controls": "*",
|
"@superset-ui/chart-controls": "*",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@apache-superset/core": "*",
|
"react": "^18.2.0",
|
||||||
"react": "^17.0.2"
|
"@apache-superset/core": "*"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@superset-ui/chart-controls": "*",
|
"@superset-ui/chart-controls": "*",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@apache-superset/core": "*",
|
"react": "^18.2.0",
|
||||||
"react": "^17.0.2"
|
"@apache-superset/core": "*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,8 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@superset-ui/chart-controls": "*",
|
"@superset-ui/chart-controls": "*",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@apache-superset/core": "*",
|
"react": "^18.2.0",
|
||||||
"react": "^17.0.2"
|
"@apache-superset/core": "*"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@apache-superset/core": "*",
|
"@apache-superset/core": "*",
|
||||||
"mapbox-gl": "*",
|
"mapbox-gl": "*",
|
||||||
"react": "^17.0.2"
|
"react": "^18.2.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -30,8 +30,8 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@superset-ui/chart-controls": "*",
|
"@superset-ui/chart-controls": "*",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@apache-superset/core": "*",
|
"react": "^18.2.0",
|
||||||
"react": "^17.0.2"
|
"@apache-superset/core": "*"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@superset-ui/chart-controls": "*",
|
"@superset-ui/chart-controls": "*",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@apache-superset/core": "*",
|
"react": "^18.2.0",
|
||||||
"react": "^17.0.2"
|
"@apache-superset/core": "*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,9 @@
|
|||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@apache-superset/core": "*",
|
"@apache-superset/core": "*",
|
||||||
"@testing-library/jest-dom": "*",
|
"@testing-library/jest-dom": "*",
|
||||||
"@testing-library/react": "^12.1.5",
|
"@testing-library/react": "^14.0.0",
|
||||||
"react": "^17.0.2",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^17.0.2"
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -31,8 +31,8 @@
|
|||||||
"@emotion/react": "^11.4.1",
|
"@emotion/react": "^11.4.1",
|
||||||
"@superset-ui/chart-controls": "*",
|
"@superset-ui/chart-controls": "*",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@apache-superset/core": "*",
|
"react": "^18.2.0",
|
||||||
"react": "^17.0.2"
|
"@apache-superset/core": "*"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@superset-ui/chart-controls": "*",
|
"@superset-ui/chart-controls": "*",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@apache-superset/core": "*",
|
"react": "^18.2.0",
|
||||||
"react": "^17.0.2"
|
"@apache-superset/core": "*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,8 +64,8 @@
|
|||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@apache-superset/core": "*",
|
"@apache-superset/core": "*",
|
||||||
"mapbox-gl": "*",
|
"mapbox-gl": "*",
|
||||||
"react": "^17.0.2",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^18.2.0",
|
||||||
"react-map-gl": "^6.1.19"
|
"react-map-gl": "^6.1.19"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
@@ -196,5 +196,5 @@ export const DeckGLContainerStyledWrapper = styled(DeckGLContainer)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export type DeckGLContainerHandle = typeof DeckGLContainer & {
|
export type DeckGLContainerHandle = typeof DeckGLContainer & {
|
||||||
setTooltip: (tooltip: ReactNode) => void;
|
setTooltip: (tooltip: TooltipProps['tooltip']) => void;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
import { PickingInfo } from '@deck.gl/core';
|
import { PickingInfo } from '@deck.gl/core';
|
||||||
import { JsonObject, QueryFormData } from '@superset-ui/core';
|
import { JsonObject, QueryFormData } from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
@@ -98,9 +99,9 @@ describe('getAggFunc', () => {
|
|||||||
|
|
||||||
describe('commonLayerProps', () => {
|
describe('commonLayerProps', () => {
|
||||||
const mockSetTooltip = jest.fn();
|
const mockSetTooltip = jest.fn();
|
||||||
const mockSetTooltipContent = jest.fn(
|
const mockSetTooltipContent = jest
|
||||||
() => (o: JsonObject) => `Tooltip for ${o}`,
|
.fn()
|
||||||
);
|
.mockReturnValue((o: JsonObject) => `Tooltip for ${o}` as React.ReactNode);
|
||||||
const mockOnSelect = jest.fn();
|
const mockOnSelect = jest.fn();
|
||||||
|
|
||||||
it('returns correct props when js_tooltip is provided', () => {
|
it('returns correct props when js_tooltip is provided', () => {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@superset-ui/chart-controls": "*",
|
"@superset-ui/chart-controls": "*",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@apache-superset/core": "*",
|
"react": "^18.2.0",
|
||||||
"react": "^17.0.2"
|
"@apache-superset/core": "*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,14 +42,13 @@
|
|||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@testing-library/dom": "^8.20.1",
|
"@testing-library/dom": "^8.20.1",
|
||||||
"@testing-library/jest-dom": "*",
|
"@testing-library/jest-dom": "*",
|
||||||
"@testing-library/react": "^12.1.5",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@testing-library/react-hooks": "*",
|
|
||||||
"@testing-library/user-event": "*",
|
"@testing-library/user-event": "*",
|
||||||
"@types/classnames": "*",
|
"@types/classnames": "*",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"match-sorter": "^6.3.3",
|
"match-sorter": "^6.3.3",
|
||||||
"react": "^17.0.2",
|
"react": "^18.0.0",
|
||||||
"react-dom": "^17.0.2"
|
"react-dom": "^18.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -37,10 +37,8 @@ import {
|
|||||||
type ColDef,
|
type ColDef,
|
||||||
type ColumnState,
|
type ColumnState,
|
||||||
ModuleRegistry,
|
ModuleRegistry,
|
||||||
GridReadyEvent,
|
|
||||||
GridState,
|
GridState,
|
||||||
CellClickedEvent,
|
GridReadyEvent,
|
||||||
IMenuActionParams,
|
|
||||||
} from '@superset-ui/core/components/ThemedAgGridReact';
|
} from '@superset-ui/core/components/ThemedAgGridReact';
|
||||||
import {
|
import {
|
||||||
AgGridChartState,
|
AgGridChartState,
|
||||||
@@ -74,7 +72,7 @@ export interface AgGridTableProps {
|
|||||||
gridHeight?: number;
|
gridHeight?: number;
|
||||||
updateInterval?: number;
|
updateInterval?: number;
|
||||||
data?: any[];
|
data?: any[];
|
||||||
onGridReady?: (params: GridReadyEvent) => void;
|
onGridReady?: (params: any) => void;
|
||||||
colDefsFromProps: any[];
|
colDefsFromProps: any[];
|
||||||
includeSearch: boolean;
|
includeSearch: boolean;
|
||||||
allowRearrangeColumns: boolean;
|
allowRearrangeColumns: boolean;
|
||||||
@@ -93,7 +91,7 @@ export interface AgGridTableProps {
|
|||||||
percentMetrics: string[];
|
percentMetrics: string[];
|
||||||
serverPageLength: number;
|
serverPageLength: number;
|
||||||
hasServerPageLengthChanged: boolean;
|
hasServerPageLengthChanged: boolean;
|
||||||
handleCrossFilter: (event: CellClickedEvent | IMenuActionParams) => void;
|
handleCrossFilter: (event: any) => void;
|
||||||
isActiveFilterValue: (key: string, val: DataRecordValue) => boolean;
|
isActiveFilterValue: (key: string, val: DataRecordValue) => boolean;
|
||||||
renderTimeComparisonDropdown: () => JSX.Element | null;
|
renderTimeComparisonDropdown: () => JSX.Element | null;
|
||||||
cleanedTotals: DataRecord;
|
cleanedTotals: DataRecord;
|
||||||
@@ -279,7 +277,7 @@ const AgGridDataTable: FunctionComponent<AgGridTableProps> = memo(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleColumnHeaderClick = useCallback(
|
const handleColumnHeaderClick = useCallback(
|
||||||
params => {
|
(params: any) => {
|
||||||
const colId = params?.column?.colId;
|
const colId = params?.column?.colId;
|
||||||
const sortDir = params?.column?.sort;
|
const sortDir = params?.column?.sort;
|
||||||
handleColSort(colId, sortDir);
|
handleColSort(colId, sortDir);
|
||||||
@@ -423,8 +421,8 @@ const AgGridDataTable: FunctionComponent<AgGridTableProps> = memo(
|
|||||||
rowData={rowData}
|
rowData={rowData}
|
||||||
headerHeight={36}
|
headerHeight={36}
|
||||||
rowHeight={30}
|
rowHeight={30}
|
||||||
columnDefs={colDefsFromProps}
|
columnDefs={colDefsFromProps as any}
|
||||||
defaultColDef={defaultColDef}
|
defaultColDef={defaultColDef as any}
|
||||||
onColumnGroupOpened={params => params.api.sizeColumnsToFit()}
|
onColumnGroupOpened={params => params.api.sizeColumnsToFit()}
|
||||||
rowSelection="multiple"
|
rowSelection="multiple"
|
||||||
animateRows
|
animateRows
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const timestampFormatter = useCallback(
|
const timestampFormatter = useCallback(
|
||||||
value => getTimeFormatterForGranularity(timeGrain)(value),
|
(value: any) => getTimeFormatterForGranularity(timeGrain)(value),
|
||||||
[timeGrain],
|
[timeGrain],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -67,5 +67,5 @@ export const TextCellRenderer = (params: CellRendererProps) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div>{valueFormatted ?? value}</div>;
|
return <div>{String(valueFormatted ?? value)}</div>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
"geostyler-wfs-parser": "^2.0.0",
|
"geostyler-wfs-parser": "^2.0.0",
|
||||||
"ol": "^7.1.0",
|
"ol": "^7.1.0",
|
||||||
"polished": "*",
|
"polished": "*",
|
||||||
"react": "^17.0.2",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^17.0.2"
|
"react-dom": "^18.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
import Layer from 'ol/layer/Layer';
|
import Layer from 'ol/layer/Layer';
|
||||||
import { FrameState } from 'ol/Map';
|
import { FrameState } from 'ol/Map';
|
||||||
import { apply as applyTransform } from 'ol/transform';
|
import { apply as applyTransform } from 'ol/transform';
|
||||||
import ReactDOM from 'react-dom';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { SupersetTheme } from '@apache-superset/core/ui';
|
import { SupersetTheme } from '@apache-superset/core/ui';
|
||||||
import { ChartConfig, ChartLayerOptions, ChartSizeValues } from '../types';
|
import { ChartConfig, ChartLayerOptions, ChartSizeValues } from '../types';
|
||||||
import { createChartComponent } from '../util/chartUtil';
|
import { createChartComponent } from '../util/chartUtil';
|
||||||
@@ -33,6 +33,8 @@ import Loader from '../images/loading.gif';
|
|||||||
export class ChartLayer extends Layer {
|
export class ChartLayer extends Layer {
|
||||||
charts: any[] = [];
|
charts: any[] = [];
|
||||||
|
|
||||||
|
chartRoots: Map<HTMLElement, any> = new Map();
|
||||||
|
|
||||||
chartConfigs: ChartConfig = {
|
chartConfigs: ChartConfig = {
|
||||||
type: 'FeatureCollection',
|
type: 'FeatureCollection',
|
||||||
features: [],
|
features: [],
|
||||||
@@ -166,7 +168,11 @@ export class ChartLayer extends Layer {
|
|||||||
*/
|
*/
|
||||||
removeAllChartElements() {
|
removeAllChartElements() {
|
||||||
this.charts.forEach(chart => {
|
this.charts.forEach(chart => {
|
||||||
ReactDOM.unmountComponentAtNode(chart.htmlElement);
|
const root = this.chartRoots.get(chart.htmlElement);
|
||||||
|
if (root) {
|
||||||
|
root.unmount();
|
||||||
|
this.chartRoots.delete(chart.htmlElement);
|
||||||
|
}
|
||||||
chart.htmlElement.remove();
|
chart.htmlElement.remove();
|
||||||
});
|
});
|
||||||
this.charts = [];
|
this.charts = [];
|
||||||
@@ -191,7 +197,9 @@ export class ChartLayer extends Layer {
|
|||||||
this.theme,
|
this.theme,
|
||||||
this.locale,
|
this.locale,
|
||||||
);
|
);
|
||||||
ReactDOM.render(chartComponent, container);
|
const root = createRoot(container);
|
||||||
|
this.chartRoots.set(container, root);
|
||||||
|
root.render(chartComponent);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
htmlElement: container,
|
htmlElement: container,
|
||||||
@@ -227,7 +235,10 @@ export class ChartLayer extends Layer {
|
|||||||
this.theme,
|
this.theme,
|
||||||
this.locale,
|
this.locale,
|
||||||
);
|
);
|
||||||
ReactDOM.render(chartComponent, chart.htmlElement);
|
const root = this.chartRoots.get(chart.htmlElement);
|
||||||
|
if (root) {
|
||||||
|
root.render(chartComponent);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...chart,
|
...chart,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"echarts": "*",
|
"echarts": "*",
|
||||||
"memoize-one": "*",
|
"memoize-one": "*",
|
||||||
"react": "^17.0.2"
|
"react": "^18.2.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export default function EchartsMixedTimeseries({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const getCrossFilterDataMask = useCallback(
|
const getCrossFilterDataMask = useCallback(
|
||||||
(seriesName, seriesIndex) => {
|
(seriesName: any, seriesIndex: any) => {
|
||||||
const selected: string[] = Object.values(selectedValues || {});
|
const selected: string[] = Object.values(selectedValues || {});
|
||||||
let values: string[];
|
let values: string[];
|
||||||
if (selected.includes(seriesName)) {
|
if (selected.includes(seriesName)) {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default function EchartsTreemap({
|
|||||||
coltypeMapping,
|
coltypeMapping,
|
||||||
}: TreemapTransformedProps) {
|
}: TreemapTransformedProps) {
|
||||||
const getCrossFilterDataMask = useCallback(
|
const getCrossFilterDataMask = useCallback(
|
||||||
(data, treePathInfo) => {
|
(data: any, treePathInfo: any) => {
|
||||||
if (data?.children) {
|
if (data?.children) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ export default function EchartsTreemap({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(data, treePathInfo) => {
|
(data: any, treePathInfo: any) => {
|
||||||
if (!emitCrossFilters || groupby.length === 0) {
|
if (!emitCrossFilters || groupby.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"currencyformatter.js": "^1.0.5",
|
||||||
"handlebars-group-by": "^1.0.1",
|
"handlebars-group-by": "^1.0.1",
|
||||||
"just-handlebars-helpers": "^1.0.19"
|
"just-handlebars-helpers": "^1.0.19"
|
||||||
},
|
},
|
||||||
@@ -35,12 +36,12 @@
|
|||||||
"@apache-superset/core": "*",
|
"@apache-superset/core": "*",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"ace-builds": "^1.4.14",
|
"ace-builds": "^1.4.14",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
"dayjs": "^1.11.13",
|
"react": "^18.2.0",
|
||||||
"react": "^17.0.2",
|
|
||||||
"react-ace": "^10.1.0",
|
"react-ace": "^10.1.0",
|
||||||
"react-dom": "^17.0.2"
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ ${helperDescriptions
|
|||||||
<div>
|
<div>
|
||||||
<ControlHeader>
|
<ControlHeader>
|
||||||
<div>
|
<div>
|
||||||
{props.label}
|
{props.label as any}
|
||||||
<InfoTooltip
|
<InfoTooltip
|
||||||
iconStyle={{ marginLeft: theme.sizeUnit }}
|
iconStyle={{ marginLeft: theme.sizeUnit }}
|
||||||
tooltip={<SafeMarkdown source={helpersTooltipContent} />}
|
tooltip={<SafeMarkdown source={helpersTooltipContent} />}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const StyleControl = (props: CustomControlConfig<StyleCustomControlProps>) => {
|
|||||||
<div>
|
<div>
|
||||||
<ControlHeader>
|
<ControlHeader>
|
||||||
<div>
|
<div>
|
||||||
{props.label}
|
{props.label as any}
|
||||||
<InfoTooltip
|
<InfoTooltip
|
||||||
iconStyle={{ marginLeft: theme.sizeUnit }}
|
iconStyle={{ marginLeft: theme.sizeUnit }}
|
||||||
tooltip={t('You need to configure HTML sanitization to use CSS')}
|
tooltip={t('You need to configure HTML sanitization to use CSS')}
|
||||||
|
|||||||
@@ -33,8 +33,8 @@
|
|||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
"prop-types": "*",
|
"prop-types": "*",
|
||||||
"react": "^17.0.2",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^17.0.2"
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/types": "^7.28.4",
|
"@babel/types": "^7.28.4",
|
||||||
|
|||||||
@@ -42,14 +42,13 @@
|
|||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@testing-library/dom": "^8.20.1",
|
"@testing-library/dom": "^8.20.1",
|
||||||
"@testing-library/jest-dom": "*",
|
"@testing-library/jest-dom": "*",
|
||||||
"@testing-library/react": "^12.1.5",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@testing-library/react-hooks": "*",
|
|
||||||
"@testing-library/user-event": "*",
|
"@testing-library/user-event": "*",
|
||||||
"@types/classnames": "*",
|
"@types/classnames": "*",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"match-sorter": "^6.3.3",
|
"match-sorter": "^6.3.3",
|
||||||
"react": "^17.0.2",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^17.0.2"
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -347,7 +347,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const timestampFormatter = useCallback(
|
const timestampFormatter = useCallback(
|
||||||
value => getTimeFormatterForGranularity(timeGrain)(value),
|
(value: any) => getTimeFormatterForGranularity(timeGrain)(value),
|
||||||
[timeGrain],
|
[timeGrain],
|
||||||
);
|
);
|
||||||
const [tableSize, setTableSize] = useState<TableSize>({
|
const [tableSize, setTableSize] = useState<TableSize>({
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
"@apache-superset/core": "*",
|
"@apache-superset/core": "*",
|
||||||
"@types/lodash": "*",
|
"@types/lodash": "*",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"react": "^17.0.2"
|
"react": "^18.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/d3-cloud": "^1.2.9"
|
"@types/d3-cloud": "^1.2.9"
|
||||||
|
|||||||
@@ -35,8 +35,7 @@ import { SupersetThemeProvider } from 'src/theme/ThemeProvider';
|
|||||||
import { ThemeController } from 'src/theme/ThemeController';
|
import { ThemeController } from 'src/theme/ThemeController';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { DndProvider } from 'react-dnd';
|
import { DndContext, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
|
||||||
import reducerIndex from 'spec/helpers/reducerIndex';
|
import reducerIndex from 'spec/helpers/reducerIndex';
|
||||||
import { QueryParamProvider } from 'use-query-params';
|
import { QueryParamProvider } from 'use-query-params';
|
||||||
import { configureStore, Store } from '@reduxjs/toolkit';
|
import { configureStore, Store } from '@reduxjs/toolkit';
|
||||||
@@ -99,7 +98,17 @@ export function createWrapper(options?: Options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (useDnd) {
|
if (useDnd) {
|
||||||
result = <DndProvider backend={HTML5Backend}>{result}</DndProvider>;
|
const DndWrapper = ({ children }: { children: ReactNode }) => {
|
||||||
|
const sensors = useSensors(
|
||||||
|
useSensor(PointerSensor, {
|
||||||
|
activationConstraint: {
|
||||||
|
distance: 8,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return <DndContext sensors={sensors}>{children}</DndContext>;
|
||||||
|
};
|
||||||
|
result = <DndWrapper>{result}</DndWrapper>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useRedux || store) {
|
if (useRedux || store) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import fetchMock from 'fetch-mock';
|
import fetchMock from 'fetch-mock';
|
||||||
import { act, renderHook } from '@testing-library/react-hooks';
|
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||||
import { COMMON_ERR_MESSAGES } from '@superset-ui/core';
|
import { COMMON_ERR_MESSAGES } from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
createWrapper,
|
createWrapper,
|
||||||
@@ -119,7 +119,7 @@ test('skips fetching validation if validator is undefined', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('returns validation if validator is configured', async () => {
|
test('returns validation if validator is configured', async () => {
|
||||||
const { result, waitFor } = initialize(true);
|
const { result } = initialize(true);
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(fetchMock.calls(queryValidationApiRoute)).toHaveLength(1),
|
expect(fetchMock.calls(queryValidationApiRoute)).toHaveLength(1),
|
||||||
);
|
);
|
||||||
@@ -142,7 +142,7 @@ test('returns server error description', async () => {
|
|||||||
},
|
},
|
||||||
{ overwriteRoutes: true },
|
{ overwriteRoutes: true },
|
||||||
);
|
);
|
||||||
const { result, waitFor } = initialize(true);
|
const { result } = initialize(true);
|
||||||
await waitFor(
|
await waitFor(
|
||||||
() =>
|
() =>
|
||||||
expect(result.current.data).toEqual([
|
expect(result.current.data).toEqual([
|
||||||
@@ -166,7 +166,7 @@ test('returns session expire description when CSRF token expired', async () => {
|
|||||||
},
|
},
|
||||||
{ overwriteRoutes: true },
|
{ overwriteRoutes: true },
|
||||||
);
|
);
|
||||||
const { result, waitFor } = initialize(true);
|
const { result } = initialize(true);
|
||||||
await waitFor(
|
await waitFor(
|
||||||
() =>
|
() =>
|
||||||
expect(result.current.data).toEqual([
|
expect(result.current.data).toEqual([
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import fetchMock from 'fetch-mock';
|
import fetchMock from 'fetch-mock';
|
||||||
import { act, renderHook } from '@testing-library/react-hooks';
|
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||||
import { getExtensionsRegistry } from '@superset-ui/core';
|
import { getExtensionsRegistry } from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
createWrapper,
|
createWrapper,
|
||||||
@@ -104,7 +104,7 @@ test('returns keywords including fetched function_names data', async () => {
|
|||||||
const dbFunctionNamesApiRoute = `glob:*/api/v1/database/${expectDbId}/function_names/`;
|
const dbFunctionNamesApiRoute = `glob:*/api/v1/database/${expectDbId}/function_names/`;
|
||||||
fetchMock.get(dbFunctionNamesApiRoute, fakeFunctionNamesApiResult);
|
fetchMock.get(dbFunctionNamesApiRoute, fakeFunctionNamesApiResult);
|
||||||
|
|
||||||
const { result, waitFor } = renderHook(
|
const { result } = renderHook(
|
||||||
() =>
|
() =>
|
||||||
useKeywords({
|
useKeywords({
|
||||||
queryEditorId: 'testqueryid',
|
queryEditorId: 'testqueryid',
|
||||||
@@ -240,7 +240,7 @@ test('returns column keywords among selected tables', async () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const { result, waitFor } = renderHook(
|
const { result } = renderHook(
|
||||||
() =>
|
() =>
|
||||||
useKeywords({
|
useKeywords({
|
||||||
queryEditorId: expectQueryEditorId,
|
queryEditorId: expectQueryEditorId,
|
||||||
@@ -315,7 +315,7 @@ test('returns long keywords with docText', async () => {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
const { result, waitFor } = renderHook(
|
const { result } = renderHook(
|
||||||
() =>
|
() =>
|
||||||
useKeywords({
|
useKeywords({
|
||||||
queryEditorId: 'testqueryid',
|
queryEditorId: 'testqueryid',
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { FC } from 'react';
|
import { FC, ReactNode } from 'react';
|
||||||
import { t } from '@superset-ui/core';
|
import { t } from '@superset-ui/core';
|
||||||
import { styled, css } from '@apache-superset/core/ui';
|
import { styled, css } from '@apache-superset/core/ui';
|
||||||
import { ModalTrigger } from '@superset-ui/core/components';
|
import { ModalTrigger } from '@superset-ui/core/components';
|
||||||
@@ -92,7 +92,7 @@ const ShortcutCode = styled.code`
|
|||||||
padding: ${({ theme }) => `${theme.sizeUnit}px ${theme.sizeUnit * 2}px`};
|
padding: ${({ theme }) => `${theme.sizeUnit}px ${theme.sizeUnit * 2}px`};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const KeyboardShortcutButton: FC<{}> = ({ children }) => (
|
const KeyboardShortcutButton: FC<{ children: ReactNode }> = ({ children }) => (
|
||||||
<ModalTrigger
|
<ModalTrigger
|
||||||
modalTitle={t('Keyboard shortcuts')}
|
modalTitle={t('Keyboard shortcuts')}
|
||||||
modalBody={
|
modalBody={
|
||||||
|
|||||||
@@ -48,14 +48,25 @@ import { StaticPosition, StyledTooltip } from './styles';
|
|||||||
interface QueryTableQuery
|
interface QueryTableQuery
|
||||||
extends Omit<
|
extends Omit<
|
||||||
QueryResponse,
|
QueryResponse,
|
||||||
'state' | 'sql' | 'progress' | 'results' | 'duration' | 'started'
|
| 'state'
|
||||||
|
| 'sql'
|
||||||
|
| 'progress'
|
||||||
|
| 'results'
|
||||||
|
| 'duration'
|
||||||
|
| 'started'
|
||||||
|
| 'user'
|
||||||
|
| 'db'
|
||||||
|
| 'querylink'
|
||||||
> {
|
> {
|
||||||
state?: Record<string, any>;
|
state?: Record<string, any>;
|
||||||
sql?: Record<string, any>;
|
sql?: ReactNode;
|
||||||
progress?: Record<string, any>;
|
progress?: Record<string, any>;
|
||||||
results?: Record<string, any>;
|
results?: Record<string, any>;
|
||||||
duration?: ReactNode;
|
duration?: ReactNode;
|
||||||
started?: ReactNode;
|
started?: ReactNode;
|
||||||
|
user?: ReactNode;
|
||||||
|
db?: ReactNode;
|
||||||
|
querylink?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QueryTableProps {
|
interface QueryTableProps {
|
||||||
@@ -235,7 +246,7 @@ const QueryTable = ({
|
|||||||
return queries
|
return queries
|
||||||
.map(query => {
|
.map(query => {
|
||||||
const { state, sql, progress, ...rest } = query;
|
const { state, sql, progress, ...rest } = query;
|
||||||
const q = rest as QueryTableQuery;
|
const q = { ...rest } as unknown as QueryTableQuery;
|
||||||
|
|
||||||
const status = statusAttributes[state] || statusAttributes.error;
|
const status = statusAttributes[state] || statusAttributes.error;
|
||||||
|
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ const RunQueryActionButton = ({
|
|||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
buttonStyle: shouldShowStopBtn ? 'danger' : 'primary',
|
buttonStyle: shouldShowStopBtn ? 'danger' : 'primary',
|
||||||
icon,
|
icon: icon as any,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ const SouthPane = ({
|
|||||||
dispatch(setActiveSouthPaneTab(id));
|
dispatch(setActiveSouthPaneTab(id));
|
||||||
};
|
};
|
||||||
const removeTable = useCallback(
|
const removeTable = useCallback(
|
||||||
(key, action) => {
|
(key: any, action: any) => {
|
||||||
if (action === 'remove') {
|
if (action === 'remove') {
|
||||||
const table = pinnedTables.find(
|
const table = pinnedTables.find(
|
||||||
({ dbId, catalog, schema, name }) =>
|
({ dbId, catalog, schema, name }) =>
|
||||||
|
|||||||
@@ -580,7 +580,7 @@ const SqlEditor: FC<Props> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const setQueryEditorAndSaveSql = useCallback(
|
const setQueryEditorAndSaveSql = useCallback(
|
||||||
sql => {
|
(sql: any) => {
|
||||||
dispatch(queryEditorSetAndSaveSql(queryEditor, sql));
|
dispatch(queryEditorSetAndSaveSql(queryEditor, sql));
|
||||||
},
|
},
|
||||||
[dispatch, queryEditor],
|
[dispatch, queryEditor],
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class TabbedSqlEditors extends PureComponent<TabbedSqlEditorsProps> {
|
|||||||
new: isNewQuery,
|
new: isNewQuery,
|
||||||
...urlParams
|
...urlParams
|
||||||
} = {
|
} = {
|
||||||
...this.context.requestedQuery,
|
...(this.context as any).requestedQuery,
|
||||||
...bootstrapData.requested_query,
|
...bootstrapData.requested_query,
|
||||||
...queryParameters,
|
...queryParameters,
|
||||||
} as Record<string, string>;
|
} as Record<string, string>;
|
||||||
@@ -135,7 +135,7 @@ class TabbedSqlEditors extends PureComponent<TabbedSqlEditorsProps> {
|
|||||||
schema,
|
schema,
|
||||||
autorun,
|
autorun,
|
||||||
sql,
|
sql,
|
||||||
isDataset: this.context.isDataset,
|
isDataset: (this.context as any).isDataset,
|
||||||
};
|
};
|
||||||
this.props.actions.addQueryEditor(newQueryEditor);
|
this.props.actions.addQueryEditor(newQueryEditor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
import configureStore from 'redux-mock-store';
|
import configureStore from 'redux-mock-store';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
import { initialState, defaultQueryEditor } from 'src/SqlLab/fixtures';
|
import { initialState, defaultQueryEditor } from 'src/SqlLab/fixtures';
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { createWrapper } from 'spec/helpers/testing-library';
|
import { createWrapper } from 'spec/helpers/testing-library';
|
||||||
|
|
||||||
import useQueryEditor from '.';
|
import useQueryEditor from '.';
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { PureComponent } from 'react';
|
import { PureComponent, ErrorInfo } from 'react';
|
||||||
import {
|
import {
|
||||||
ensureIsArray,
|
ensureIsArray,
|
||||||
FeatureFlag,
|
FeatureFlag,
|
||||||
@@ -205,16 +205,13 @@ class Chart extends PureComponent<ChartProps, {}> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRenderContainerFailure(
|
handleRenderContainerFailure(error: Error, info: ErrorInfo) {
|
||||||
error: Error,
|
|
||||||
info: { componentStack: string } | null,
|
|
||||||
) {
|
|
||||||
const { actions, chartId } = this.props;
|
const { actions, chartId } = this.props;
|
||||||
logging.warn(error);
|
logging.warn(error);
|
||||||
actions.chartRenderingFailed(
|
actions.chartRenderingFailed(
|
||||||
error.toString(),
|
error.toString(),
|
||||||
chartId,
|
chartId,
|
||||||
info ? info.componentStack : null,
|
info ? (info.componentStack ?? null) : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
actions.logEvent(LOG_ACTIONS_RENDER_CHART, {
|
actions.logEvent(LOG_ACTIONS_RENDER_CHART, {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
import { FeatureFlag, VizType } from '@superset-ui/core';
|
import { FeatureFlag, VizType } from '@superset-ui/core';
|
||||||
import { render, screen } from 'spec/helpers/testing-library';
|
import { render, screen } from 'spec/helpers/testing-library';
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react';
|
||||||
import mockState from 'spec/fixtures/mockState';
|
import mockState from 'spec/fixtures/mockState';
|
||||||
import { sliceId } from 'spec/fixtures/mockChartQueries';
|
import { sliceId } from 'spec/fixtures/mockChartQueries';
|
||||||
import { noOp } from 'src/utils/common';
|
import { noOp } from 'src/utils/common';
|
||||||
|
|||||||
@@ -0,0 +1,274 @@
|
|||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
CSSProperties,
|
||||||
|
ReactNode,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import { Menu } from '@superset-ui/core/components/Menu';
|
||||||
|
import {
|
||||||
|
BaseFormData,
|
||||||
|
Behavior,
|
||||||
|
Column,
|
||||||
|
ContextMenuFilters,
|
||||||
|
css,
|
||||||
|
ensureIsArray,
|
||||||
|
getChartMetadataRegistry,
|
||||||
|
t,
|
||||||
|
useTheme,
|
||||||
|
} from '@superset-ui/core';
|
||||||
|
import { Constants, Input, Loading } from '@superset-ui/core/components';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import { FixedSizeList as List } from 'react-window';
|
||||||
|
import { Icons } from '@superset-ui/core/components/Icons';
|
||||||
|
import { InputRef } from 'antd';
|
||||||
|
import { MenuItemTooltip } from '../DisabledMenuItemTooltip';
|
||||||
|
import { getSubmenuYOffset } from '../utils';
|
||||||
|
import { VirtualizedMenuItem } from '../MenuItemWithTruncation';
|
||||||
|
import { Dataset } from '../types';
|
||||||
|
|
||||||
|
const SUBMENU_HEIGHT = 200;
|
||||||
|
const SHOW_COLUMNS_SEARCH_THRESHOLD = 10;
|
||||||
|
const SEARCH_INPUT_HEIGHT = 48;
|
||||||
|
|
||||||
|
export interface DrillByMenuItemsProps {
|
||||||
|
drillByConfig?: ContextMenuFilters['drillBy'];
|
||||||
|
formData: BaseFormData & { [key: string]: any };
|
||||||
|
contextMenuY?: number;
|
||||||
|
submenuIndex?: number;
|
||||||
|
onSelection?: (...args: any) => void;
|
||||||
|
onClick?: (event: MouseEvent) => void;
|
||||||
|
onCloseMenu?: () => void;
|
||||||
|
openNewModal?: boolean;
|
||||||
|
excludedColumns?: Column[];
|
||||||
|
open: boolean;
|
||||||
|
onDrillBy?: (column: Column, dataset: Dataset) => void;
|
||||||
|
dataset?: Dataset;
|
||||||
|
isLoadingDataset?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DrillByMenuItems = ({
|
||||||
|
drillByConfig,
|
||||||
|
formData,
|
||||||
|
contextMenuY = 0,
|
||||||
|
submenuIndex = 0,
|
||||||
|
onSelection = () => {},
|
||||||
|
onClick = () => {},
|
||||||
|
onCloseMenu = () => {},
|
||||||
|
excludedColumns,
|
||||||
|
openNewModal = true,
|
||||||
|
open,
|
||||||
|
onDrillBy,
|
||||||
|
dataset,
|
||||||
|
isLoadingDataset = false,
|
||||||
|
...rest
|
||||||
|
}: DrillByMenuItemsProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const [searchInput, setSearchInput] = useState('');
|
||||||
|
const [debouncedSearchInput, setDebouncedSearchInput] = useState('');
|
||||||
|
const ref = useRef<InputRef>(null);
|
||||||
|
const columns = dataset ? ensureIsArray(dataset.drillable_columns) : [];
|
||||||
|
const showSearch = columns.length > SHOW_COLUMNS_SEARCH_THRESHOLD;
|
||||||
|
|
||||||
|
const handleSelection = useCallback(
|
||||||
|
(event: any, column: any) => {
|
||||||
|
onClick(event);
|
||||||
|
onSelection(column, drillByConfig);
|
||||||
|
if (openNewModal && onDrillBy && dataset) {
|
||||||
|
onDrillBy(column, dataset);
|
||||||
|
}
|
||||||
|
onCloseMenu();
|
||||||
|
},
|
||||||
|
[drillByConfig, onClick, onSelection, openNewModal, onDrillBy, dataset],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
ref.current?.input?.focus({ preventScroll: true });
|
||||||
|
} else {
|
||||||
|
// Reset search input when menu is closed
|
||||||
|
setSearchInput('');
|
||||||
|
setDebouncedSearchInput('');
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
const hasDrillBy = drillByConfig?.groupbyFieldName;
|
||||||
|
|
||||||
|
const handlesDimensionContextMenu = useMemo(
|
||||||
|
() =>
|
||||||
|
getChartMetadataRegistry()
|
||||||
|
.get(formData.viz_type)
|
||||||
|
?.behaviors.find(behavior => behavior === Behavior.DrillBy),
|
||||||
|
[formData.viz_type],
|
||||||
|
);
|
||||||
|
|
||||||
|
const debouncedSetSearchInput = useMemo(
|
||||||
|
() =>
|
||||||
|
debounce((value: string) => {
|
||||||
|
setDebouncedSearchInput(value);
|
||||||
|
}, Constants.FAST_DEBOUNCE),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleInput = (value: string) => {
|
||||||
|
setSearchInput(value);
|
||||||
|
debouncedSetSearchInput(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredColumns = useMemo(
|
||||||
|
() =>
|
||||||
|
columns.filter(column =>
|
||||||
|
(column.verbose_name || column.column_name)
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(debouncedSearchInput.toLowerCase()),
|
||||||
|
),
|
||||||
|
[columns, debouncedSearchInput],
|
||||||
|
);
|
||||||
|
|
||||||
|
const submenuYOffset = useMemo(
|
||||||
|
() =>
|
||||||
|
getSubmenuYOffset(
|
||||||
|
contextMenuY,
|
||||||
|
filteredColumns.length || 1,
|
||||||
|
submenuIndex,
|
||||||
|
SUBMENU_HEIGHT,
|
||||||
|
showSearch ? SEARCH_INPUT_HEIGHT : 0,
|
||||||
|
),
|
||||||
|
[contextMenuY, filteredColumns.length, submenuIndex, showSearch],
|
||||||
|
);
|
||||||
|
|
||||||
|
let tooltip: ReactNode;
|
||||||
|
|
||||||
|
if (!handlesDimensionContextMenu) {
|
||||||
|
tooltip = t('Drill by is not yet supported for this chart type');
|
||||||
|
} else if (!hasDrillBy) {
|
||||||
|
tooltip = t('Drill by is not available for this data point');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handlesDimensionContextMenu || !hasDrillBy) {
|
||||||
|
return (
|
||||||
|
<Menu.Item key="drill-by-disabled" disabled {...rest}>
|
||||||
|
<div>
|
||||||
|
{t('Drill by')}
|
||||||
|
<MenuItemTooltip title={tooltip} />
|
||||||
|
</div>
|
||||||
|
</Menu.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Row = ({
|
||||||
|
index,
|
||||||
|
data,
|
||||||
|
style,
|
||||||
|
}: {
|
||||||
|
index: number;
|
||||||
|
data: { columns: Column[] };
|
||||||
|
style: CSSProperties;
|
||||||
|
}) => {
|
||||||
|
const { columns, ...rest } = data;
|
||||||
|
const column = columns[index];
|
||||||
|
return (
|
||||||
|
<VirtualizedMenuItem
|
||||||
|
tooltipText={column.verbose_name || column.column_name}
|
||||||
|
onClick={e => handleSelection(e, column)}
|
||||||
|
style={style}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{column.verbose_name || column.column_name}
|
||||||
|
</VirtualizedMenuItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Don't render drill by menu items when matrixify is enabled
|
||||||
|
if (formData.matrixify_enabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Menu.SubMenu
|
||||||
|
key="drill-by-submenu"
|
||||||
|
title={t('Drill by')}
|
||||||
|
popupClassName="chart-context-submenu"
|
||||||
|
popupOffset={[0, submenuYOffset]}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<div data-test="drill-by-submenu">
|
||||||
|
{showSearch && (
|
||||||
|
<Input
|
||||||
|
ref={ref}
|
||||||
|
prefix={
|
||||||
|
<Icons.SearchOutlined
|
||||||
|
iconSize="l"
|
||||||
|
iconColor={theme.colorIcon}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onChange={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleInput(e.target.value);
|
||||||
|
}}
|
||||||
|
placeholder={t('Search columns')}
|
||||||
|
onClick={e => {
|
||||||
|
// prevent closing menu when clicking on input
|
||||||
|
e.nativeEvent.stopImmediatePropagation();
|
||||||
|
}}
|
||||||
|
allowClear
|
||||||
|
css={css`
|
||||||
|
width: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: ${theme.sizeUnit * 2}px ${theme.sizeUnit * 3}px;
|
||||||
|
box-shadow: none;
|
||||||
|
`}
|
||||||
|
value={searchInput}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isLoadingDataset ? (
|
||||||
|
<div
|
||||||
|
css={css`
|
||||||
|
padding: ${theme.sizeUnit * 3}px 0;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Loading position="inline-centered" />
|
||||||
|
</div>
|
||||||
|
) : filteredColumns.length ? (
|
||||||
|
<List
|
||||||
|
width="100%"
|
||||||
|
height={SUBMENU_HEIGHT}
|
||||||
|
itemSize={35}
|
||||||
|
itemCount={filteredColumns.length}
|
||||||
|
itemData={{ columns: filteredColumns, ...rest }}
|
||||||
|
overscanCount={20}
|
||||||
|
>
|
||||||
|
{Row}
|
||||||
|
</List>
|
||||||
|
) : (
|
||||||
|
<Menu.Item disabled key="no-drill-by-columns-found" {...rest}>
|
||||||
|
{t('No columns found')}
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Menu.SubMenu>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react';
|
||||||
import {
|
import {
|
||||||
render,
|
render,
|
||||||
screen,
|
screen,
|
||||||
|
|||||||
@@ -0,0 +1,253 @@
|
|||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
Dispatch,
|
||||||
|
ReactNode,
|
||||||
|
SetStateAction,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
} from 'react';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
import {
|
||||||
|
Behavior,
|
||||||
|
BinaryQueryObjectFilterClause,
|
||||||
|
css,
|
||||||
|
extractQueryFields,
|
||||||
|
getChartMetadataRegistry,
|
||||||
|
QueryFormData,
|
||||||
|
removeHTMLTags,
|
||||||
|
styled,
|
||||||
|
t,
|
||||||
|
} from '@superset-ui/core';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { Menu } from '@superset-ui/core/components/Menu';
|
||||||
|
import { RootState } from 'src/dashboard/types';
|
||||||
|
import { getSubmenuYOffset } from '../utils';
|
||||||
|
import { MenuItemTooltip } from '../DisabledMenuItemTooltip';
|
||||||
|
import { MenuItemWithTruncation } from '../MenuItemWithTruncation';
|
||||||
|
import { Dataset } from '../types';
|
||||||
|
|
||||||
|
const DRILL_TO_DETAIL = t('Drill to detail');
|
||||||
|
const DRILL_TO_DETAIL_BY = t('Drill to detail by');
|
||||||
|
const DISABLED_REASONS = {
|
||||||
|
DATABASE: t(
|
||||||
|
'Drill to detail is disabled for this database. Change the database settings to enable it.',
|
||||||
|
),
|
||||||
|
NO_AGGREGATIONS: t(
|
||||||
|
'Drill to detail is disabled because this chart does not group data by dimension value.',
|
||||||
|
),
|
||||||
|
NO_FILTERS: t(
|
||||||
|
'Right-click on a dimension value to drill to detail by that value.',
|
||||||
|
),
|
||||||
|
NOT_SUPPORTED: t(
|
||||||
|
'Drill to detail by value is not yet supported for this chart type.',
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const DisabledMenuItem = ({
|
||||||
|
children,
|
||||||
|
menuKey,
|
||||||
|
...rest
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
menuKey: string;
|
||||||
|
}) => (
|
||||||
|
<Menu.Item disabled key={menuKey} {...rest}>
|
||||||
|
<div
|
||||||
|
css={css`
|
||||||
|
white-space: normal;
|
||||||
|
max-width: 160px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</Menu.Item>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Filter = ({
|
||||||
|
children,
|
||||||
|
stripHTML = false,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
stripHTML: boolean;
|
||||||
|
}) => {
|
||||||
|
const content =
|
||||||
|
stripHTML && typeof children === 'string'
|
||||||
|
? removeHTMLTags(children)
|
||||||
|
: children;
|
||||||
|
return <span>{content}</span>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledFilter = styled(Filter)`
|
||||||
|
${({ theme }) => `
|
||||||
|
font-weight: ${theme.fontWeightStrong};
|
||||||
|
color: ${theme.colorPrimary};
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export type DrillDetailMenuItemsProps = {
|
||||||
|
formData: QueryFormData;
|
||||||
|
filters?: BinaryQueryObjectFilterClause[];
|
||||||
|
setFilters: Dispatch<SetStateAction<BinaryQueryObjectFilterClause[]>>;
|
||||||
|
isContextMenu?: boolean;
|
||||||
|
contextMenuY?: number;
|
||||||
|
onSelection?: () => void;
|
||||||
|
onClick?: (event: MouseEvent) => void;
|
||||||
|
submenuIndex?: number;
|
||||||
|
setShowModal: (show: boolean) => void;
|
||||||
|
key?: string;
|
||||||
|
forceSubmenuRender?: boolean;
|
||||||
|
dataset?: Dataset;
|
||||||
|
isLoadingDataset?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DrillDetailMenuItems = ({
|
||||||
|
formData,
|
||||||
|
filters = [],
|
||||||
|
isContextMenu = false,
|
||||||
|
contextMenuY = 0,
|
||||||
|
onSelection = () => null,
|
||||||
|
onClick = () => null,
|
||||||
|
submenuIndex = 0,
|
||||||
|
setFilters,
|
||||||
|
setShowModal,
|
||||||
|
key,
|
||||||
|
...props
|
||||||
|
}: DrillDetailMenuItemsProps) => {
|
||||||
|
const drillToDetailDisabled = useSelector<RootState, boolean | undefined>(
|
||||||
|
({ datasources }) =>
|
||||||
|
datasources[formData.datasource]?.database?.disable_drill_to_detail,
|
||||||
|
);
|
||||||
|
|
||||||
|
const openModal = useCallback(
|
||||||
|
(filters: any, event: any) => {
|
||||||
|
onClick(event);
|
||||||
|
onSelection();
|
||||||
|
setFilters(filters);
|
||||||
|
setShowModal(true);
|
||||||
|
},
|
||||||
|
[onClick, onSelection],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check for Behavior.DRILL_TO_DETAIL to tell if plugin handles the `contextmenu`
|
||||||
|
// event for dimensions. If it doesn't, tell the user that drill to detail by
|
||||||
|
// dimension is not supported. If it does, and the `contextmenu` handler didn't
|
||||||
|
// pass any filters, tell the user that they didn't select a dimension.
|
||||||
|
const handlesDimensionContextMenu = useMemo(
|
||||||
|
() =>
|
||||||
|
getChartMetadataRegistry()
|
||||||
|
.get(formData.viz_type)
|
||||||
|
?.behaviors.find(behavior => behavior === Behavior.DrillToDetail),
|
||||||
|
[formData.viz_type],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check metrics to see if chart's current configuration lacks
|
||||||
|
// aggregations, in which case Drill to Detail should be disabled.
|
||||||
|
const noAggregations = useMemo(() => {
|
||||||
|
const { metrics } = extractQueryFields(formData);
|
||||||
|
return isEmpty(metrics);
|
||||||
|
}, [formData]);
|
||||||
|
|
||||||
|
// Ensure submenu doesn't appear offscreen
|
||||||
|
const submenuYOffset = useMemo(
|
||||||
|
() =>
|
||||||
|
getSubmenuYOffset(
|
||||||
|
contextMenuY,
|
||||||
|
filters.length > 1 ? filters.length + 1 : filters.length,
|
||||||
|
submenuIndex,
|
||||||
|
),
|
||||||
|
[contextMenuY, filters.length, submenuIndex],
|
||||||
|
);
|
||||||
|
|
||||||
|
let drillDisabled;
|
||||||
|
let drillByDisabled;
|
||||||
|
if (drillToDetailDisabled) {
|
||||||
|
drillDisabled = DISABLED_REASONS.DATABASE;
|
||||||
|
drillByDisabled = DISABLED_REASONS.DATABASE;
|
||||||
|
} else if (handlesDimensionContextMenu) {
|
||||||
|
if (noAggregations) {
|
||||||
|
drillDisabled = DISABLED_REASONS.NO_AGGREGATIONS;
|
||||||
|
drillByDisabled = DISABLED_REASONS.NO_AGGREGATIONS;
|
||||||
|
} else if (!filters?.length) {
|
||||||
|
drillByDisabled = DISABLED_REASONS.NO_FILTERS;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
drillByDisabled = DISABLED_REASONS.NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
const drillToDetailMenuItem = drillDisabled ? (
|
||||||
|
<DisabledMenuItem menuKey="drill-to-detail-disabled" {...props}>
|
||||||
|
{DRILL_TO_DETAIL}
|
||||||
|
<MenuItemTooltip title={drillDisabled} />
|
||||||
|
</DisabledMenuItem>
|
||||||
|
) : (
|
||||||
|
<Menu.Item key="drill-to-detail" onClick={openModal.bind(null, [])}>
|
||||||
|
{DRILL_TO_DETAIL}
|
||||||
|
</Menu.Item>
|
||||||
|
);
|
||||||
|
|
||||||
|
const drillToDetailByMenuItem = drillByDisabled ? (
|
||||||
|
<DisabledMenuItem menuKey="drill-to-detail-by-disabled" {...props}>
|
||||||
|
{DRILL_TO_DETAIL_BY}
|
||||||
|
<MenuItemTooltip title={drillByDisabled} />
|
||||||
|
</DisabledMenuItem>
|
||||||
|
) : (
|
||||||
|
<Menu.SubMenu
|
||||||
|
popupOffset={[0, submenuYOffset]}
|
||||||
|
popupClassName="chart-context-submenu"
|
||||||
|
title={DRILL_TO_DETAIL_BY}
|
||||||
|
key={key}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div data-test="drill-to-detail-by-submenu">
|
||||||
|
{filters.map((filter, i) => (
|
||||||
|
<MenuItemWithTruncation
|
||||||
|
tooltipText={`${DRILL_TO_DETAIL_BY} ${filter.formattedVal}`}
|
||||||
|
menuKey={`drill-detail-filter-${i}`}
|
||||||
|
onClick={openModal.bind(null, [filter])}
|
||||||
|
>
|
||||||
|
{`${DRILL_TO_DETAIL_BY} `}
|
||||||
|
<StyledFilter stripHTML>{filter.formattedVal}</StyledFilter>
|
||||||
|
</MenuItemWithTruncation>
|
||||||
|
))}
|
||||||
|
{filters.length > 1 && (
|
||||||
|
<Menu.Item
|
||||||
|
key="drill-detail-filter-all"
|
||||||
|
onClick={openModal.bind(null, filters)}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{`${DRILL_TO_DETAIL_BY} `}
|
||||||
|
<StyledFilter stripHTML={false}>{t('all')}</StyledFilter>
|
||||||
|
</div>
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Menu.SubMenu>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{drillToDetailMenuItem}
|
||||||
|
{isContextMenu && drillToDetailByMenuItem}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DrillDetailMenuItems;
|
||||||
@@ -58,7 +58,7 @@ export default function TableControls({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const removeFilter = useCallback(
|
const removeFilter = useCallback(
|
||||||
colName => {
|
(colName: any) => {
|
||||||
const updatedFilterMap = { ...filterMap };
|
const updatedFilterMap = { ...filterMap };
|
||||||
delete updatedFilterMap[colName];
|
delete updatedFilterMap[colName];
|
||||||
setFilters(Object.values(updatedFilterMap));
|
setFilters(Object.values(updatedFilterMap));
|
||||||
@@ -109,7 +109,7 @@ export default function TableControls({
|
|||||||
>
|
>
|
||||||
{colName}
|
{colName}
|
||||||
</span>
|
</span>
|
||||||
<strong data-test="filter-val">{val}</strong>
|
<strong data-test="filter-val">{String(val)}</strong>
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export const useDrillDetailMenuItems = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const openModal = useCallback(
|
const openModal = useCallback(
|
||||||
(filters, event) => {
|
(filters: any, event: any) => {
|
||||||
onClick(event);
|
onClick(event);
|
||||||
onSelection();
|
onSelection();
|
||||||
setFilters(filters);
|
setFilters(filters);
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export default function Field<V>({
|
|||||||
errorMessage,
|
errorMessage,
|
||||||
}: FieldProps<V>) {
|
}: FieldProps<V>) {
|
||||||
const onControlChange = useCallback(
|
const onControlChange = useCallback(
|
||||||
newValue => {
|
(newValue: any) => {
|
||||||
onChange(fieldKey, newValue);
|
onChange(fieldKey, newValue);
|
||||||
},
|
},
|
||||||
[onChange, fieldKey],
|
[onChange, fieldKey],
|
||||||
|
|||||||
@@ -16,7 +16,13 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { useContext, useEffect, useReducer, createContext, FC } from 'react';
|
import {
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useReducer,
|
||||||
|
createContext,
|
||||||
|
PropsWithChildren,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChartMetadata,
|
ChartMetadata,
|
||||||
@@ -122,7 +128,7 @@ const sharedModules = {
|
|||||||
'@superset-ui/core': () => import('@superset-ui/core'),
|
'@superset-ui/core': () => import('@superset-ui/core'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DynamicPluginProvider: FC = ({ children }) => {
|
export const DynamicPluginProvider = ({ children }: PropsWithChildren) => {
|
||||||
const [pluginState, dispatch] = useReducer(
|
const [pluginState, dispatch] = useReducer(
|
||||||
pluginContextReducer,
|
pluginContextReducer,
|
||||||
dummyPluginContext,
|
dummyPluginContext,
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export const FilterableTable = ({
|
|||||||
const keyword = useRef<string | undefined>(filterText);
|
const keyword = useRef<string | undefined>(filterText);
|
||||||
keyword.current = filterText;
|
keyword.current = filterText;
|
||||||
|
|
||||||
const keywordFilter = useCallback(node => {
|
const keywordFilter = useCallback((node: any) => {
|
||||||
if (keyword.current && node.data) {
|
if (keyword.current && node.data) {
|
||||||
return hasMatch(keyword.current, node.data);
|
return hasMatch(keyword.current, node.data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { useCellContentParser } from './useCellContentParser';
|
import { useCellContentParser } from './useCellContentParser';
|
||||||
|
|
||||||
test('should return NULL for null cell data', () => {
|
test('should return NULL for null cell data', () => {
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export const Header: React.FC<Params> = ({
|
|||||||
const [currentSort, setCurrentSort] = useState<string | null>(null);
|
const [currentSort, setCurrentSort] = useState<string | null>(null);
|
||||||
const [sortIndex, setSortIndex] = useState<number | null>();
|
const [sortIndex, setSortIndex] = useState<number | null>();
|
||||||
const onSort = useCallback(
|
const onSort = useCallback(
|
||||||
event => {
|
(event: any) => {
|
||||||
sortOption.current = (sortOption.current + 1) % SORT_DIRECTION.length;
|
sortOption.current = (sortOption.current + 1) % SORT_DIRECTION.length;
|
||||||
const sort = SORT_DIRECTION[sortOption.current];
|
const sort = SORT_DIRECTION[sortOption.current];
|
||||||
setSort(sort, event.shiftKey);
|
setSort(sort, event.shiftKey);
|
||||||
|
|||||||
@@ -53,32 +53,32 @@ export function GridTable<RecordType extends object>({
|
|||||||
[externalFilter],
|
[externalFilter],
|
||||||
);
|
);
|
||||||
const rowIndexLength = `${data.length}}`.length;
|
const rowIndexLength = `${data.length}}`.length;
|
||||||
const onKeyDown: AgGridReactProps<Record<string, any>>['onCellKeyDown'] =
|
const onKeyDown = useCallback((params: any) => {
|
||||||
useCallback(({ event, column, data, value, api }) => {
|
const { event, column, data, value, api } = params;
|
||||||
if (
|
if (
|
||||||
!document.getSelection?.()?.toString?.() &&
|
!document.getSelection?.()?.toString?.() &&
|
||||||
event &&
|
event &&
|
||||||
event.key === 'c' &&
|
event.key === 'c' &&
|
||||||
(event.ctrlKey || event.metaKey)
|
(event.ctrlKey || event.metaKey)
|
||||||
) {
|
) {
|
||||||
const columns =
|
const columns =
|
||||||
column.getColId() === PIVOT_COL_ID
|
column.getColId() === PIVOT_COL_ID
|
||||||
? api
|
? api
|
||||||
.getAllDisplayedColumns()
|
.getAllDisplayedColumns()
|
||||||
.filter((column: Column) => column.getColId() !== PIVOT_COL_ID)
|
.filter((column: Column) => column.getColId() !== PIVOT_COL_ID)
|
||||||
: [column];
|
: [column];
|
||||||
const record =
|
const record =
|
||||||
column.getColId() === PIVOT_COL_ID
|
column.getColId() === PIVOT_COL_ID
|
||||||
? [
|
? [
|
||||||
columns.map((column: Column) => column.getColId()).join('\t'),
|
columns.map((column: Column) => column.getColId()).join('\t'),
|
||||||
columns
|
columns
|
||||||
.map((column: Column) => data?.[column.getColId()])
|
.map((column: Column) => data?.[column.getColId()])
|
||||||
.join('\t'),
|
.join('\t'),
|
||||||
].join('\n')
|
].join('\n')
|
||||||
: String(value);
|
: String(value);
|
||||||
copyTextToClipboard(() => Promise.resolve(record));
|
copyTextToClipboard(() => Promise.resolve(record));
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
const columnDefs = useMemo(
|
const columnDefs = useMemo(
|
||||||
() =>
|
() =>
|
||||||
[
|
[
|
||||||
@@ -179,7 +179,7 @@ export function GridTable<RecordType extends object>({
|
|||||||
isExternalFilterPresent={isExternalFilterPresent}
|
isExternalFilterPresent={isExternalFilterPresent}
|
||||||
doesExternalFilterPass={externalFilter}
|
doesExternalFilterPass={externalFilter}
|
||||||
components={gridComponents}
|
components={gridComponents}
|
||||||
gridOptions={gridOptions}
|
gridOptions={gridOptions as any}
|
||||||
onCellKeyDown={onKeyDown}
|
onCellKeyDown={onKeyDown}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { act } from 'spec/helpers/testing-library';
|
import { act } from 'spec/helpers/testing-library';
|
||||||
import {
|
import {
|
||||||
useModalValidation,
|
useModalValidation,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook, act } from '@testing-library/react-hooks';
|
import { renderHook, act } from '@testing-library/react';
|
||||||
import {
|
import {
|
||||||
LocalStorageKeys,
|
LocalStorageKeys,
|
||||||
setItem,
|
setItem,
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ const DashboardBuilder = () => {
|
|||||||
}, [dashboardLayout, dispatch]);
|
}, [dashboardLayout, dispatch]);
|
||||||
|
|
||||||
const handleDrop = useCallback(
|
const handleDrop = useCallback(
|
||||||
dropResult => dispatch(handleComponentDrop(dropResult)),
|
(dropResult: any) => dispatch(handleComponentDrop(dropResult)),
|
||||||
[dispatch],
|
[dispatch],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -559,7 +559,7 @@ const DashboardBuilder = () => {
|
|||||||
: theme.sizeUnit * 8;
|
: theme.sizeUnit * 8;
|
||||||
|
|
||||||
const renderChild = useCallback(
|
const renderChild = useCallback(
|
||||||
adjustedWidth => {
|
(adjustedWidth: any) => {
|
||||||
const filterBarWidth = dashboardFiltersOpen
|
const filterBarWidth = dashboardFiltersOpen
|
||||||
? adjustedWidth
|
? adjustedWidth
|
||||||
: CLOSED_FILTER_BAR_WIDTH;
|
: CLOSED_FILTER_BAR_WIDTH;
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ const DashboardContainer: FC<DashboardContainerProps> = ({ topLevelTabs }) => {
|
|||||||
}, [onBeforeUnload]);
|
}, [onBeforeUnload]);
|
||||||
|
|
||||||
const renderTabBar = useCallback(() => <></>, []);
|
const renderTabBar = useCallback(() => <></>, []);
|
||||||
const handleFocus = useCallback(e => {
|
const handleFocus = useCallback((e: any) => {
|
||||||
if (
|
if (
|
||||||
// prevent scrolling when tabbing to the tab pane
|
// prevent scrolling when tabbing to the tab pane
|
||||||
e.target.classList.contains('ant-tabs-tabpane') &&
|
e.target.classList.contains('ant-tabs-tabpane') &&
|
||||||
@@ -293,7 +293,7 @@ const DashboardContainer: FC<DashboardContainerProps> = ({ topLevelTabs }) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const renderParentSizeChildren = useCallback(
|
const renderParentSizeChildren = useCallback(
|
||||||
({ width }) => {
|
({ width }: { width: any }) => {
|
||||||
const tabItems = childIds.map((id, index) => ({
|
const tabItems = childIds.map((id, index) => ({
|
||||||
key: index === 0 ? DASHBOARD_GRID_ID : index.toString(),
|
key: index === 0 ? DASHBOARD_GRID_ID : index.toString(),
|
||||||
label: null,
|
label: null,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { css, styled } from '@apache-superset/core/ui';
|
|||||||
import { Constants } from '@superset-ui/core/components';
|
import { Constants } from '@superset-ui/core/components';
|
||||||
import { RootState } from 'src/dashboard/types';
|
import { RootState } from 'src/dashboard/types';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useDragDropManager } from 'react-dnd';
|
import { useDndMonitor } from '@dnd-kit/core';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
|
|
||||||
@@ -117,34 +117,30 @@ const DashboardWrapper: FC<PropsWithChildren<{}>> = ({ children }) => {
|
|||||||
const editMode = useSelector<RootState, boolean>(
|
const editMode = useSelector<RootState, boolean>(
|
||||||
state => state.dashboardState.editMode,
|
state => state.dashboardState.editMode,
|
||||||
);
|
);
|
||||||
const dragDropManager = useDragDropManager();
|
const [isDragged, setIsDragged] = useState(false);
|
||||||
const [isDragged, setIsDragged] = useState(
|
|
||||||
dragDropManager.getMonitor().isDragging(),
|
const debouncedSetIsDragged = debounce(
|
||||||
|
setIsDragged,
|
||||||
|
Constants.FAST_DEBOUNCE,
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useDndMonitor({
|
||||||
const monitor = dragDropManager.getMonitor();
|
onDragStart() {
|
||||||
const debouncedSetIsDragged = debounce(
|
// set a debounced function to prevent drag source
|
||||||
setIsDragged,
|
// from interfering with the drop zone highlighting
|
||||||
Constants.FAST_DEBOUNCE,
|
debouncedSetIsDragged(true);
|
||||||
);
|
},
|
||||||
const unsub = monitor.subscribeToStateChange(() => {
|
onDragEnd() {
|
||||||
const isDragging = monitor.isDragging();
|
debouncedSetIsDragged.cancel();
|
||||||
if (isDragging) {
|
setIsDragged(false);
|
||||||
// set a debounced function to prevent HTML5 drag source
|
},
|
||||||
// from interfering with the drop zone highlighting
|
});
|
||||||
debouncedSetIsDragged(true);
|
|
||||||
} else {
|
|
||||||
debouncedSetIsDragged.cancel();
|
|
||||||
setIsDragged(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
unsub();
|
|
||||||
debouncedSetIsDragged.cancel();
|
debouncedSetIsDragged.cancel();
|
||||||
};
|
};
|
||||||
}, [dragDropManager]);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledDiv
|
<StyledDiv
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ const PropertiesModal = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDashboardData = useCallback(
|
const handleDashboardData = useCallback(
|
||||||
dashboardData => {
|
(dashboardData: any) => {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
dashboard_title,
|
dashboard_title,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { DragLayer, XYCoord } from 'react-dnd';
|
import { useDragLayer } from 'react-dnd';
|
||||||
import { Slice } from 'src/dashboard/types';
|
import { Slice } from 'src/dashboard/types';
|
||||||
import AddSliceCard from '../AddSliceCard';
|
import AddSliceCard from '../AddSliceCard';
|
||||||
import {
|
import {
|
||||||
@@ -31,10 +31,7 @@ interface DragItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface AddSliceDragPreviewProps {
|
interface AddSliceDragPreviewProps {
|
||||||
dragItem: DragItem | null;
|
|
||||||
slices: Slice[] | null;
|
slices: Slice[] | null;
|
||||||
isDragging: boolean;
|
|
||||||
currentOffset: XYCoord | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const staticCardStyles: React.CSSProperties = {
|
const staticCardStyles: React.CSSProperties = {
|
||||||
@@ -47,11 +44,15 @@ const staticCardStyles: React.CSSProperties = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AddSliceDragPreview: React.FC<AddSliceDragPreviewProps> = ({
|
const AddSliceDragPreview: React.FC<AddSliceDragPreviewProps> = ({
|
||||||
dragItem,
|
|
||||||
slices,
|
slices,
|
||||||
isDragging,
|
|
||||||
currentOffset,
|
|
||||||
}) => {
|
}) => {
|
||||||
|
const { dragItem, isDragging, currentOffset } = useDragLayer(
|
||||||
|
(monitor: any) => ({
|
||||||
|
dragItem: monitor.getItem() as DragItem | null,
|
||||||
|
currentOffset: monitor.getSourceClientOffset(),
|
||||||
|
isDragging: monitor.isDragging(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
if (!isDragging || !currentOffset || !dragItem || !slices) return null;
|
if (!isDragging || !currentOffset || !dragItem || !slices) return null;
|
||||||
|
|
||||||
const slice = slices[dragItem.index];
|
const slice = slices[dragItem.index];
|
||||||
@@ -77,9 +78,4 @@ const AddSliceDragPreview: React.FC<AddSliceDragPreviewProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// This injects these props into the component
|
export default AddSliceDragPreview;
|
||||||
export default DragLayer(monitor => ({
|
|
||||||
dragItem: monitor.getItem() as DragItem | null,
|
|
||||||
currentOffset: monitor.getSourceClientOffset(),
|
|
||||||
isDragging: monitor.isDragging(),
|
|
||||||
}))(AddSliceDragPreview);
|
|
||||||
|
|||||||
@@ -1,248 +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 { getEmptyImage } from 'react-dnd-html5-backend';
|
|
||||||
import { PureComponent } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { TAB_TYPE } from 'src/dashboard/util/componentTypes';
|
|
||||||
import { DragSource, DropTarget } from 'react-dnd';
|
|
||||||
import cx from 'classnames';
|
|
||||||
import { css, styled } from '@apache-superset/core/ui';
|
|
||||||
|
|
||||||
import { componentShape } from '../../util/propShapes';
|
|
||||||
import { dragConfig, dropConfig } from './dragDroppableConfig';
|
|
||||||
import { DROP_FORBIDDEN } from '../../util/getDropPosition';
|
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
children: PropTypes.func,
|
|
||||||
className: PropTypes.string,
|
|
||||||
component: componentShape,
|
|
||||||
parentComponent: componentShape,
|
|
||||||
depth: PropTypes.number.isRequired,
|
|
||||||
disableDragDrop: PropTypes.bool,
|
|
||||||
dropToChild: PropTypes.bool,
|
|
||||||
orientation: PropTypes.oneOf(['row', 'column']),
|
|
||||||
index: PropTypes.number.isRequired,
|
|
||||||
style: PropTypes.object,
|
|
||||||
onDrop: PropTypes.func,
|
|
||||||
onHover: PropTypes.func,
|
|
||||||
onDropIndicatorChange: PropTypes.func,
|
|
||||||
onDragTab: PropTypes.func,
|
|
||||||
editMode: PropTypes.bool,
|
|
||||||
useEmptyDragPreview: PropTypes.bool,
|
|
||||||
|
|
||||||
// from react-dnd
|
|
||||||
isDragging: PropTypes.bool,
|
|
||||||
isDraggingOver: PropTypes.bool,
|
|
||||||
isDraggingOverShallow: PropTypes.bool,
|
|
||||||
dragComponentType: PropTypes.string,
|
|
||||||
dragComponentId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
||||||
droppableRef: PropTypes.func,
|
|
||||||
dragSourceRef: PropTypes.func,
|
|
||||||
dragPreviewRef: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultProps = {
|
|
||||||
className: null,
|
|
||||||
style: null,
|
|
||||||
parentComponent: null,
|
|
||||||
disableDragDrop: false,
|
|
||||||
dropToChild: false,
|
|
||||||
children() {},
|
|
||||||
onDrop() {},
|
|
||||||
onHover() {},
|
|
||||||
onDropIndicatorChange() {},
|
|
||||||
onDragTab() {},
|
|
||||||
orientation: 'row',
|
|
||||||
useEmptyDragPreview: false,
|
|
||||||
isDragging: false,
|
|
||||||
isDraggingOver: false,
|
|
||||||
isDraggingOverShallow: false,
|
|
||||||
droppableRef() {},
|
|
||||||
dragSourceRef() {},
|
|
||||||
dragPreviewRef() {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const DragDroppableStyles = styled.div`
|
|
||||||
${({ theme }) => css`
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&.dragdroppable--dragging {
|
|
||||||
opacity: 0.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.dragdroppable-row {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.dragdroppable-column .resizable-container span div {
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
& {
|
|
||||||
.drop-indicator {
|
|
||||||
display: block;
|
|
||||||
background-color: ${theme.colorPrimary};
|
|
||||||
position: absolute;
|
|
||||||
z-index: 10;
|
|
||||||
opacity: 0.3;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
&.drop-indicator--forbidden {
|
|
||||||
background-color: ${theme.colorErrorBg};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`};
|
|
||||||
`;
|
|
||||||
// export unwrapped component for testing
|
|
||||||
export class UnwrappedDragDroppable extends PureComponent {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
dropIndicator: null, // this gets set/modified by the react-dnd HOCs
|
|
||||||
};
|
|
||||||
this.setRef = this.setRef.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.mounted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.mounted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
|
||||||
const {
|
|
||||||
onDropIndicatorChange,
|
|
||||||
isDraggingOver,
|
|
||||||
component,
|
|
||||||
index,
|
|
||||||
dragComponentId,
|
|
||||||
onDragTab,
|
|
||||||
} = this.props;
|
|
||||||
const { dropIndicator } = this.state;
|
|
||||||
const isTabsType = component.type === TAB_TYPE;
|
|
||||||
const validStateChange =
|
|
||||||
dropIndicator !== prevState.dropIndicator ||
|
|
||||||
isDraggingOver !== prevProps.isDraggingOver ||
|
|
||||||
index !== prevProps.index;
|
|
||||||
|
|
||||||
if (onDropIndicatorChange && isTabsType && validStateChange) {
|
|
||||||
onDropIndicatorChange({ dropIndicator, isDraggingOver, index });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dragComponentId !== prevProps.dragComponentId) {
|
|
||||||
setTimeout(() => {
|
|
||||||
/**
|
|
||||||
* This timeout ensures the dargSourceRef and dragPreviewRef are set
|
|
||||||
* before the component is removed in Tabs.jsx. Otherwise react-dnd
|
|
||||||
* will not render the drag preview.
|
|
||||||
*/
|
|
||||||
onDragTab(dragComponentId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setRef(ref) {
|
|
||||||
this.ref = ref;
|
|
||||||
// this is needed for a custom drag preview
|
|
||||||
if (this.props.useEmptyDragPreview) {
|
|
||||||
this.props.dragPreviewRef(getEmptyImage(), {
|
|
||||||
// IE fallback: specify that we'd rather screenshot the node
|
|
||||||
// when it already knows it's being dragged so we can hide it with CSS.
|
|
||||||
captureDraggingState: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.props.dragPreviewRef(ref);
|
|
||||||
}
|
|
||||||
this.props.droppableRef?.(ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
children,
|
|
||||||
className,
|
|
||||||
orientation,
|
|
||||||
dragSourceRef,
|
|
||||||
disableDragDrop,
|
|
||||||
isDragging,
|
|
||||||
isDraggingOver,
|
|
||||||
style,
|
|
||||||
editMode,
|
|
||||||
component,
|
|
||||||
dragComponentType,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const { dropIndicator } = this.state;
|
|
||||||
const dropIndicatorProps =
|
|
||||||
isDraggingOver && dropIndicator && !disableDragDrop
|
|
||||||
? {
|
|
||||||
className: cx(
|
|
||||||
'drop-indicator',
|
|
||||||
dropIndicator === DROP_FORBIDDEN && 'drop-indicator--forbidden',
|
|
||||||
),
|
|
||||||
}
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const draggingTabOnTab =
|
|
||||||
component.type === TAB_TYPE && dragComponentType === TAB_TYPE;
|
|
||||||
|
|
||||||
const childProps = editMode
|
|
||||||
? {
|
|
||||||
dragSourceRef,
|
|
||||||
dropIndicatorProps,
|
|
||||||
draggingTabOnTab,
|
|
||||||
'data-test': 'dragdroppable-content',
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
'data-test': 'dragdroppable-content',
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DragDroppableStyles
|
|
||||||
style={style}
|
|
||||||
ref={this.setRef}
|
|
||||||
data-test="dragdroppable-object"
|
|
||||||
className={cx(
|
|
||||||
'dragdroppable',
|
|
||||||
editMode && 'dragdroppable--edit-mode',
|
|
||||||
orientation === 'row' && 'dragdroppable-row',
|
|
||||||
orientation === 'column' && 'dragdroppable-column',
|
|
||||||
isDragging && 'dragdroppable--dragging',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children(childProps)}
|
|
||||||
</DragDroppableStyles>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UnwrappedDragDroppable.propTypes = propTypes;
|
|
||||||
UnwrappedDragDroppable.defaultProps = defaultProps;
|
|
||||||
|
|
||||||
export const Draggable = DragSource(...dragConfig)(UnwrappedDragDroppable);
|
|
||||||
export const Droppable = DropTarget(...dropConfig)(UnwrappedDragDroppable);
|
|
||||||
|
|
||||||
// note that the composition order here determines using
|
|
||||||
// component.method() vs decoratedComponentInstance.method() in the drag/drop config
|
|
||||||
export const DragDroppable = DragSource(...dragConfig)(
|
|
||||||
DropTarget(...dropConfig)(UnwrappedDragDroppable),
|
|
||||||
);
|
|
||||||
295
superset-frontend/src/dashboard/components/dnd/DragDroppable.tsx
Normal file
295
superset-frontend/src/dashboard/components/dnd/DragDroppable.tsx
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
CSSProperties,
|
||||||
|
forwardRef,
|
||||||
|
useImperativeHandle,
|
||||||
|
} from 'react';
|
||||||
|
import { TAB_TYPE } from 'src/dashboard/util/componentTypes';
|
||||||
|
import {
|
||||||
|
useDraggable,
|
||||||
|
useDroppable,
|
||||||
|
DndContext,
|
||||||
|
DragOverlay,
|
||||||
|
Active,
|
||||||
|
Over,
|
||||||
|
} from '@dnd-kit/core';
|
||||||
|
import {
|
||||||
|
CSS,
|
||||||
|
} from '@dnd-kit/utilities';
|
||||||
|
import cx from 'classnames';
|
||||||
|
import { css, styled } from '@apache-superset/core/ui';
|
||||||
|
|
||||||
|
import { DROP_FORBIDDEN } from '../../util/getDropPosition';
|
||||||
|
|
||||||
|
type Orientation = 'row' | 'column';
|
||||||
|
|
||||||
|
interface ComponentType {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
parents?: string[];
|
||||||
|
children?: string[];
|
||||||
|
meta?: {
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
headerSize?: string;
|
||||||
|
background?: string;
|
||||||
|
chartId?: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ChildProps {
|
||||||
|
dragSourceRef?: (element: HTMLElement | null) => void;
|
||||||
|
dropIndicatorProps?: {
|
||||||
|
className: string;
|
||||||
|
} | null;
|
||||||
|
draggingTabOnTab?: boolean;
|
||||||
|
'data-test': string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DragDroppableProps {
|
||||||
|
children: (props: ChildProps) => ReactNode;
|
||||||
|
className?: string | null;
|
||||||
|
component: ComponentType;
|
||||||
|
parentComponent?: ComponentType | null;
|
||||||
|
depth: number;
|
||||||
|
disableDragDrop?: boolean;
|
||||||
|
dropToChild?: boolean;
|
||||||
|
orientation?: Orientation;
|
||||||
|
index: number;
|
||||||
|
style?: CSSProperties | null;
|
||||||
|
onDrop?: () => void;
|
||||||
|
onHover?: () => void;
|
||||||
|
onDropIndicatorChange?: (params: {
|
||||||
|
dropIndicator: string | null;
|
||||||
|
isDraggingOver: boolean;
|
||||||
|
index: number;
|
||||||
|
}) => void;
|
||||||
|
onDragTab?: (dragComponentId: string | number | undefined) => void;
|
||||||
|
editMode?: boolean;
|
||||||
|
useEmptyDragPreview?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DragDroppableStyles = styled.div`
|
||||||
|
${({ theme }) => css`
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.dragdroppable--dragging {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dragdroppable-row {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dragdroppable-column .resizable-container span div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-indicator {
|
||||||
|
display: block;
|
||||||
|
background-color: ${theme.colors.primary.base};
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dragdroppable-row .drop-indicator {
|
||||||
|
left: 0;
|
||||||
|
top: -1px;
|
||||||
|
height: 2px;
|
||||||
|
width: 100%;
|
||||||
|
min-width: ${theme.gridUnit * 4}px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dragdroppable-column .drop-indicator {
|
||||||
|
top: 0;
|
||||||
|
left: -1px;
|
||||||
|
width: 2px;
|
||||||
|
height: 100%;
|
||||||
|
min-height: ${theme.gridUnit * 4}px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-indicator--forbidden {
|
||||||
|
background-color: ${theme.colors.warning.light1};
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DragDroppable = forwardRef<HTMLDivElement, DragDroppableProps>(
|
||||||
|
(props, ref) => {
|
||||||
|
const {
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
component,
|
||||||
|
parentComponent,
|
||||||
|
depth,
|
||||||
|
disableDragDrop,
|
||||||
|
dropToChild,
|
||||||
|
orientation,
|
||||||
|
index,
|
||||||
|
style,
|
||||||
|
onDrop,
|
||||||
|
onHover,
|
||||||
|
onDropIndicatorChange,
|
||||||
|
onDragTab,
|
||||||
|
editMode,
|
||||||
|
useEmptyDragPreview,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [dropIndicator, setDropIndicator] = useState<string | null>(null);
|
||||||
|
const elementRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Setup draggable functionality
|
||||||
|
const {
|
||||||
|
attributes: dragAttributes,
|
||||||
|
listeners: dragListeners,
|
||||||
|
setNodeRef: setDragNodeRef,
|
||||||
|
isDragging,
|
||||||
|
transform,
|
||||||
|
} = useDraggable({
|
||||||
|
id: `drag-${component.id}`,
|
||||||
|
disabled: disableDragDrop || !editMode,
|
||||||
|
data: {
|
||||||
|
type: component.type,
|
||||||
|
id: component.id,
|
||||||
|
meta: component.meta,
|
||||||
|
index,
|
||||||
|
parentId: parentComponent?.id,
|
||||||
|
parentType: parentComponent?.type,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup droppable functionality
|
||||||
|
const {
|
||||||
|
setNodeRef: setDropNodeRef,
|
||||||
|
isOver: isDraggingOver,
|
||||||
|
active,
|
||||||
|
over,
|
||||||
|
} = useDroppable({
|
||||||
|
id: `drop-${component.id}`,
|
||||||
|
disabled: disableDragDrop,
|
||||||
|
data: {
|
||||||
|
type: component.type,
|
||||||
|
id: component.id,
|
||||||
|
index,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Combine refs
|
||||||
|
const setRefs = (element: HTMLDivElement | null) => {
|
||||||
|
elementRef.current = element;
|
||||||
|
setDragNodeRef(element);
|
||||||
|
setDropNodeRef(element);
|
||||||
|
if (ref) {
|
||||||
|
if (typeof ref === 'function') {
|
||||||
|
ref(element);
|
||||||
|
} else {
|
||||||
|
ref.current = element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle drop indicator changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (onDropIndicatorChange && component.type === TAB_TYPE) {
|
||||||
|
onDropIndicatorChange({
|
||||||
|
dropIndicator,
|
||||||
|
isDraggingOver,
|
||||||
|
index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [dropIndicator, isDraggingOver, index, onDropIndicatorChange, component.type]);
|
||||||
|
|
||||||
|
// Handle drag tab
|
||||||
|
useEffect(() => {
|
||||||
|
if (onDragTab && active?.data.current?.id) {
|
||||||
|
onDragTab(active.data.current.id);
|
||||||
|
}
|
||||||
|
}, [active?.data.current?.id, onDragTab]);
|
||||||
|
|
||||||
|
const dragComponentType = active?.data.current?.type;
|
||||||
|
const draggingTabOnTab =
|
||||||
|
component.type === TAB_TYPE && dragComponentType === TAB_TYPE;
|
||||||
|
|
||||||
|
const dropIndicatorProps =
|
||||||
|
isDraggingOver && dropIndicator && !disableDragDrop
|
||||||
|
? {
|
||||||
|
className: cx(
|
||||||
|
'drop-indicator',
|
||||||
|
dropIndicator === DROP_FORBIDDEN && 'drop-indicator--forbidden',
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const childProps: ChildProps = editMode
|
||||||
|
? {
|
||||||
|
dragSourceRef: (element) => {
|
||||||
|
if (element && !disableDragDrop) {
|
||||||
|
// Apply drag attributes and listeners to the drag handle
|
||||||
|
Object.entries(dragAttributes).forEach(([key, value]) => {
|
||||||
|
element.setAttribute(key, value as string);
|
||||||
|
});
|
||||||
|
Object.entries(dragListeners || {}).forEach(([event, handler]) => {
|
||||||
|
element.addEventListener(event, handler as EventListener);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dropIndicatorProps,
|
||||||
|
draggingTabOnTab,
|
||||||
|
'data-test': 'dragdroppable-content',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
'data-test': 'dragdroppable-content',
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformStyle = transform
|
||||||
|
? {
|
||||||
|
transform: CSS.Transform.toString(transform),
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DragDroppableStyles
|
||||||
|
style={{ ...style, ...transformStyle }}
|
||||||
|
ref={setRefs}
|
||||||
|
data-test="dragdroppable-object"
|
||||||
|
className={cx(
|
||||||
|
'dragdroppable',
|
||||||
|
editMode && 'dragdroppable--edit-mode',
|
||||||
|
orientation === 'row' && 'dragdroppable-row',
|
||||||
|
orientation === 'column' && 'dragdroppable-column',
|
||||||
|
isDragging && 'dragdroppable--dragging',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children(childProps)}
|
||||||
|
</DragDroppableStyles>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
DragDroppable.displayName = 'DragDroppable';
|
||||||
|
|
||||||
|
// Export compatibility aliases for gradual migration
|
||||||
|
export const Draggable = DragDroppable;
|
||||||
|
export const Droppable = DragDroppable;
|
||||||
@@ -16,16 +16,8 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import {
|
|
||||||
DragSourceMonitor,
|
|
||||||
DropTargetMonitor,
|
|
||||||
ConnectDragSource,
|
|
||||||
ConnectDragPreview,
|
|
||||||
ConnectDropTarget,
|
|
||||||
} from 'react-dnd';
|
|
||||||
import { LayoutItem, ComponentType } from 'src/dashboard/types';
|
import { LayoutItem, ComponentType } from 'src/dashboard/types';
|
||||||
import handleHover from './handleHover';
|
|
||||||
import handleDrop from './handleDrop';
|
|
||||||
|
|
||||||
// note: the 'type' hook is not useful for us as dropping is contingent on other properties
|
// note: the 'type' hook is not useful for us as dropping is contingent on other properties
|
||||||
const TYPE = 'DRAG_DROPPABLE';
|
const TYPE = 'DRAG_DROPPABLE';
|
||||||
@@ -68,121 +60,7 @@ export interface DropResult {
|
|||||||
position?: string;
|
position?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DragStateProps {
|
// For @dnd-kit, we'll handle drag/drop logic directly in the component
|
||||||
dragSourceRef: ConnectDragSource;
|
// These exports maintain backward compatibility for components that import from this file
|
||||||
dragPreviewRef: ConnectDragPreview;
|
export { TYPE };
|
||||||
isDragging: boolean;
|
export default { TYPE };
|
||||||
dragComponentType?: ComponentType;
|
|
||||||
dragComponentId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DropStateProps {
|
|
||||||
droppableRef: ConnectDropTarget;
|
|
||||||
isDraggingOver: boolean;
|
|
||||||
isDraggingOverShallow: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DragDroppableComponent {
|
|
||||||
mounted: boolean;
|
|
||||||
props: DragDroppableProps;
|
|
||||||
setState: (stateUpdate: () => { dropIndicator: string | null }) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const dragConfig: [
|
|
||||||
string,
|
|
||||||
{
|
|
||||||
canDrag: (props: DragDroppableProps) => boolean;
|
|
||||||
beginDrag: (props: DragDroppableProps) => DragItem;
|
|
||||||
},
|
|
||||||
(connect: any, monitor: DragSourceMonitor) => DragStateProps,
|
|
||||||
] = [
|
|
||||||
TYPE,
|
|
||||||
{
|
|
||||||
canDrag(props: DragDroppableProps): boolean {
|
|
||||||
return !props.disableDragDrop;
|
|
||||||
},
|
|
||||||
|
|
||||||
// this defines the dragging item object returned by monitor.getItem()
|
|
||||||
beginDrag(props: DragDroppableProps): DragItem {
|
|
||||||
const { component, index, parentComponent } = props;
|
|
||||||
return {
|
|
||||||
type: component.type,
|
|
||||||
id: component.id,
|
|
||||||
meta: component.meta,
|
|
||||||
index,
|
|
||||||
parentId: parentComponent?.id,
|
|
||||||
parentType: parentComponent?.type,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
function dragStateToProps(
|
|
||||||
connect: any,
|
|
||||||
monitor: DragSourceMonitor,
|
|
||||||
): DragStateProps {
|
|
||||||
return {
|
|
||||||
dragSourceRef: connect.dragSource(),
|
|
||||||
dragPreviewRef: connect.dragPreview(),
|
|
||||||
isDragging: monitor.isDragging(),
|
|
||||||
dragComponentType: monitor.getItem()?.type as ComponentType,
|
|
||||||
dragComponentId: monitor.getItem()?.id as string,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const dropConfig: [
|
|
||||||
string,
|
|
||||||
{
|
|
||||||
canDrop: (props: DragDroppableProps) => boolean;
|
|
||||||
hover: (
|
|
||||||
props: DragDroppableProps,
|
|
||||||
monitor: DropTargetMonitor,
|
|
||||||
component: DragDroppableComponent,
|
|
||||||
) => void;
|
|
||||||
drop: (
|
|
||||||
props: DragDroppableProps,
|
|
||||||
monitor: DropTargetMonitor,
|
|
||||||
component: DragDroppableComponent,
|
|
||||||
) => DropResult | undefined;
|
|
||||||
},
|
|
||||||
(connect: any, monitor: DropTargetMonitor) => DropStateProps,
|
|
||||||
] = [
|
|
||||||
TYPE,
|
|
||||||
{
|
|
||||||
canDrop(props: DragDroppableProps): boolean {
|
|
||||||
return !props.disableDragDrop;
|
|
||||||
},
|
|
||||||
hover(
|
|
||||||
props: DragDroppableProps,
|
|
||||||
monitor: DropTargetMonitor,
|
|
||||||
component: DragDroppableComponent,
|
|
||||||
): void {
|
|
||||||
if (component && component.mounted) {
|
|
||||||
handleHover(props, monitor, component);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// note:
|
|
||||||
// the react-dnd api requires that the drop() method return a result or undefined
|
|
||||||
// monitor.didDrop() cannot be used because it returns true only for the most-nested target
|
|
||||||
drop(
|
|
||||||
props: DragDroppableProps,
|
|
||||||
monitor: DropTargetMonitor,
|
|
||||||
component: DragDroppableComponent,
|
|
||||||
): DropResult | undefined {
|
|
||||||
const dropResult = monitor.getDropResult() as DropResult | null;
|
|
||||||
if ((!dropResult || !dropResult.destination) && component.mounted) {
|
|
||||||
return handleDrop(props, monitor, component);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
function dropStateToProps(
|
|
||||||
connect: any,
|
|
||||||
monitor: DropTargetMonitor,
|
|
||||||
): DropStateProps {
|
|
||||||
return {
|
|
||||||
droppableRef: connect.dropTarget(),
|
|
||||||
isDraggingOver: monitor.isOver(),
|
|
||||||
isDraggingOverShallow: monitor.isOver({ shallow: true }),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -16,13 +16,69 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
import { DropTargetMonitor } from 'react-dnd';
|
||||||
import getDropPosition, {
|
import getDropPosition, {
|
||||||
clearDropCache,
|
clearDropCache,
|
||||||
DROP_FORBIDDEN,
|
DROP_FORBIDDEN,
|
||||||
} from '../../util/getDropPosition';
|
} from '../../util/getDropPosition';
|
||||||
|
|
||||||
export default function handleDrop(props, monitor, Component) {
|
interface DragItem {
|
||||||
// this may happen due to throttling
|
id: string;
|
||||||
|
type: string;
|
||||||
|
meta?: Record<string, any>;
|
||||||
|
parentId: string;
|
||||||
|
parentType: string;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ComponentType {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
children: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DragDroppableProps {
|
||||||
|
parentComponent?: ComponentType;
|
||||||
|
component: ComponentType;
|
||||||
|
index: number;
|
||||||
|
onDrop: (dropResult: DropResult) => void;
|
||||||
|
dropToChild?: boolean | ((item: DragItem) => boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DragDroppableComponent {
|
||||||
|
mounted: boolean;
|
||||||
|
setState: (updater: () => { dropIndicator: string | null }) => void;
|
||||||
|
props: DragDroppableProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DropDestination {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DropSource {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DropResult {
|
||||||
|
source: DropSource;
|
||||||
|
dragging: {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
meta?: Record<string, any>;
|
||||||
|
};
|
||||||
|
destination: DropDestination;
|
||||||
|
position: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function handleDrop(
|
||||||
|
props: DragDroppableProps,
|
||||||
|
monitor: DropTargetMonitor,
|
||||||
|
Component: DragDroppableComponent,
|
||||||
|
): DropResult | undefined {
|
||||||
if (!Component.mounted) return undefined;
|
if (!Component.mounted) return undefined;
|
||||||
|
|
||||||
Component.setState(() => ({ dropIndicator: null }));
|
Component.setState(() => ({ dropIndicator: null }));
|
||||||
@@ -40,9 +96,9 @@ export default function handleDrop(props, monitor, Component) {
|
|||||||
dropToChild,
|
dropToChild,
|
||||||
} = Component.props;
|
} = Component.props;
|
||||||
|
|
||||||
const draggingItem = monitor.getItem();
|
const draggingItem = monitor.getItem() as DragItem;
|
||||||
|
|
||||||
const dropResult = {
|
const dropResult: DropResult = {
|
||||||
source: {
|
source: {
|
||||||
id: draggingItem.parentId,
|
id: draggingItem.parentId,
|
||||||
type: draggingItem.parentType,
|
type: draggingItem.parentType,
|
||||||
@@ -54,12 +110,16 @@ export default function handleDrop(props, monitor, Component) {
|
|||||||
meta: draggingItem.meta,
|
meta: draggingItem.meta,
|
||||||
},
|
},
|
||||||
position: dropPosition,
|
position: dropPosition,
|
||||||
|
destination: {
|
||||||
|
id: '',
|
||||||
|
type: '',
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldAppendToChildren =
|
const shouldAppendToChildren =
|
||||||
typeof dropToChild === 'function' ? dropToChild(draggingItem) : dropToChild;
|
typeof dropToChild === 'function' ? dropToChild(draggingItem) : dropToChild;
|
||||||
|
|
||||||
// simplest case, append as child
|
|
||||||
if (shouldAppendToChildren) {
|
if (shouldAppendToChildren) {
|
||||||
dropResult.destination = {
|
dropResult.destination = {
|
||||||
id: component.id,
|
id: component.id,
|
||||||
@@ -73,8 +133,6 @@ export default function handleDrop(props, monitor, Component) {
|
|||||||
index: componentIndex,
|
index: componentIndex,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// if the item is in the same list with a smaller index, you must account for the
|
|
||||||
// "missing" index upon movement within the list
|
|
||||||
const sameParent =
|
const sameParent =
|
||||||
parentComponent && draggingItem.parentId === parentComponent.id;
|
parentComponent && draggingItem.parentId === parentComponent.id;
|
||||||
const sameParentLowerIndex =
|
const sameParentLowerIndex =
|
||||||
@@ -17,14 +17,33 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
|
import { DropTargetMonitor } from 'react-dnd';
|
||||||
import { DASHBOARD_ROOT_TYPE } from 'src/dashboard/util/componentTypes';
|
import { DASHBOARD_ROOT_TYPE } from 'src/dashboard/util/componentTypes';
|
||||||
import getDropPosition from 'src/dashboard/util/getDropPosition';
|
import getDropPosition from 'src/dashboard/util/getDropPosition';
|
||||||
import handleScroll from './handleScroll';
|
import handleScroll from './handleScroll';
|
||||||
|
|
||||||
const HOVER_THROTTLE_MS = 100;
|
const HOVER_THROTTLE_MS = 100;
|
||||||
|
|
||||||
function handleHover(props, monitor, Component) {
|
interface ComponentType {
|
||||||
// this may happen due to throttling
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DragDroppableProps {
|
||||||
|
component: ComponentType;
|
||||||
|
onHover?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DragDroppableComponent {
|
||||||
|
mounted: boolean;
|
||||||
|
setState: (updater: () => { dropIndicator: string | null }) => void;
|
||||||
|
props: DragDroppableProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleHover(
|
||||||
|
props: DragDroppableProps,
|
||||||
|
monitor: DropTargetMonitor,
|
||||||
|
Component: DragDroppableComponent,
|
||||||
|
): void {
|
||||||
if (!Component.mounted) return;
|
if (!Component.mounted) return;
|
||||||
|
|
||||||
const dropPosition = getDropPosition(monitor, Component);
|
const dropPosition = getDropPosition(monitor, Component);
|
||||||
@@ -40,12 +59,11 @@ function handleHover(props, monitor, Component) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Component?.props?.onHover();
|
Component?.props?.onHover?.();
|
||||||
|
|
||||||
Component.setState(() => ({
|
Component.setState(() => ({
|
||||||
dropIndicator: dropPosition,
|
dropIndicator: dropPosition,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is called very frequently by react-dnd
|
|
||||||
export default throttle(handleHover, HOVER_THROTTLE_MS);
|
export default throttle(handleHover, HOVER_THROTTLE_MS);
|
||||||
@@ -239,7 +239,7 @@ const ChartHolder = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const renderChild = useCallback(
|
const renderChild = useCallback(
|
||||||
({ dragSourceRef }) => (
|
({ dragSourceRef }: { dragSourceRef: any }) => (
|
||||||
<ResizableContainer
|
<ResizableContainer
|
||||||
id={component.id}
|
id={component.id}
|
||||||
adjustableWidth={parentComponent.type === ROW_TYPE}
|
adjustableWidth={parentComponent.type === ROW_TYPE}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ const DynamicComponent: FC<DynamicComponentProps> = ({
|
|||||||
|
|
||||||
const updateMeta = (metaKey: string, nextValue: string | number) => {
|
const updateMeta = (metaKey: string, nextValue: string | number) => {
|
||||||
updateComponents({
|
updateComponents({
|
||||||
[component.id]: {
|
[String(component.id)]: {
|
||||||
...component,
|
...component,
|
||||||
meta: {
|
meta: {
|
||||||
...component.meta,
|
...component.meta,
|
||||||
@@ -126,7 +126,9 @@ const DynamicComponent: FC<DynamicComponentProps> = ({
|
|||||||
<BackgroundStyleDropdown
|
<BackgroundStyleDropdown
|
||||||
id={`${component.id}-background`}
|
id={`${component.id}-background`}
|
||||||
value={component.meta.background}
|
value={component.meta.background}
|
||||||
onChange={value => updateMeta('background', value)}
|
onChange={value =>
|
||||||
|
updateMeta('background', value as string | number)
|
||||||
|
}
|
||||||
/>,
|
/>,
|
||||||
]}
|
]}
|
||||||
editMode={editMode}
|
editMode={editMode}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReactElement } from 'react';
|
import { ReactElement } from 'react';
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { createWrapper, render } from 'spec/helpers/testing-library';
|
import { createWrapper, render } from 'spec/helpers/testing-library';
|
||||||
import { useCrossFiltersScopingModal } from './useCrossFiltersScopingModal';
|
import { useCrossFiltersScopingModal } from './useCrossFiltersScopingModal';
|
||||||
|
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ const FilterBarSettings = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const updateCrossFiltersSetting = useCallback(
|
const updateCrossFiltersSetting = useCallback(
|
||||||
async isEnabled => {
|
async (isEnabled: any) => {
|
||||||
if (!isEnabled) {
|
if (!isEnabled) {
|
||||||
dispatch(clearDataMaskState());
|
dispatch(clearDataMaskState());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ const FilterControls: FC<FilterControlsProps> = ({
|
|||||||
}, [overflowedCrossFilters, overflowedFiltersInScope]);
|
}, [overflowedCrossFilters, overflowedFiltersInScope]);
|
||||||
|
|
||||||
const rendererCrossFilter = useCallback(
|
const rendererCrossFilter = useCallback(
|
||||||
(crossFilter, orientation, last) => (
|
(crossFilter: any, orientation: any, last: any) => (
|
||||||
<CrossFilter
|
<CrossFilter
|
||||||
filter={crossFilter}
|
filter={crossFilter}
|
||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
import React, { useRef, FC } from 'react';
|
||||||
import { styled } from '@apache-superset/core/ui';
|
import { styled } from '@apache-superset/core/ui';
|
||||||
import { useRef, FC } from 'react';
|
|
||||||
import {
|
import {
|
||||||
DragSourceMonitor,
|
DragSourceMonitor,
|
||||||
DropTargetMonitor,
|
DropTargetMonitor,
|
||||||
@@ -57,6 +57,7 @@ interface FilterTabTitleProps {
|
|||||||
index: number;
|
index: number;
|
||||||
filterIds: string[];
|
filterIds: string[];
|
||||||
onRearrange: (dragItemIndex: number, targetIndex: number) => void;
|
onRearrange: (dragItemIndex: number, targetIndex: number) => void;
|
||||||
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DragItem {
|
interface DragItem {
|
||||||
@@ -73,6 +74,7 @@ export const DraggableFilter: FC<FilterTabTitleProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const [{ isDragging }, drag] = useDrag({
|
const [{ isDragging }, drag] = useDrag({
|
||||||
|
type: FILTER_TYPE,
|
||||||
item: { filterIds, type: FILTER_TYPE, index },
|
item: { filterIds, type: FILTER_TYPE, index },
|
||||||
collect: (monitor: DragSourceMonitor) => ({
|
collect: (monitor: DragSourceMonitor) => ({
|
||||||
isDragging: monitor.isDragging(),
|
isDragging: monitor.isDragging(),
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user