mirror of
https://github.com/apache/superset.git
synced 2026-05-04 07:24:18 +00:00
Compare commits
19 Commits
fix/postgr
...
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 }],
|
||||
// only used in packages/superset-ui-core/src/chart/components/reactify.tsx
|
||||
['babel-plugin-typescript-to-proptypes', { loose: true }],
|
||||
'react-hot-loader/babel',
|
||||
[
|
||||
'@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",
|
||||
"ag-grid-community": "34.2.0",
|
||||
"ag-grid-react": "34.2.0",
|
||||
"antd": "^5.26.0",
|
||||
"antd": "^5.26.3",
|
||||
"chrono-node": "^2.7.8",
|
||||
"classnames": "^2.2.5",
|
||||
"content-disposition": "^0.5.4",
|
||||
@@ -179,15 +179,17 @@
|
||||
"ol": "^7.5.2",
|
||||
"polished": "^4.3.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"query-string": "^7.1.3",
|
||||
"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-diff-viewer-continued": "^3.4.0",
|
||||
"react-dnd": "^11.1.3",
|
||||
"react-dnd": "^16.0.1",
|
||||
"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-hot-loader": "^4.13.1",
|
||||
"react-intersection-observer": "^9.16.0",
|
||||
"react-json-tree": "^0.20.0",
|
||||
"react-lines-ellipsis": "^0.16.1",
|
||||
@@ -237,10 +239,8 @@
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@babel/runtime-corejs3": "^7.28.2",
|
||||
"@babel/types": "^7.26.9",
|
||||
"@cypress/react": "^8.0.2",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/jest": "^11.13.0",
|
||||
"@hot-loader/react-dom": "^17.0.2",
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@mihkeleidast/storybook-addon-source": "^1.0.1",
|
||||
"@playwright/test": "^1.56.0",
|
||||
@@ -256,12 +256,11 @@
|
||||
"@storybook/react-webpack5": "8.1.11",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@swc/core": "^1.14.0",
|
||||
"@swc/plugin-emotion": "^12.0.0",
|
||||
"@swc/plugin-transform-imports": "^10.0.0",
|
||||
"@swc/plugin-emotion": "^13.1.0",
|
||||
"@swc/plugin-transform-imports": "^11.1.0",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"@types/content-disposition": "^0.5.9",
|
||||
"@types/dom-to-image": "^2.6.7",
|
||||
@@ -271,8 +270,9 @@
|
||||
"@types/math-expression-evaluator": "^1.3.3",
|
||||
"@types/mousetrap": "^1.6.15",
|
||||
"@types/node": "^24.8.1",
|
||||
"@types/react": "^17.0.83",
|
||||
"@types/react-dom": "^17.0.26",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"@types/react-gravatar": "^2.6.14",
|
||||
"@types/react-json-tree": "^0.13.0",
|
||||
"@types/react-loadable": "^5.5.11",
|
||||
"@types/react-redux": "^7.1.10",
|
||||
@@ -378,6 +378,10 @@
|
||||
"npm": "^10.8.1"
|
||||
},
|
||||
"overrides": {
|
||||
"react-sortable-hoc": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"core-js": "^3.38.1",
|
||||
"d3-color": "^3.1.0",
|
||||
"puppeteer": "^22.4.1",
|
||||
|
||||
@@ -23,8 +23,7 @@
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
"@testing-library/jest-dom": "*",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/react-hooks": "*",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "*",
|
||||
"@types/react": "*",
|
||||
"@types/react-loadable": "*",
|
||||
@@ -32,12 +31,12 @@
|
||||
"@types/tinycolor2": "*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.2.0",
|
||||
"@emotion/cache": "^11.4.0",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"nanoid": "^5.0.9",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loadable": "^5.5.0",
|
||||
"tinycolor2": "*",
|
||||
"@fontsource/fira-code": "^5.2.6",
|
||||
|
||||
@@ -35,15 +35,14 @@
|
||||
"@superset-ui/core": "*",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
"@testing-library/jest-dom": "*",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/react-hooks": "*",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "*",
|
||||
"ace-builds": "^1.4.14",
|
||||
"brace": "^0.11.1",
|
||||
"memoize-one": "^5.1.1",
|
||||
"react": "^17.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-dom": "^17.0.2"
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -97,16 +97,15 @@
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
"@testing-library/jest-dom": "*",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/react-hooks": "*",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "*",
|
||||
"@types/react": "*",
|
||||
"@types/react-loadable": "*",
|
||||
"@types/react-window": "^1.8.8",
|
||||
"@types/tinycolor2": "*",
|
||||
"nanoid": "^5.0.9",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loadable": "^5.5.0",
|
||||
"tinycolor2": "*"
|
||||
},
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useJsonValidation } from './useJsonValidation';
|
||||
|
||||
describe('useJsonValidation', () => {
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
import {
|
||||
useEffect,
|
||||
useState,
|
||||
RefObject,
|
||||
forwardRef,
|
||||
ComponentType,
|
||||
ForwardRefExoticComponent,
|
||||
PropsWithoutRef,
|
||||
RefAttributes,
|
||||
ForwardedRef,
|
||||
} from 'react';
|
||||
|
||||
import { Loading } from '../Loading';
|
||||
@@ -54,7 +54,7 @@ function DefaultPlaceholder({
|
||||
* first (if provided) and re-render once import is complete.
|
||||
*/
|
||||
export function AsyncEsmComponent<
|
||||
P = PlaceholderProps,
|
||||
P = Record<string, unknown>,
|
||||
M = ComponentType<P> | { default: ComponentType<P> },
|
||||
>(
|
||||
/**
|
||||
@@ -98,8 +98,8 @@ export function AsyncEsmComponent<
|
||||
};
|
||||
|
||||
const AsyncComponent: AsyncComponent = forwardRef(function AsyncComponent(
|
||||
props: FullProps,
|
||||
ref: RefObject<ComponentType<FullProps>>,
|
||||
props: PropsWithoutRef<FullProps>,
|
||||
ref: ForwardedRef<ComponentType<FullProps>>,
|
||||
) {
|
||||
const [loaded, setLoaded] = useState(component !== undefined);
|
||||
useEffect(() => {
|
||||
|
||||
@@ -24,7 +24,6 @@ import type {
|
||||
ButtonVariantType,
|
||||
ButtonColorType,
|
||||
} from 'antd/es/button';
|
||||
import { IconType } from '@superset-ui/core/components/Icons/types';
|
||||
import type { TooltipPlacement } from '../Tooltip/types';
|
||||
|
||||
export type { AntdButtonProps, ButtonType, ButtonVariantType, ButtonColorType };
|
||||
@@ -49,5 +48,4 @@ export type ButtonProps = Omit<AntdButtonProps, 'css'> & {
|
||||
buttonStyle?: ButtonStyle;
|
||||
cta?: boolean;
|
||||
showMarginRight?: boolean;
|
||||
icon?: IconType;
|
||||
};
|
||||
|
||||
@@ -65,7 +65,7 @@ export const Component = (props: DropdownContainerProps) => {
|
||||
const [overflowingState, setOverflowingState] = useState<OverflowingState>();
|
||||
const containerRef = useRef<DropdownRef>(null);
|
||||
const onOverflowingStateChange = useCallback(
|
||||
value => {
|
||||
(value: OverflowingState) => {
|
||||
if (!isEqual(overflowingState, value)) {
|
||||
setItems(generateItems(value));
|
||||
setOverflowingState(value);
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
* under the License.
|
||||
*/
|
||||
import type { CSSProperties, ReactElement, RefObject, ReactNode } from 'react';
|
||||
import { IconType } from '../Icons';
|
||||
|
||||
/**
|
||||
* Container item.
|
||||
@@ -70,7 +69,7 @@ export interface DropdownContainerProps {
|
||||
/**
|
||||
* Icon of the dropdown trigger.
|
||||
*/
|
||||
dropdownTriggerIcon?: IconType;
|
||||
dropdownTriggerIcon?: ReactNode;
|
||||
/**
|
||||
* Text of the dropdown trigger.
|
||||
*/
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
* under the License.
|
||||
*/
|
||||
import type { ReactNode, SyntheticEvent } from 'react';
|
||||
import type { IconType } from '@superset-ui/core/components';
|
||||
|
||||
export type EmptyStateSize = 'small' | 'medium' | 'large';
|
||||
|
||||
@@ -26,7 +25,7 @@ export type EmptyStateProps = {
|
||||
description?: ReactNode;
|
||||
image?: ReactNode | string;
|
||||
buttonText?: ReactNode;
|
||||
buttonIcon?: IconType;
|
||||
buttonIcon?: ReactNode;
|
||||
buttonAction?: (event: SyntheticEvent) => void;
|
||||
size?: EmptyStateSize;
|
||||
children?: ReactNode;
|
||||
|
||||
@@ -16,17 +16,4 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { Form as AntdForm } 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,
|
||||
});
|
||||
export { Form } from 'antd';
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export type { FormProps, FormInstance, FormItemProps } from 'antd/es/form';
|
||||
export type { FormProps, FormInstance, FormItemProps } from 'antd';
|
||||
|
||||
export interface LabeledErrorBoundInputProps {
|
||||
label?: string;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { FC } from 'react';
|
||||
import { FC, PropsWithChildren } from 'react';
|
||||
import { styled, useTheme, css } from '@apache-superset/core/ui';
|
||||
import { Skeleton } from '../Skeleton';
|
||||
import { Card } from '../Card';
|
||||
@@ -134,7 +134,7 @@ const ThinSkeleton = styled(Skeleton)`
|
||||
|
||||
const paragraphConfig = { rows: 1, width: 150 };
|
||||
|
||||
const AnchorLink: FC<LinkProps> = ({ to, children }) => (
|
||||
const AnchorLink: FC<PropsWithChildren<LinkProps>> = ({ to, children }) => (
|
||||
<a href={to}>{children}</a>
|
||||
);
|
||||
|
||||
|
||||
@@ -16,7 +16,12 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import type { ReactNode, ComponentType, ReactElement } from 'react';
|
||||
import type {
|
||||
ReactNode,
|
||||
ComponentType,
|
||||
ReactElement,
|
||||
PropsWithChildren,
|
||||
} from 'react';
|
||||
import type { BackgroundPosition } from './ImageLoader';
|
||||
|
||||
export interface LinkProps {
|
||||
@@ -27,7 +32,7 @@ export interface ListViewCardProps {
|
||||
title?: ReactNode;
|
||||
subtitle?: ReactNode;
|
||||
url?: string;
|
||||
linkComponent?: ComponentType<LinkProps>;
|
||||
linkComponent?: ComponentType<PropsWithChildren<LinkProps>>;
|
||||
imgURL?: string | null;
|
||||
imgFallbackURL?: string;
|
||||
imgPosition?: BackgroundPosition;
|
||||
|
||||
@@ -194,7 +194,7 @@ const MetadataBar = ({ items, tooltipPlacement = 'top' }: MetadataBarProps) => {
|
||||
}
|
||||
|
||||
const onResize = useCallback(
|
||||
width => {
|
||||
(width: number) => {
|
||||
// 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.
|
||||
const breakpoint =
|
||||
|
||||
@@ -54,7 +54,7 @@ export function FormModal({
|
||||
}, [onSave, resetForm]);
|
||||
|
||||
const handleFormSubmit = useCallback(
|
||||
async values => {
|
||||
async (values: Object) => {
|
||||
try {
|
||||
setIsSaving(true);
|
||||
await formSubmitHandler(values);
|
||||
@@ -113,7 +113,7 @@ export function FormModal({
|
||||
onValuesChange={onFormChange}
|
||||
onFieldsChange={onFormChange}
|
||||
>
|
||||
{typeof children === 'function' ? children(form) : children}
|
||||
{children}
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
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 TableCollection from '.';
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
UseResizeColumnsColumnOptions,
|
||||
UseResizeColumnsColumnProps,
|
||||
} from 'react-table';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
|
||||
import { SortOrder } from '../Table';
|
||||
|
||||
@@ -87,11 +88,11 @@ export function mapColumns<T extends object>(
|
||||
columns: EnhancedColumnInstance<T>[],
|
||||
headerGroups: EnhancedHeaderGroup<T>[],
|
||||
columnsForWrapText?: string[],
|
||||
) {
|
||||
): ColumnsType<object> {
|
||||
return columns.map(column => {
|
||||
const { isSorted, isSortedDesc } = getSortingInfo(headerGroups, column.id);
|
||||
return {
|
||||
title: column.Header,
|
||||
title: column.Header as ReactNode,
|
||||
dataIndex: column.id?.includes('.') ? column.id.split('.') : column.id,
|
||||
hidden: column.hidden,
|
||||
key: column.id,
|
||||
@@ -121,7 +122,7 @@ export function mapColumns<T extends object>(
|
||||
column,
|
||||
});
|
||||
}
|
||||
return val;
|
||||
return String(val);
|
||||
},
|
||||
className: column.className,
|
||||
};
|
||||
|
||||
@@ -96,8 +96,8 @@ const StyledPlus = styled.span`
|
||||
|
||||
export default function TruncatedList<ListItemType>({
|
||||
items,
|
||||
renderVisibleItem = item => item,
|
||||
renderTooltipItem = item => item,
|
||||
renderVisibleItem = item => item as ReactNode,
|
||||
renderTooltipItem = item => item as ReactNode,
|
||||
getKey = item => item as unknown as Key,
|
||||
maxLinks = 20,
|
||||
}: TruncatedListProps<ListItemType>) {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useChangeEffect } from './useChangeEffect';
|
||||
|
||||
test('call callback the first time with undefined and value', () => {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useComponentDidMount } from './useComponentDidMount';
|
||||
|
||||
test('the effect should only be executed on the first render', () => {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useComponentDidUpdate } from './useComponentDidUpdate';
|
||||
|
||||
test('the effect should not be executed on the first render', () => {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useElementOnScreen } from './useElementOnScreen';
|
||||
|
||||
const observeMock = jest.fn();
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { usePrevious } from './usePrevious';
|
||||
|
||||
test('get undefined on the first render when initialValue is not defined', () => {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import useCSSTextTruncation from './useCSSTextTruncation';
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { RefObject } from 'react';
|
||||
import useChildElementTruncation from './useChildElementTruncation';
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
MouseEventHandler,
|
||||
ReactElement,
|
||||
ComponentType,
|
||||
PropsWithChildren,
|
||||
} from 'react';
|
||||
import type { Editor } from 'brace';
|
||||
import type { QueryData } from '../chart/types/QueryResponse';
|
||||
@@ -249,7 +250,7 @@ export type Extensions = Partial<{
|
||||
'navbar.right-menu.item.icon': ComponentType<RightMenuItemIconProps>;
|
||||
'navbar.right': ComponentType;
|
||||
'report-modal.dropdown.item.icon': ComponentType;
|
||||
'root.context.provider': ComponentType;
|
||||
'root.context.provider': ComponentType<PropsWithChildren>;
|
||||
'welcome.message': ComponentType;
|
||||
'welcome.banner': ComponentType;
|
||||
'welcome.main.replacement': ComponentType;
|
||||
|
||||
@@ -46,8 +46,8 @@
|
||||
"gh-pages": "^6.3.0",
|
||||
"jquery": "^3.7.1",
|
||||
"memoize-one": "^5.2.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loadable": "^5.5.0",
|
||||
"react-resizable": "^3.0.5"
|
||||
},
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0",
|
||||
"@apache-superset/core": "*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"peerDependencies": {
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0",
|
||||
"@apache-superset/core": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
"peerDependencies": {
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0",
|
||||
"@apache-superset/core": "*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"mapbox-gl": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
"peerDependencies": {
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0",
|
||||
"@apache-superset/core": "*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"peerDependencies": {
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0",
|
||||
"@apache-superset/core": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"@testing-library/jest-dom": "*",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0",
|
||||
"@apache-superset/core": "*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"peerDependencies": {
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0",
|
||||
"@apache-superset/core": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +64,8 @@
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"mapbox-gl": "*",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-map-gl": "^6.1.19"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
||||
@@ -196,5 +196,5 @@ export const DeckGLContainerStyledWrapper = styled(DeckGLContainer)`
|
||||
`;
|
||||
|
||||
export type DeckGLContainerHandle = typeof DeckGLContainer & {
|
||||
setTooltip: (tooltip: ReactNode) => void;
|
||||
setTooltip: (tooltip: TooltipProps['tooltip']) => void;
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { PickingInfo } from '@deck.gl/core';
|
||||
import { JsonObject, QueryFormData } from '@superset-ui/core';
|
||||
import {
|
||||
@@ -98,9 +99,9 @@ describe('getAggFunc', () => {
|
||||
|
||||
describe('commonLayerProps', () => {
|
||||
const mockSetTooltip = jest.fn();
|
||||
const mockSetTooltipContent = jest.fn(
|
||||
() => (o: JsonObject) => `Tooltip for ${o}`,
|
||||
);
|
||||
const mockSetTooltipContent = jest
|
||||
.fn()
|
||||
.mockReturnValue((o: JsonObject) => `Tooltip for ${o}` as React.ReactNode);
|
||||
const mockOnSelect = jest.fn();
|
||||
|
||||
it('returns correct props when js_tooltip is provided', () => {
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"peerDependencies": {
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0",
|
||||
"@apache-superset/core": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,14 +42,13 @@
|
||||
"@superset-ui/core": "*",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
"@testing-library/jest-dom": "*",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/react-hooks": "*",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "*",
|
||||
"@types/classnames": "*",
|
||||
"@types/react": "*",
|
||||
"match-sorter": "^6.3.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -37,10 +37,8 @@ import {
|
||||
type ColDef,
|
||||
type ColumnState,
|
||||
ModuleRegistry,
|
||||
GridReadyEvent,
|
||||
GridState,
|
||||
CellClickedEvent,
|
||||
IMenuActionParams,
|
||||
GridReadyEvent,
|
||||
} from '@superset-ui/core/components/ThemedAgGridReact';
|
||||
import {
|
||||
AgGridChartState,
|
||||
@@ -74,7 +72,7 @@ export interface AgGridTableProps {
|
||||
gridHeight?: number;
|
||||
updateInterval?: number;
|
||||
data?: any[];
|
||||
onGridReady?: (params: GridReadyEvent) => void;
|
||||
onGridReady?: (params: any) => void;
|
||||
colDefsFromProps: any[];
|
||||
includeSearch: boolean;
|
||||
allowRearrangeColumns: boolean;
|
||||
@@ -93,7 +91,7 @@ export interface AgGridTableProps {
|
||||
percentMetrics: string[];
|
||||
serverPageLength: number;
|
||||
hasServerPageLengthChanged: boolean;
|
||||
handleCrossFilter: (event: CellClickedEvent | IMenuActionParams) => void;
|
||||
handleCrossFilter: (event: any) => void;
|
||||
isActiveFilterValue: (key: string, val: DataRecordValue) => boolean;
|
||||
renderTimeComparisonDropdown: () => JSX.Element | null;
|
||||
cleanedTotals: DataRecord;
|
||||
@@ -279,7 +277,7 @@ const AgGridDataTable: FunctionComponent<AgGridTableProps> = memo(
|
||||
};
|
||||
|
||||
const handleColumnHeaderClick = useCallback(
|
||||
params => {
|
||||
(params: any) => {
|
||||
const colId = params?.column?.colId;
|
||||
const sortDir = params?.column?.sort;
|
||||
handleColSort(colId, sortDir);
|
||||
@@ -423,8 +421,8 @@ const AgGridDataTable: FunctionComponent<AgGridTableProps> = memo(
|
||||
rowData={rowData}
|
||||
headerHeight={36}
|
||||
rowHeight={30}
|
||||
columnDefs={colDefsFromProps}
|
||||
defaultColDef={defaultColDef}
|
||||
columnDefs={colDefsFromProps as any}
|
||||
defaultColDef={defaultColDef as any}
|
||||
onColumnGroupOpened={params => params.api.sizeColumnsToFit()}
|
||||
rowSelection="multiple"
|
||||
animateRows
|
||||
|
||||
@@ -172,7 +172,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
||||
);
|
||||
|
||||
const timestampFormatter = useCallback(
|
||||
value => getTimeFormatterForGranularity(timeGrain)(value),
|
||||
(value: any) => getTimeFormatterForGranularity(timeGrain)(value),
|
||||
[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",
|
||||
"ol": "^7.1.0",
|
||||
"polished": "*",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import Layer from 'ol/layer/Layer';
|
||||
import { FrameState } from 'ol/Map';
|
||||
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 { ChartConfig, ChartLayerOptions, ChartSizeValues } from '../types';
|
||||
import { createChartComponent } from '../util/chartUtil';
|
||||
@@ -33,6 +33,8 @@ import Loader from '../images/loading.gif';
|
||||
export class ChartLayer extends Layer {
|
||||
charts: any[] = [];
|
||||
|
||||
chartRoots: Map<HTMLElement, any> = new Map();
|
||||
|
||||
chartConfigs: ChartConfig = {
|
||||
type: 'FeatureCollection',
|
||||
features: [],
|
||||
@@ -166,7 +168,11 @@ export class ChartLayer extends Layer {
|
||||
*/
|
||||
removeAllChartElements() {
|
||||
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();
|
||||
});
|
||||
this.charts = [];
|
||||
@@ -191,7 +197,9 @@ export class ChartLayer extends Layer {
|
||||
this.theme,
|
||||
this.locale,
|
||||
);
|
||||
ReactDOM.render(chartComponent, container);
|
||||
const root = createRoot(container);
|
||||
this.chartRoots.set(container, root);
|
||||
root.render(chartComponent);
|
||||
|
||||
return {
|
||||
htmlElement: container,
|
||||
@@ -227,7 +235,10 @@ export class ChartLayer extends Layer {
|
||||
this.theme,
|
||||
this.locale,
|
||||
);
|
||||
ReactDOM.render(chartComponent, chart.htmlElement);
|
||||
const root = this.chartRoots.get(chart.htmlElement);
|
||||
if (root) {
|
||||
root.render(chartComponent);
|
||||
}
|
||||
|
||||
return {
|
||||
...chart,
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"@superset-ui/core": "*",
|
||||
"echarts": "*",
|
||||
"memoize-one": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -57,7 +57,7 @@ export default function EchartsMixedTimeseries({
|
||||
);
|
||||
|
||||
const getCrossFilterDataMask = useCallback(
|
||||
(seriesName, seriesIndex) => {
|
||||
(seriesName: any, seriesIndex: any) => {
|
||||
const selected: string[] = Object.values(selectedValues || {});
|
||||
let values: string[];
|
||||
if (selected.includes(seriesName)) {
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function EchartsTreemap({
|
||||
coltypeMapping,
|
||||
}: TreemapTransformedProps) {
|
||||
const getCrossFilterDataMask = useCallback(
|
||||
(data, treePathInfo) => {
|
||||
(data: any, treePathInfo: any) => {
|
||||
if (data?.children) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -96,7 +96,7 @@ export default function EchartsTreemap({
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(data, treePathInfo) => {
|
||||
(data: any, treePathInfo: any) => {
|
||||
if (!emitCrossFilters || groupby.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"currencyformatter.js": "^1.0.5",
|
||||
"handlebars-group-by": "^1.0.1",
|
||||
"just-handlebars-helpers": "^1.0.19"
|
||||
},
|
||||
@@ -35,12 +36,12 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"ace-builds": "^1.4.14",
|
||||
"dayjs": "^1.11.13",
|
||||
"handlebars": "^4.7.8",
|
||||
"lodash": "^4.17.11",
|
||||
"dayjs": "^1.11.13",
|
||||
"react": "^17.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-dom": "^17.0.2"
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^30.0.0",
|
||||
|
||||
@@ -70,7 +70,7 @@ ${helperDescriptions
|
||||
<div>
|
||||
<ControlHeader>
|
||||
<div>
|
||||
{props.label}
|
||||
{props.label as any}
|
||||
<InfoTooltip
|
||||
iconStyle={{ marginLeft: theme.sizeUnit }}
|
||||
tooltip={<SafeMarkdown source={helpersTooltipContent} />}
|
||||
|
||||
@@ -47,7 +47,7 @@ const StyleControl = (props: CustomControlConfig<StyleCustomControlProps>) => {
|
||||
<div>
|
||||
<ControlHeader>
|
||||
<div>
|
||||
{props.label}
|
||||
{props.label as any}
|
||||
<InfoTooltip
|
||||
iconStyle={{ marginLeft: theme.sizeUnit }}
|
||||
tooltip={t('You need to configure HTML sanitization to use CSS')}
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
"@superset-ui/core": "*",
|
||||
"lodash": "^4.17.11",
|
||||
"prop-types": "*",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.28.4",
|
||||
|
||||
@@ -42,14 +42,13 @@
|
||||
"@superset-ui/core": "*",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
"@testing-library/jest-dom": "*",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/react-hooks": "*",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "*",
|
||||
"@types/classnames": "*",
|
||||
"@types/react": "*",
|
||||
"match-sorter": "^6.3.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -347,7 +347,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
||||
);
|
||||
|
||||
const timestampFormatter = useCallback(
|
||||
value => getTimeFormatterForGranularity(timeGrain)(value),
|
||||
(value: any) => getTimeFormatterForGranularity(timeGrain)(value),
|
||||
[timeGrain],
|
||||
);
|
||||
const [tableSize, setTableSize] = useState<TableSize>({
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@types/lodash": "*",
|
||||
"@types/react": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/d3-cloud": "^1.2.9"
|
||||
|
||||
@@ -35,8 +35,7 @@ import { SupersetThemeProvider } from 'src/theme/ThemeProvider';
|
||||
import { ThemeController } from 'src/theme/ThemeController';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { DndContext, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
|
||||
import reducerIndex from 'spec/helpers/reducerIndex';
|
||||
import { QueryParamProvider } from 'use-query-params';
|
||||
import { configureStore, Store } from '@reduxjs/toolkit';
|
||||
@@ -99,7 +98,17 @@ export function createWrapper(options?: Options) {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
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 {
|
||||
createWrapper,
|
||||
@@ -119,7 +119,7 @@ test('skips fetching validation if validator is undefined', () => {
|
||||
});
|
||||
|
||||
test('returns validation if validator is configured', async () => {
|
||||
const { result, waitFor } = initialize(true);
|
||||
const { result } = initialize(true);
|
||||
await waitFor(() =>
|
||||
expect(fetchMock.calls(queryValidationApiRoute)).toHaveLength(1),
|
||||
);
|
||||
@@ -142,7 +142,7 @@ test('returns server error description', async () => {
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
const { result, waitFor } = initialize(true);
|
||||
const { result } = initialize(true);
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(result.current.data).toEqual([
|
||||
@@ -166,7 +166,7 @@ test('returns session expire description when CSRF token expired', async () => {
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
const { result, waitFor } = initialize(true);
|
||||
const { result } = initialize(true);
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(result.current.data).toEqual([
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
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 {
|
||||
createWrapper,
|
||||
@@ -104,7 +104,7 @@ test('returns keywords including fetched function_names data', async () => {
|
||||
const dbFunctionNamesApiRoute = `glob:*/api/v1/database/${expectDbId}/function_names/`;
|
||||
fetchMock.get(dbFunctionNamesApiRoute, fakeFunctionNamesApiResult);
|
||||
|
||||
const { result, waitFor } = renderHook(
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useKeywords({
|
||||
queryEditorId: 'testqueryid',
|
||||
@@ -240,7 +240,7 @@ test('returns column keywords among selected tables', async () => {
|
||||
);
|
||||
});
|
||||
|
||||
const { result, waitFor } = renderHook(
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useKeywords({
|
||||
queryEditorId: expectQueryEditorId,
|
||||
@@ -315,7 +315,7 @@ test('returns long keywords with docText', async () => {
|
||||
),
|
||||
);
|
||||
});
|
||||
const { result, waitFor } = renderHook(
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useKeywords({
|
||||
queryEditorId: 'testqueryid',
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { FC } from 'react';
|
||||
import { FC, ReactNode } from 'react';
|
||||
import { t } from '@superset-ui/core';
|
||||
import { styled, css } from '@apache-superset/core/ui';
|
||||
import { ModalTrigger } from '@superset-ui/core/components';
|
||||
@@ -92,7 +92,7 @@ const ShortcutCode = styled.code`
|
||||
padding: ${({ theme }) => `${theme.sizeUnit}px ${theme.sizeUnit * 2}px`};
|
||||
`;
|
||||
|
||||
const KeyboardShortcutButton: FC<{}> = ({ children }) => (
|
||||
const KeyboardShortcutButton: FC<{ children: ReactNode }> = ({ children }) => (
|
||||
<ModalTrigger
|
||||
modalTitle={t('Keyboard shortcuts')}
|
||||
modalBody={
|
||||
|
||||
@@ -48,14 +48,25 @@ import { StaticPosition, StyledTooltip } from './styles';
|
||||
interface QueryTableQuery
|
||||
extends Omit<
|
||||
QueryResponse,
|
||||
'state' | 'sql' | 'progress' | 'results' | 'duration' | 'started'
|
||||
| 'state'
|
||||
| 'sql'
|
||||
| 'progress'
|
||||
| 'results'
|
||||
| 'duration'
|
||||
| 'started'
|
||||
| 'user'
|
||||
| 'db'
|
||||
| 'querylink'
|
||||
> {
|
||||
state?: Record<string, any>;
|
||||
sql?: Record<string, any>;
|
||||
sql?: ReactNode;
|
||||
progress?: Record<string, any>;
|
||||
results?: Record<string, any>;
|
||||
duration?: ReactNode;
|
||||
started?: ReactNode;
|
||||
user?: ReactNode;
|
||||
db?: ReactNode;
|
||||
querylink?: ReactNode;
|
||||
}
|
||||
|
||||
interface QueryTableProps {
|
||||
@@ -235,7 +246,7 @@ const QueryTable = ({
|
||||
return queries
|
||||
.map(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;
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ const RunQueryActionButton = ({
|
||||
}
|
||||
: {
|
||||
buttonStyle: shouldShowStopBtn ? 'danger' : 'primary',
|
||||
icon,
|
||||
icon: icon as any,
|
||||
})}
|
||||
>
|
||||
{text}
|
||||
|
||||
@@ -130,7 +130,7 @@ const SouthPane = ({
|
||||
dispatch(setActiveSouthPaneTab(id));
|
||||
};
|
||||
const removeTable = useCallback(
|
||||
(key, action) => {
|
||||
(key: any, action: any) => {
|
||||
if (action === 'remove') {
|
||||
const table = pinnedTables.find(
|
||||
({ dbId, catalog, schema, name }) =>
|
||||
|
||||
@@ -580,7 +580,7 @@ const SqlEditor: FC<Props> = ({
|
||||
};
|
||||
|
||||
const setQueryEditorAndSaveSql = useCallback(
|
||||
sql => {
|
||||
(sql: any) => {
|
||||
dispatch(queryEditorSetAndSaveSql(queryEditor, sql));
|
||||
},
|
||||
[dispatch, queryEditor],
|
||||
|
||||
@@ -95,7 +95,7 @@ class TabbedSqlEditors extends PureComponent<TabbedSqlEditorsProps> {
|
||||
new: isNewQuery,
|
||||
...urlParams
|
||||
} = {
|
||||
...this.context.requestedQuery,
|
||||
...(this.context as any).requestedQuery,
|
||||
...bootstrapData.requested_query,
|
||||
...queryParameters,
|
||||
} as Record<string, string>;
|
||||
@@ -135,7 +135,7 @@ class TabbedSqlEditors extends PureComponent<TabbedSqlEditorsProps> {
|
||||
schema,
|
||||
autorun,
|
||||
sql,
|
||||
isDataset: this.context.isDataset,
|
||||
isDataset: (this.context as any).isDataset,
|
||||
};
|
||||
this.props.actions.addQueryEditor(newQueryEditor);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import configureStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
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 useQueryEditor from '.';
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { PureComponent } from 'react';
|
||||
import { PureComponent, ErrorInfo } from 'react';
|
||||
import {
|
||||
ensureIsArray,
|
||||
FeatureFlag,
|
||||
@@ -205,16 +205,13 @@ class Chart extends PureComponent<ChartProps, {}> {
|
||||
);
|
||||
}
|
||||
|
||||
handleRenderContainerFailure(
|
||||
error: Error,
|
||||
info: { componentStack: string } | null,
|
||||
) {
|
||||
handleRenderContainerFailure(error: Error, info: ErrorInfo) {
|
||||
const { actions, chartId } = this.props;
|
||||
logging.warn(error);
|
||||
actions.chartRenderingFailed(
|
||||
error.toString(),
|
||||
chartId,
|
||||
info ? info.componentStack : null,
|
||||
info ? (info.componentStack ?? null) : null,
|
||||
);
|
||||
|
||||
actions.logEvent(LOG_ACTIONS_RENDER_CHART, {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
import { FeatureFlag, VizType } from '@superset-ui/core';
|
||||
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 { sliceId } from 'spec/fixtures/mockChartQueries';
|
||||
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.
|
||||
*/
|
||||
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import {
|
||||
render,
|
||||
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(
|
||||
colName => {
|
||||
(colName: any) => {
|
||||
const updatedFilterMap = { ...filterMap };
|
||||
delete updatedFilterMap[colName];
|
||||
setFilters(Object.values(updatedFilterMap));
|
||||
@@ -109,7 +109,7 @@ export default function TableControls({
|
||||
>
|
||||
{colName}
|
||||
</span>
|
||||
<strong data-test="filter-val">{val}</strong>
|
||||
<strong data-test="filter-val">{String(val)}</strong>
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -116,7 +116,7 @@ export const useDrillDetailMenuItems = ({
|
||||
);
|
||||
|
||||
const openModal = useCallback(
|
||||
(filters, event) => {
|
||||
(filters: any, event: any) => {
|
||||
onClick(event);
|
||||
onSelection();
|
||||
setFilters(filters);
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function Field<V>({
|
||||
errorMessage,
|
||||
}: FieldProps<V>) {
|
||||
const onControlChange = useCallback(
|
||||
newValue => {
|
||||
(newValue: any) => {
|
||||
onChange(fieldKey, newValue);
|
||||
},
|
||||
[onChange, fieldKey],
|
||||
|
||||
@@ -16,7 +16,13 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useContext, useEffect, useReducer, createContext, FC } from 'react';
|
||||
import {
|
||||
useContext,
|
||||
useEffect,
|
||||
useReducer,
|
||||
createContext,
|
||||
PropsWithChildren,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
ChartMetadata,
|
||||
@@ -122,7 +128,7 @@ const sharedModules = {
|
||||
'@superset-ui/core': () => import('@superset-ui/core'),
|
||||
};
|
||||
|
||||
export const DynamicPluginProvider: FC = ({ children }) => {
|
||||
export const DynamicPluginProvider = ({ children }: PropsWithChildren) => {
|
||||
const [pluginState, dispatch] = useReducer(
|
||||
pluginContextReducer,
|
||||
dummyPluginContext,
|
||||
|
||||
@@ -112,7 +112,7 @@ export const FilterableTable = ({
|
||||
const keyword = useRef<string | undefined>(filterText);
|
||||
keyword.current = filterText;
|
||||
|
||||
const keywordFilter = useCallback(node => {
|
||||
const keywordFilter = useCallback((node: any) => {
|
||||
if (keyword.current && node.data) {
|
||||
return hasMatch(keyword.current, node.data);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useCellContentParser } from './useCellContentParser';
|
||||
|
||||
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 [sortIndex, setSortIndex] = useState<number | null>();
|
||||
const onSort = useCallback(
|
||||
event => {
|
||||
(event: any) => {
|
||||
sortOption.current = (sortOption.current + 1) % SORT_DIRECTION.length;
|
||||
const sort = SORT_DIRECTION[sortOption.current];
|
||||
setSort(sort, event.shiftKey);
|
||||
|
||||
@@ -53,32 +53,32 @@ export function GridTable<RecordType extends object>({
|
||||
[externalFilter],
|
||||
);
|
||||
const rowIndexLength = `${data.length}}`.length;
|
||||
const onKeyDown: AgGridReactProps<Record<string, any>>['onCellKeyDown'] =
|
||||
useCallback(({ event, column, data, value, api }) => {
|
||||
if (
|
||||
!document.getSelection?.()?.toString?.() &&
|
||||
event &&
|
||||
event.key === 'c' &&
|
||||
(event.ctrlKey || event.metaKey)
|
||||
) {
|
||||
const columns =
|
||||
column.getColId() === PIVOT_COL_ID
|
||||
? api
|
||||
.getAllDisplayedColumns()
|
||||
.filter((column: Column) => column.getColId() !== PIVOT_COL_ID)
|
||||
: [column];
|
||||
const record =
|
||||
column.getColId() === PIVOT_COL_ID
|
||||
? [
|
||||
columns.map((column: Column) => column.getColId()).join('\t'),
|
||||
columns
|
||||
.map((column: Column) => data?.[column.getColId()])
|
||||
.join('\t'),
|
||||
].join('\n')
|
||||
: String(value);
|
||||
copyTextToClipboard(() => Promise.resolve(record));
|
||||
}
|
||||
}, []);
|
||||
const onKeyDown = useCallback((params: any) => {
|
||||
const { event, column, data, value, api } = params;
|
||||
if (
|
||||
!document.getSelection?.()?.toString?.() &&
|
||||
event &&
|
||||
event.key === 'c' &&
|
||||
(event.ctrlKey || event.metaKey)
|
||||
) {
|
||||
const columns =
|
||||
column.getColId() === PIVOT_COL_ID
|
||||
? api
|
||||
.getAllDisplayedColumns()
|
||||
.filter((column: Column) => column.getColId() !== PIVOT_COL_ID)
|
||||
: [column];
|
||||
const record =
|
||||
column.getColId() === PIVOT_COL_ID
|
||||
? [
|
||||
columns.map((column: Column) => column.getColId()).join('\t'),
|
||||
columns
|
||||
.map((column: Column) => data?.[column.getColId()])
|
||||
.join('\t'),
|
||||
].join('\n')
|
||||
: String(value);
|
||||
copyTextToClipboard(() => Promise.resolve(record));
|
||||
}
|
||||
}, []);
|
||||
const columnDefs = useMemo(
|
||||
() =>
|
||||
[
|
||||
@@ -179,7 +179,7 @@ export function GridTable<RecordType extends object>({
|
||||
isExternalFilterPresent={isExternalFilterPresent}
|
||||
doesExternalFilterPass={externalFilter}
|
||||
components={gridComponents}
|
||||
gridOptions={gridOptions}
|
||||
gridOptions={gridOptions as any}
|
||||
onCellKeyDown={onKeyDown}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'spec/helpers/testing-library';
|
||||
import {
|
||||
useModalValidation,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import {
|
||||
LocalStorageKeys,
|
||||
setItem,
|
||||
|
||||
@@ -400,7 +400,7 @@ const DashboardBuilder = () => {
|
||||
}, [dashboardLayout, dispatch]);
|
||||
|
||||
const handleDrop = useCallback(
|
||||
dropResult => dispatch(handleComponentDrop(dropResult)),
|
||||
(dropResult: any) => dispatch(handleComponentDrop(dropResult)),
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
@@ -559,7 +559,7 @@ const DashboardBuilder = () => {
|
||||
: theme.sizeUnit * 8;
|
||||
|
||||
const renderChild = useCallback(
|
||||
adjustedWidth => {
|
||||
(adjustedWidth: any) => {
|
||||
const filterBarWidth = dashboardFiltersOpen
|
||||
? adjustedWidth
|
||||
: CLOSED_FILTER_BAR_WIDTH;
|
||||
|
||||
@@ -279,7 +279,7 @@ const DashboardContainer: FC<DashboardContainerProps> = ({ topLevelTabs }) => {
|
||||
}, [onBeforeUnload]);
|
||||
|
||||
const renderTabBar = useCallback(() => <></>, []);
|
||||
const handleFocus = useCallback(e => {
|
||||
const handleFocus = useCallback((e: any) => {
|
||||
if (
|
||||
// prevent scrolling when tabbing to the tab pane
|
||||
e.target.classList.contains('ant-tabs-tabpane') &&
|
||||
@@ -293,7 +293,7 @@ const DashboardContainer: FC<DashboardContainerProps> = ({ topLevelTabs }) => {
|
||||
}, []);
|
||||
|
||||
const renderParentSizeChildren = useCallback(
|
||||
({ width }) => {
|
||||
({ width }: { width: any }) => {
|
||||
const tabItems = childIds.map((id, index) => ({
|
||||
key: index === 0 ? DASHBOARD_GRID_ID : index.toString(),
|
||||
label: null,
|
||||
|
||||
@@ -22,7 +22,7 @@ import { css, styled } from '@apache-superset/core/ui';
|
||||
import { Constants } from '@superset-ui/core/components';
|
||||
import { RootState } from 'src/dashboard/types';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useDragDropManager } from 'react-dnd';
|
||||
import { useDndMonitor } from '@dnd-kit/core';
|
||||
import classNames from 'classnames';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
@@ -117,34 +117,30 @@ const DashboardWrapper: FC<PropsWithChildren<{}>> = ({ children }) => {
|
||||
const editMode = useSelector<RootState, boolean>(
|
||||
state => state.dashboardState.editMode,
|
||||
);
|
||||
const dragDropManager = useDragDropManager();
|
||||
const [isDragged, setIsDragged] = useState(
|
||||
dragDropManager.getMonitor().isDragging(),
|
||||
const [isDragged, setIsDragged] = useState(false);
|
||||
|
||||
const debouncedSetIsDragged = debounce(
|
||||
setIsDragged,
|
||||
Constants.FAST_DEBOUNCE,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const monitor = dragDropManager.getMonitor();
|
||||
const debouncedSetIsDragged = debounce(
|
||||
setIsDragged,
|
||||
Constants.FAST_DEBOUNCE,
|
||||
);
|
||||
const unsub = monitor.subscribeToStateChange(() => {
|
||||
const isDragging = monitor.isDragging();
|
||||
if (isDragging) {
|
||||
// set a debounced function to prevent HTML5 drag source
|
||||
// from interfering with the drop zone highlighting
|
||||
debouncedSetIsDragged(true);
|
||||
} else {
|
||||
debouncedSetIsDragged.cancel();
|
||||
setIsDragged(false);
|
||||
}
|
||||
});
|
||||
useDndMonitor({
|
||||
onDragStart() {
|
||||
// set a debounced function to prevent drag source
|
||||
// from interfering with the drop zone highlighting
|
||||
debouncedSetIsDragged(true);
|
||||
},
|
||||
onDragEnd() {
|
||||
debouncedSetIsDragged.cancel();
|
||||
setIsDragged(false);
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
unsub();
|
||||
debouncedSetIsDragged.cancel();
|
||||
};
|
||||
}, [dragDropManager]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<StyledDiv
|
||||
|
||||
@@ -154,7 +154,7 @@ const PropertiesModal = ({
|
||||
};
|
||||
|
||||
const handleDashboardData = useCallback(
|
||||
dashboardData => {
|
||||
(dashboardData: any) => {
|
||||
const {
|
||||
id,
|
||||
dashboard_title,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { DragLayer, XYCoord } from 'react-dnd';
|
||||
import { useDragLayer } from 'react-dnd';
|
||||
import { Slice } from 'src/dashboard/types';
|
||||
import AddSliceCard from '../AddSliceCard';
|
||||
import {
|
||||
@@ -31,10 +31,7 @@ interface DragItem {
|
||||
}
|
||||
|
||||
interface AddSliceDragPreviewProps {
|
||||
dragItem: DragItem | null;
|
||||
slices: Slice[] | null;
|
||||
isDragging: boolean;
|
||||
currentOffset: XYCoord | null;
|
||||
}
|
||||
|
||||
const staticCardStyles: React.CSSProperties = {
|
||||
@@ -47,11 +44,15 @@ const staticCardStyles: React.CSSProperties = {
|
||||
};
|
||||
|
||||
const AddSliceDragPreview: React.FC<AddSliceDragPreviewProps> = ({
|
||||
dragItem,
|
||||
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;
|
||||
|
||||
const slice = slices[dragItem.index];
|
||||
@@ -77,9 +78,4 @@ const AddSliceDragPreview: React.FC<AddSliceDragPreviewProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
// This injects these props into the component
|
||||
export default DragLayer(monitor => ({
|
||||
dragItem: monitor.getItem() as DragItem | null,
|
||||
currentOffset: monitor.getSourceClientOffset(),
|
||||
isDragging: monitor.isDragging(),
|
||||
}))(AddSliceDragPreview);
|
||||
export default 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
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
DragSourceMonitor,
|
||||
DropTargetMonitor,
|
||||
ConnectDragSource,
|
||||
ConnectDragPreview,
|
||||
ConnectDropTarget,
|
||||
} from 'react-dnd';
|
||||
|
||||
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
|
||||
const TYPE = 'DRAG_DROPPABLE';
|
||||
@@ -68,121 +60,7 @@ export interface DropResult {
|
||||
position?: string;
|
||||
}
|
||||
|
||||
export interface DragStateProps {
|
||||
dragSourceRef: ConnectDragSource;
|
||||
dragPreviewRef: ConnectDragPreview;
|
||||
isDragging: boolean;
|
||||
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 }),
|
||||
};
|
||||
},
|
||||
];
|
||||
// For @dnd-kit, we'll handle drag/drop logic directly in the component
|
||||
// These exports maintain backward compatibility for components that import from this file
|
||||
export { TYPE };
|
||||
export default { TYPE };
|
||||
@@ -16,13 +16,69 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { DropTargetMonitor } from 'react-dnd';
|
||||
import getDropPosition, {
|
||||
clearDropCache,
|
||||
DROP_FORBIDDEN,
|
||||
} from '../../util/getDropPosition';
|
||||
|
||||
export default function handleDrop(props, monitor, Component) {
|
||||
// this may happen due to throttling
|
||||
interface DragItem {
|
||||
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;
|
||||
|
||||
Component.setState(() => ({ dropIndicator: null }));
|
||||
@@ -40,9 +96,9 @@ export default function handleDrop(props, monitor, Component) {
|
||||
dropToChild,
|
||||
} = Component.props;
|
||||
|
||||
const draggingItem = monitor.getItem();
|
||||
const draggingItem = monitor.getItem() as DragItem;
|
||||
|
||||
const dropResult = {
|
||||
const dropResult: DropResult = {
|
||||
source: {
|
||||
id: draggingItem.parentId,
|
||||
type: draggingItem.parentType,
|
||||
@@ -54,12 +110,16 @@ export default function handleDrop(props, monitor, Component) {
|
||||
meta: draggingItem.meta,
|
||||
},
|
||||
position: dropPosition,
|
||||
destination: {
|
||||
id: '',
|
||||
type: '',
|
||||
index: 0,
|
||||
},
|
||||
};
|
||||
|
||||
const shouldAppendToChildren =
|
||||
typeof dropToChild === 'function' ? dropToChild(draggingItem) : dropToChild;
|
||||
|
||||
// simplest case, append as child
|
||||
if (shouldAppendToChildren) {
|
||||
dropResult.destination = {
|
||||
id: component.id,
|
||||
@@ -73,8 +133,6 @@ export default function handleDrop(props, monitor, Component) {
|
||||
index: componentIndex,
|
||||
};
|
||||
} 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 =
|
||||
parentComponent && draggingItem.parentId === parentComponent.id;
|
||||
const sameParentLowerIndex =
|
||||
@@ -17,14 +17,33 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { throttle } from 'lodash';
|
||||
import { DropTargetMonitor } from 'react-dnd';
|
||||
import { DASHBOARD_ROOT_TYPE } from 'src/dashboard/util/componentTypes';
|
||||
import getDropPosition from 'src/dashboard/util/getDropPosition';
|
||||
import handleScroll from './handleScroll';
|
||||
|
||||
const HOVER_THROTTLE_MS = 100;
|
||||
|
||||
function handleHover(props, monitor, Component) {
|
||||
// this may happen due to throttling
|
||||
interface ComponentType {
|
||||
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;
|
||||
|
||||
const dropPosition = getDropPosition(monitor, Component);
|
||||
@@ -40,12 +59,11 @@ function handleHover(props, monitor, Component) {
|
||||
return;
|
||||
}
|
||||
|
||||
Component?.props?.onHover();
|
||||
Component?.props?.onHover?.();
|
||||
|
||||
Component.setState(() => ({
|
||||
dropIndicator: dropPosition,
|
||||
}));
|
||||
}
|
||||
|
||||
// this is called very frequently by react-dnd
|
||||
export default throttle(handleHover, HOVER_THROTTLE_MS);
|
||||
@@ -239,7 +239,7 @@ const ChartHolder = ({
|
||||
}, []);
|
||||
|
||||
const renderChild = useCallback(
|
||||
({ dragSourceRef }) => (
|
||||
({ dragSourceRef }: { dragSourceRef: any }) => (
|
||||
<ResizableContainer
|
||||
id={component.id}
|
||||
adjustableWidth={parentComponent.type === ROW_TYPE}
|
||||
|
||||
@@ -89,7 +89,7 @@ const DynamicComponent: FC<DynamicComponentProps> = ({
|
||||
|
||||
const updateMeta = (metaKey: string, nextValue: string | number) => {
|
||||
updateComponents({
|
||||
[component.id]: {
|
||||
[String(component.id)]: {
|
||||
...component,
|
||||
meta: {
|
||||
...component.meta,
|
||||
@@ -126,7 +126,9 @@ const DynamicComponent: FC<DynamicComponentProps> = ({
|
||||
<BackgroundStyleDropdown
|
||||
id={`${component.id}-background`}
|
||||
value={component.meta.background}
|
||||
onChange={value => updateMeta('background', value)}
|
||||
onChange={value =>
|
||||
updateMeta('background', value as string | number)
|
||||
}
|
||||
/>,
|
||||
]}
|
||||
editMode={editMode}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
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 { useCrossFiltersScopingModal } from './useCrossFiltersScopingModal';
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ const FilterBarSettings = () => {
|
||||
});
|
||||
|
||||
const updateCrossFiltersSetting = useCallback(
|
||||
async isEnabled => {
|
||||
async (isEnabled: any) => {
|
||||
if (!isEnabled) {
|
||||
dispatch(clearDataMaskState());
|
||||
}
|
||||
|
||||
@@ -341,7 +341,7 @@ const FilterControls: FC<FilterControlsProps> = ({
|
||||
}, [overflowedCrossFilters, overflowedFiltersInScope]);
|
||||
|
||||
const rendererCrossFilter = useCallback(
|
||||
(crossFilter, orientation, last) => (
|
||||
(crossFilter: any, orientation: any, last: any) => (
|
||||
<CrossFilter
|
||||
filter={crossFilter}
|
||||
orientation={orientation}
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useRef, FC } from 'react';
|
||||
import { styled } from '@apache-superset/core/ui';
|
||||
import { useRef, FC } from 'react';
|
||||
import {
|
||||
DragSourceMonitor,
|
||||
DropTargetMonitor,
|
||||
@@ -57,6 +57,7 @@ interface FilterTabTitleProps {
|
||||
index: number;
|
||||
filterIds: string[];
|
||||
onRearrange: (dragItemIndex: number, targetIndex: number) => void;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface DragItem {
|
||||
@@ -73,6 +74,7 @@ export const DraggableFilter: FC<FilterTabTitleProps> = ({
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [{ isDragging }, drag] = useDrag({
|
||||
type: FILTER_TYPE,
|
||||
item: { filterIds, type: FILTER_TYPE, index },
|
||||
collect: (monitor: DragSourceMonitor) => ({
|
||||
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