mirror of
https://github.com/apache/superset.git
synced 2026-05-04 07:24:18 +00:00
Compare commits
43 Commits
fix/check-
...
msyavuz/ch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e44fdcadf1 | ||
|
|
a81b8fc68a | ||
|
|
5b1dfdd85a | ||
|
|
1de8019f1c | ||
|
|
d31a546653 | ||
|
|
301610faca | ||
|
|
8e3a61ef3d | ||
|
|
8f71ff2e78 | ||
|
|
92d219ad34 | ||
|
|
3fb4c08966 | ||
|
|
7ba2ea65bf | ||
|
|
c9a2b1d907 | ||
|
|
eb67530d4a | ||
|
|
156af9a3e8 | ||
|
|
3efb2c5743 | ||
|
|
556aeeb7b0 | ||
|
|
8d110cd3db | ||
|
|
e3f3212644 | ||
|
|
9309b769dd | ||
|
|
fc6d783578 | ||
|
|
210d9dbdf2 | ||
|
|
742af2f1d8 | ||
|
|
ffe14568cc | ||
|
|
e460120983 | ||
|
|
f0c809bad2 | ||
|
|
0b2fca90ba | ||
|
|
28ae7f67dc | ||
|
|
55fca52ecf | ||
|
|
ab1b079818 | ||
|
|
6b0b16310e | ||
|
|
2f1b082cec | ||
|
|
85cb87e413 | ||
|
|
dd7a2f153a | ||
|
|
2479f0fc18 | ||
|
|
8a7964dabd | ||
|
|
8b6f7b3583 | ||
|
|
9b1b3b3e45 | ||
|
|
353c222292 | ||
|
|
80cd613dc4 | ||
|
|
2360702292 | ||
|
|
50aa59067a | ||
|
|
09b341f7c2 | ||
|
|
51b47f9400 |
9834
superset-frontend/package-lock.json
generated
9834
superset-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -194,13 +194,13 @@
|
||||
"pretty-ms": "^9.3.0",
|
||||
"query-string": "9.3.1",
|
||||
"re-resizable": "^6.11.2",
|
||||
"react": "^17.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-arborist": "^3.5.0",
|
||||
"react-checkbox-tree": "^1.8.0",
|
||||
"react-diff-viewer-continued": "^4.2.2",
|
||||
"react-dnd": "^11.1.3",
|
||||
"react-dnd-html5-backend": "^11.1.3",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-google-recaptcha": "^3.1.0",
|
||||
"react-intersection-observer": "^10.0.3",
|
||||
"react-json-tree": "^0.20.0",
|
||||
@@ -211,7 +211,6 @@
|
||||
"react-reverse-portal": "^2.3.0",
|
||||
"react-router-dom": "^5.3.4",
|
||||
"react-search-input": "^0.11.3",
|
||||
"react-sortable-hoc": "^2.0.0",
|
||||
"react-split": "^2.0.9",
|
||||
"react-table": "^7.8.0",
|
||||
"react-transition-group": "^4.4.5",
|
||||
@@ -251,7 +250,6 @@
|
||||
"@babel/runtime": "^7.29.2",
|
||||
"@babel/runtime-corejs3": "^7.29.2",
|
||||
"@babel/types": "^7.28.6",
|
||||
"@cypress/react": "^8.0.2",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/jest": "^11.14.2",
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
@@ -273,10 +271,9 @@
|
||||
"@swc/core": "^1.15.32",
|
||||
"@swc/plugin-emotion": "^14.8.0",
|
||||
"@swc/plugin-transform-imports": "^12.5.0",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@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",
|
||||
@@ -286,8 +283,8 @@
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@types/mousetrap": "^1.6.15",
|
||||
"@types/node": "^25.6.0",
|
||||
"@types/react": "^17.0.83",
|
||||
"@types/react-dom": "^17.0.26",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"@types/react-loadable": "^5.5.11",
|
||||
"@types/react-redux": "^7.1.10",
|
||||
"@types/react-resizable": "^3.0.8",
|
||||
|
||||
@@ -81,10 +81,9 @@
|
||||
"typescript": "^5.0.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
"@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": "*",
|
||||
@@ -98,8 +97,8 @@
|
||||
"@fontsource/ibm-plex-mono": "^5.2.7",
|
||||
"@fontsource/inter": "^5.2.6",
|
||||
"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": "*",
|
||||
"lodash": "^4.18.1",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { ReactElement } from 'react';
|
||||
import { render, RenderOptions } from '@testing-library/react';
|
||||
import { render, RenderOptions, RenderResult } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { themeObject } from './theme';
|
||||
|
||||
@@ -33,7 +33,7 @@ const Providers = ({ children }: { children: React.ReactNode }) => (
|
||||
const customRender = (
|
||||
ui: ReactElement,
|
||||
options?: Omit<RenderOptions, 'wrapper'>,
|
||||
) => render(ui, { wrapper: Providers, ...options });
|
||||
): RenderResult => render(ui, { wrapper: Providers, ...options });
|
||||
|
||||
export {
|
||||
createEvent,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { ThemeProvider } from '@emotion/react';
|
||||
import { theme as antdTheme } from 'antd';
|
||||
import {
|
||||
|
||||
@@ -33,17 +33,16 @@
|
||||
"@ant-design/icons": "^5.6.1",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@superset-ui/core": "*",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
"@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"
|
||||
|
||||
@@ -91,10 +91,9 @@
|
||||
"@emotion/cache": "^11.4.0",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
"@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": "*",
|
||||
@@ -102,8 +101,8 @@
|
||||
"@types/tinycolor2": "*",
|
||||
"antd": "^5.26.0",
|
||||
"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": "*"
|
||||
},
|
||||
|
||||
@@ -17,19 +17,15 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import type { ReactElement } from 'react';
|
||||
import {
|
||||
Tooltip,
|
||||
type TooltipPlacement,
|
||||
type IconType,
|
||||
} from '@superset-ui/core/components';
|
||||
import type { ReactElement, ReactNode } from 'react';
|
||||
import { Tooltip, type TooltipPlacement } from '@superset-ui/core/components';
|
||||
import { css, useTheme } from '@apache-superset/core/theme';
|
||||
|
||||
export interface ActionProps {
|
||||
label: string;
|
||||
tooltip?: string | ReactElement;
|
||||
placement?: TooltipPlacement;
|
||||
icon: IconType;
|
||||
icon: ReactNode;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -16,16 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
useEffect,
|
||||
useState,
|
||||
RefObject,
|
||||
forwardRef,
|
||||
ComponentType,
|
||||
ForwardRefExoticComponent,
|
||||
PropsWithoutRef,
|
||||
RefAttributes,
|
||||
} from 'react';
|
||||
import React, { useEffect, useState, forwardRef, ComponentType } from 'react';
|
||||
|
||||
import { Loading } from '../Loading';
|
||||
import type { PlaceholderProps } from './types';
|
||||
@@ -93,15 +84,16 @@ export function AsyncEsmComponent<
|
||||
return promise;
|
||||
}
|
||||
|
||||
type AsyncComponent = ForwardRefExoticComponent<
|
||||
PropsWithoutRef<FullProps> & RefAttributes<ComponentType<FullProps>>
|
||||
type AsyncComponent = React.ForwardRefExoticComponent<
|
||||
React.PropsWithoutRef<FullProps> & React.RefAttributes<unknown>
|
||||
> & {
|
||||
preload?: typeof waitForPromise;
|
||||
};
|
||||
|
||||
// @ts-expect-error -- generic forwardRef has PropsWithoutRef incompatibility with FullProps
|
||||
const AsyncComponent: AsyncComponent = forwardRef(function AsyncComponent(
|
||||
props: FullProps,
|
||||
ref: RefObject<ComponentType<FullProps>>,
|
||||
ref,
|
||||
) {
|
||||
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,5 @@ export type ButtonProps = Omit<AntdButtonProps, 'css'> & {
|
||||
buttonStyle?: ButtonStyle;
|
||||
cta?: boolean;
|
||||
showMarginRight?: boolean;
|
||||
icon?: IconType;
|
||||
icon?: ReactNode;
|
||||
};
|
||||
|
||||
@@ -73,7 +73,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.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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 { fireEvent, render, screen, userEvent } from '@superset-ui/core/spec';
|
||||
import { useState } from 'react';
|
||||
import { DynamicEditableTitle } from '.';
|
||||
|
||||
const Harness = ({ initialTitle = 'Original' }: { initialTitle?: string }) => {
|
||||
const [title, setTitle] = useState(initialTitle);
|
||||
return (
|
||||
<DynamicEditableTitle
|
||||
title={title}
|
||||
placeholder="placeholder"
|
||||
canEdit
|
||||
label="Title"
|
||||
onSave={setTitle}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
test('rapid typing then backspacing keeps every keystroke', async () => {
|
||||
render(<Harness />);
|
||||
const input = screen.getByRole('textbox') as HTMLInputElement;
|
||||
userEvent.click(input);
|
||||
await userEvent.type(input, 'abc', { delay: 1 });
|
||||
expect(input.value).toBe('Originalabc');
|
||||
await userEvent.type(input, '{backspace}{backspace}{backspace}', {
|
||||
delay: 1,
|
||||
});
|
||||
expect(input.value).toBe('Original');
|
||||
});
|
||||
|
||||
test('a change event that arrives before isEditing flips is not dropped', () => {
|
||||
// Reproduces the regression: the input is focused but `isEditing` is still
|
||||
// false because no click has been registered yet (e.g. focus arrived via
|
||||
// tab, autofocus, or programmatic focus). The pre-fix `handleChange`
|
||||
// bailed out with `!isEditing`, dropping the keystroke. Because the
|
||||
// input is controlled, antd's internal `useMergedState` then resyncs the
|
||||
// DOM value back to the (stale) `props.value`, so the user sees their
|
||||
// typed character disappear. This test fires a raw change event so it
|
||||
// doesn't go through userEvent's implicit click.
|
||||
const onSave = jest.fn();
|
||||
render(
|
||||
<DynamicEditableTitle
|
||||
title="Foo"
|
||||
placeholder="placeholder"
|
||||
canEdit
|
||||
label="Title"
|
||||
onSave={onSave}
|
||||
/>,
|
||||
);
|
||||
const input = screen.getByRole('textbox') as HTMLInputElement;
|
||||
fireEvent.change(input, { target: { value: 'FooX' } });
|
||||
expect(input.value).toBe('FooX');
|
||||
});
|
||||
|
||||
test('prop changes mid-edit do not clobber unsaved typing', async () => {
|
||||
const { rerender } = render(<Harness initialTitle="Foo" />);
|
||||
const input = screen.getByRole('textbox') as HTMLInputElement;
|
||||
userEvent.click(input);
|
||||
await userEvent.type(input, 'X', { delay: 1 });
|
||||
expect(input.value).toBe('FooX');
|
||||
rerender(<Harness initialTitle="Foo" />);
|
||||
expect(input.value).toBe('FooX');
|
||||
});
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
@@ -30,6 +31,7 @@ import { css, SupersetTheme, useTheme } from '@apache-superset/core/theme';
|
||||
import { useResizeDetector } from 'react-resize-detector';
|
||||
import { Tooltip } from '../Tooltip';
|
||||
import { Input } from '../Input';
|
||||
import type { InputRef } from '../Input';
|
||||
import type { DynamicEditableTitleProps } from './types';
|
||||
|
||||
const titleStyles = (theme: SupersetTheme) => css`
|
||||
@@ -75,8 +77,10 @@ export const DynamicEditableTitle = memo(
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
const [currentTitle, setCurrentTitle] = useState(title || '');
|
||||
const [inputWidth, setInputWidth] = useState<number>(0);
|
||||
|
||||
const { width: inputWidth, ref: sizerRef } = useResizeDetector();
|
||||
const sizerRef = useRef<HTMLSpanElement>(null);
|
||||
const inputRef = useRef<InputRef>(null);
|
||||
const { width: containerWidth, ref: containerRef } = useResizeDetector({
|
||||
refreshMode: 'debounce',
|
||||
});
|
||||
@@ -85,27 +89,33 @@ export const DynamicEditableTitle = memo(
|
||||
setCurrentTitle(title);
|
||||
}, [title]);
|
||||
useEffect(() => {
|
||||
if (isEditing && sizerRef?.current) {
|
||||
if (isEditing) {
|
||||
// move cursor and scroll to the end
|
||||
if (sizerRef.current.setSelectionRange) {
|
||||
const { length } = sizerRef.current.value;
|
||||
sizerRef.current.setSelectionRange(length, length);
|
||||
sizerRef.current.scrollLeft = sizerRef.current.scrollWidth;
|
||||
const inputElement = inputRef.current?.input;
|
||||
if (inputElement) {
|
||||
const { length } = inputElement.value;
|
||||
inputElement.setSelectionRange(length, length);
|
||||
inputElement.scrollLeft = inputElement.scrollWidth;
|
||||
}
|
||||
}
|
||||
}, [isEditing]);
|
||||
|
||||
// a trick to make the input grow when user types text
|
||||
// we make additional span component, place it somewhere out of view and copy input
|
||||
// then we can measure the width of that span to resize the input element
|
||||
// we make an additional span component, place it somewhere out of view and
|
||||
// mirror the input value, then measure the span synchronously (pre-paint)
|
||||
// to resize the input element. Reading offsetWidth in a useLayoutEffect
|
||||
// forces a sync layout, so the input width updates in the same commit as
|
||||
// the value change — preventing a flicker frame where the input is shown
|
||||
// with new value but stale width.
|
||||
useLayoutEffect(() => {
|
||||
if (sizerRef?.current) {
|
||||
if (sizerRef.current) {
|
||||
sizerRef.current.textContent = currentTitle || placeholder;
|
||||
setInputWidth(sizerRef.current.offsetWidth);
|
||||
}
|
||||
}, [currentTitle, placeholder, sizerRef]);
|
||||
}, [currentTitle, placeholder]);
|
||||
|
||||
useEffect(() => {
|
||||
const inputElement = sizerRef.current?.input;
|
||||
const inputElement = inputRef.current?.input;
|
||||
|
||||
if (inputElement) {
|
||||
if (inputElement.scrollWidth > inputElement.clientWidth) {
|
||||
@@ -137,9 +147,17 @@ export const DynamicEditableTitle = memo(
|
||||
|
||||
const handleChange = useCallback(
|
||||
(ev: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!canEdit || !isEditing) {
|
||||
if (!canEdit) {
|
||||
return;
|
||||
}
|
||||
// Any change implies the user is editing. Ensure isEditing is true
|
||||
// even if the change event arrives before the click handler has
|
||||
// committed (e.g. focus via tab, autofocus, or batched click+type
|
||||
// events). Otherwise the keystroke would be dropped and the
|
||||
// controlled input would revert to the previous value.
|
||||
if (!isEditing) {
|
||||
setIsEditing(true);
|
||||
}
|
||||
setCurrentTitle(ev.target.value);
|
||||
},
|
||||
[canEdit, isEditing],
|
||||
@@ -168,6 +186,7 @@ export const DynamicEditableTitle = memo(
|
||||
}
|
||||
>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
data-test="editable-title-input"
|
||||
variant="borderless"
|
||||
aria-label={label ?? t('Title')}
|
||||
|
||||
@@ -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;
|
||||
/** Controls image size. Defaults to 'medium'. */
|
||||
size?: EmptyStateSize;
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Form as AntdForm } from 'antd';
|
||||
import { FormProps } from './types';
|
||||
|
||||
function CustomForm(props: FormProps) {
|
||||
return <AntdForm {...props} />;
|
||||
return <AntdForm {...(props as any)} />;
|
||||
}
|
||||
|
||||
export const Form = Object.assign(CustomForm, {
|
||||
|
||||
@@ -41,7 +41,6 @@ test('renders with monospace prop', () => {
|
||||
|
||||
// test stories from the storybook!
|
||||
test('renders all the storybook gallery variants', () => {
|
||||
// @ts-expect-error: Suppress TypeScript error for LabelGallery usage
|
||||
const { container } = render(<LabelGallery />);
|
||||
const nonInteractiveLabelCount = 4;
|
||||
const renderedLabelCount = options.length * 2 + nonInteractiveLabelCount;
|
||||
|
||||
@@ -21,6 +21,7 @@ import type { BackgroundPosition } from './ImageLoader';
|
||||
|
||||
export interface LinkProps {
|
||||
to: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export interface ListViewCardProps {
|
||||
|
||||
@@ -194,7 +194,7 @@ const MetadataBar = ({ items, tooltipPlacement = 'top' }: MetadataBarProps) => {
|
||||
}
|
||||
|
||||
const onResize = useCallback(
|
||||
width => {
|
||||
(width: number | undefined) => {
|
||||
// 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);
|
||||
|
||||
@@ -104,6 +104,9 @@ export const StyledModal = styled(BaseModal)<StyledModalProps>`
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
// Keep the close button clickable when modal body content uses
|
||||
// position: sticky with elevated z-index (e.g. DatabaseModal header).
|
||||
z-index: ${theme.zIndexPopupBase + 1};
|
||||
}
|
||||
|
||||
.ant-modal-close:hover {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import type { CSSProperties, ReactNode } from 'react';
|
||||
import type { ModalFuncProps } from 'antd';
|
||||
import type { FormInstance, ModalFuncProps } from 'antd';
|
||||
import type { ResizableProps } from 're-resizable';
|
||||
import type { DraggableProps } from 'react-draggable';
|
||||
import { ButtonStyle } from '../Button/types';
|
||||
@@ -68,7 +68,8 @@ export interface StyledModalProps {
|
||||
|
||||
export type { ModalFuncProps };
|
||||
|
||||
export interface FormModalProps extends ModalProps {
|
||||
export interface FormModalProps extends Omit<ModalProps, 'children'> {
|
||||
children: ReactNode | ((form: FormInstance) => ReactNode);
|
||||
initialValues?: object;
|
||||
formSubmitHandler: (values: object) => Promise<void>;
|
||||
onSave: () => void;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { ReactNode, ReactElement } from 'react';
|
||||
import { ReactNode, ReactElement, memo } from 'react';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { css, SupersetTheme, useTheme } from '@apache-superset/core/theme';
|
||||
import { Icons } from '@superset-ui/core/components/Icons';
|
||||
@@ -118,62 +118,64 @@ export type PageHeaderWithActionsProps = {
|
||||
};
|
||||
};
|
||||
|
||||
export const PageHeaderWithActions = ({
|
||||
editableTitleProps,
|
||||
showTitlePanelItems,
|
||||
certificatiedBadgeProps,
|
||||
showFaveStar,
|
||||
faveStarProps,
|
||||
titlePanelAdditionalItems,
|
||||
rightPanelAdditionalItems,
|
||||
additionalActionsMenu,
|
||||
menuDropdownProps,
|
||||
showMenuDropdown = true,
|
||||
tooltipProps,
|
||||
}: PageHeaderWithActionsProps) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<div css={headerStyles} className="header-with-actions">
|
||||
<div className="title-panel">
|
||||
<DynamicEditableTitle {...editableTitleProps} />
|
||||
{showTitlePanelItems && (
|
||||
<div css={buttonsStyles}>
|
||||
{certificatiedBadgeProps?.certifiedBy && (
|
||||
<CertifiedBadge {...certificatiedBadgeProps} />
|
||||
)}
|
||||
{showFaveStar && <FaveStar {...faveStarProps} />}
|
||||
{titlePanelAdditionalItems}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="right-button-panel">
|
||||
{rightPanelAdditionalItems}
|
||||
<div css={additionalActionsContainerStyles}>
|
||||
{showMenuDropdown && (
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
popupRender={() => additionalActionsMenu}
|
||||
{...menuDropdownProps}
|
||||
>
|
||||
<span>
|
||||
<Button
|
||||
css={menuTriggerStyles}
|
||||
buttonStyle="tertiary"
|
||||
aria-label={t('Menu actions trigger')}
|
||||
tooltip={tooltipProps?.text}
|
||||
placement={tooltipProps?.placement}
|
||||
data-test="actions-trigger"
|
||||
>
|
||||
<Icons.EllipsisOutlined
|
||||
iconColor={theme.colorPrimary}
|
||||
iconSize="l"
|
||||
/>
|
||||
</Button>
|
||||
</span>
|
||||
</Dropdown>
|
||||
export const PageHeaderWithActions = memo(
|
||||
({
|
||||
editableTitleProps,
|
||||
showTitlePanelItems,
|
||||
certificatiedBadgeProps,
|
||||
showFaveStar,
|
||||
faveStarProps,
|
||||
titlePanelAdditionalItems,
|
||||
rightPanelAdditionalItems,
|
||||
additionalActionsMenu,
|
||||
menuDropdownProps,
|
||||
showMenuDropdown = true,
|
||||
tooltipProps,
|
||||
}: PageHeaderWithActionsProps) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<div css={headerStyles} className="header-with-actions">
|
||||
<div className="title-panel">
|
||||
<DynamicEditableTitle {...editableTitleProps} />
|
||||
{showTitlePanelItems && (
|
||||
<div css={buttonsStyles}>
|
||||
{certificatiedBadgeProps?.certifiedBy && (
|
||||
<CertifiedBadge {...certificatiedBadgeProps} />
|
||||
)}
|
||||
{showFaveStar && <FaveStar {...faveStarProps} />}
|
||||
{titlePanelAdditionalItems}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="right-button-panel">
|
||||
{rightPanelAdditionalItems}
|
||||
<div css={additionalActionsContainerStyles}>
|
||||
{showMenuDropdown && (
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
popupRender={() => additionalActionsMenu}
|
||||
{...menuDropdownProps}
|
||||
>
|
||||
<span>
|
||||
<Button
|
||||
css={menuTriggerStyles}
|
||||
buttonStyle="tertiary"
|
||||
aria-label={t('Menu actions trigger')}
|
||||
tooltip={tooltipProps?.text}
|
||||
placement={tooltipProps?.placement}
|
||||
data-test="actions-trigger"
|
||||
>
|
||||
<Icons.EllipsisOutlined
|
||||
iconColor={theme.colorPrimary}
|
||||
iconSize="l"
|
||||
/>
|
||||
</Button>
|
||||
</span>
|
||||
</Dropdown>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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 '.';
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ export function mapColumns<T extends 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 +121,7 @@ export function mapColumns<T extends object>(
|
||||
column,
|
||||
});
|
||||
}
|
||||
return val;
|
||||
return val as ReactNode;
|
||||
},
|
||||
className: column.className,
|
||||
};
|
||||
|
||||
@@ -19,6 +19,14 @@
|
||||
import { render, screen, userEvent, waitFor } from '@superset-ui/core/spec';
|
||||
import { TableView, TableViewProps } from '.';
|
||||
|
||||
// Mock window.scrollTo to prevent jsdom "Not implemented" errors
|
||||
beforeAll(() => {
|
||||
window.scrollTo = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
const mockedProps: TableViewProps = {
|
||||
columns: [
|
||||
{
|
||||
@@ -125,27 +133,25 @@ test('should change page when pagination is clicked', async () => {
|
||||
expect(screen.getByText('Emily')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Kate')).not.toBeInTheDocument();
|
||||
|
||||
const page2 = screen.getByRole('listitem', { name: '2' });
|
||||
await userEvent.click(page2);
|
||||
await userEvent.click(screen.getByTitle('Next Page'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByRole('cell')).toHaveLength(3);
|
||||
expect(screen.getByText('321')).toBeInTheDocument();
|
||||
expect(screen.getByText('10')).toBeInTheDocument();
|
||||
expect(screen.getByText('Kate')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Emily')).not.toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getAllByRole('cell')).toHaveLength(3);
|
||||
expect(screen.getByText('321')).toBeInTheDocument();
|
||||
expect(screen.getByText('10')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Emily')).not.toBeInTheDocument();
|
||||
|
||||
const page1 = screen.getByRole('listitem', { name: '1' });
|
||||
await userEvent.click(page1);
|
||||
await userEvent.click(screen.getByTitle('Previous Page'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByRole('cell')).toHaveLength(3);
|
||||
expect(screen.getByText('123')).toBeInTheDocument();
|
||||
expect(screen.getByText('27')).toBeInTheDocument();
|
||||
expect(screen.getByText('Emily')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Kate')).not.toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getAllByRole('cell')).toHaveLength(3);
|
||||
expect(screen.getByText('123')).toBeInTheDocument();
|
||||
expect(screen.getByText('27')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Kate')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should sort by age', async () => {
|
||||
@@ -240,8 +246,7 @@ test('should handle server-side pagination', async () => {
|
||||
render(<TableView {...serverPaginationProps} />);
|
||||
|
||||
// Click next page
|
||||
const page2 = screen.getByRole('listitem', { name: '2' });
|
||||
await userEvent.click(page2);
|
||||
await userEvent.click(screen.getByTitle('Next Page'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onServerPagination).toHaveBeenCalledWith({
|
||||
@@ -301,9 +306,7 @@ test('should scroll to top when scrollTopOnPagination is true', async () => {
|
||||
};
|
||||
render(<TableView {...scrollProps} />);
|
||||
|
||||
// Click next page
|
||||
const page2 = screen.getByRole('listitem', { name: '2' });
|
||||
await userEvent.click(page2);
|
||||
await userEvent.click(screen.getByTitle('Next Page'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(scrollToSpy).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' });
|
||||
@@ -324,9 +327,7 @@ test('should NOT scroll to top when scrollTopOnPagination is false', async () =>
|
||||
};
|
||||
render(<TableView {...scrollProps} />);
|
||||
|
||||
// Click next page
|
||||
const page2 = screen.getByRole('listitem', { name: '2' });
|
||||
await userEvent.click(page2);
|
||||
await userEvent.click(screen.getByTitle('Next Page'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('321')).toBeInTheDocument();
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { memo, useEffect, useRef, useMemo, useCallback } from 'react';
|
||||
import { memo, useEffect, useRef, useMemo, useCallback, useState } from 'react';
|
||||
import { isEqual } from 'lodash';
|
||||
import { styled } from '@apache-superset/core/theme';
|
||||
import { useFilters, usePagination, useSortBy, useTable } from 'react-table';
|
||||
import { useFilters, useSortBy, useTable } from 'react-table';
|
||||
import { Empty } from '@superset-ui/core/components';
|
||||
import TableCollection from '@superset-ui/core/components/TableCollection';
|
||||
import { TableSize } from '@superset-ui/core/components/Table';
|
||||
@@ -117,43 +117,45 @@ const RawTableView = ({
|
||||
...props
|
||||
}: TableViewProps) => {
|
||||
const tableRef = useRef<HTMLTableElement>(null);
|
||||
const effectivePageSize = initialPageSize ?? DEFAULT_PAGE_SIZE;
|
||||
const [pageIndex, setPageIndex] = useState(initialPageIndex ?? 0);
|
||||
|
||||
const initialState = useMemo(
|
||||
() => ({
|
||||
pageSize: initialPageSize ?? DEFAULT_PAGE_SIZE,
|
||||
pageIndex: initialPageIndex ?? 0,
|
||||
pageSize: effectivePageSize,
|
||||
pageIndex: 0,
|
||||
sortBy: initialSortBy,
|
||||
}),
|
||||
[initialPageSize, initialPageIndex, initialSortBy],
|
||||
[effectivePageSize, initialSortBy],
|
||||
);
|
||||
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
page,
|
||||
rows,
|
||||
prepareRow,
|
||||
gotoPage,
|
||||
setSortBy,
|
||||
state: { pageIndex, sortBy },
|
||||
state: { sortBy },
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
initialState,
|
||||
manualPagination: serverPagination,
|
||||
manualPagination: true,
|
||||
manualSortBy: serverPagination,
|
||||
pageCount: serverPagination
|
||||
? Math.ceil(totalCount / initialState.pageSize)
|
||||
: undefined,
|
||||
autoResetSortBy: false,
|
||||
},
|
||||
useFilters,
|
||||
useSortBy,
|
||||
...(withPagination ? [usePagination] : []),
|
||||
);
|
||||
|
||||
const content = useMemo(() => {
|
||||
if (!withPagination || serverPagination) return rows;
|
||||
const start = pageIndex * effectivePageSize;
|
||||
return rows.slice(start, start + effectivePageSize);
|
||||
}, [withPagination, serverPagination, rows, pageIndex, effectivePageSize]);
|
||||
|
||||
const EmptyWrapperComponent = useMemo(() => {
|
||||
switch (emptyWrapperType) {
|
||||
case EmptyWrapperType.Small:
|
||||
@@ -164,11 +166,6 @@ const RawTableView = ({
|
||||
}
|
||||
}, [emptyWrapperType]);
|
||||
|
||||
const content = useMemo(
|
||||
() => (withPagination ? page : rows),
|
||||
[withPagination, page, rows],
|
||||
);
|
||||
|
||||
const isEmpty = useMemo(
|
||||
() => !loading && content.length === 0,
|
||||
[loading, content.length],
|
||||
@@ -192,10 +189,9 @@ const RawTableView = ({
|
||||
const handlePageChange = useCallback(
|
||||
(p: number) => {
|
||||
if (scrollTopOnPagination) handleScrollToTop();
|
||||
|
||||
gotoPage(p);
|
||||
setPageIndex(p);
|
||||
},
|
||||
[scrollTopOnPagination, handleScrollToTop, gotoPage],
|
||||
[scrollTopOnPagination, handleScrollToTop],
|
||||
);
|
||||
|
||||
const paginationProps = useMemo(() => {
|
||||
@@ -211,7 +207,7 @@ const RawTableView = ({
|
||||
if (serverPagination) {
|
||||
return {
|
||||
pageIndex,
|
||||
pageSize: initialPageSize ?? DEFAULT_PAGE_SIZE,
|
||||
pageSize: effectivePageSize,
|
||||
totalCount,
|
||||
onPageChange: handlePageChange,
|
||||
};
|
||||
@@ -219,7 +215,7 @@ const RawTableView = ({
|
||||
|
||||
return {
|
||||
pageIndex,
|
||||
pageSize: initialPageSize ?? DEFAULT_PAGE_SIZE,
|
||||
pageSize: effectivePageSize,
|
||||
totalCount: data.length,
|
||||
onPageChange: handlePageChange,
|
||||
};
|
||||
@@ -227,28 +223,28 @@ const RawTableView = ({
|
||||
withPagination,
|
||||
serverPagination,
|
||||
pageIndex,
|
||||
initialPageSize,
|
||||
effectivePageSize,
|
||||
totalCount,
|
||||
data.length,
|
||||
handlePageChange,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (serverPagination && pageIndex !== initialState.pageIndex) {
|
||||
if (serverPagination && pageIndex !== (initialPageIndex ?? 0)) {
|
||||
onServerPagination({
|
||||
pageIndex,
|
||||
});
|
||||
}
|
||||
}, [initialState.pageIndex, onServerPagination, pageIndex, serverPagination]);
|
||||
}, [initialPageIndex, onServerPagination, pageIndex, serverPagination]);
|
||||
|
||||
useEffect(() => {
|
||||
if (serverPagination && !isEqual(sortBy, initialState.sortBy)) {
|
||||
if (serverPagination && !isEqual(sortBy, initialSortBy)) {
|
||||
onServerPagination({
|
||||
pageIndex: 0,
|
||||
sortBy,
|
||||
});
|
||||
}
|
||||
}, [initialState.sortBy, onServerPagination, serverPagination, sortBy]);
|
||||
}, [initialSortBy, onServerPagination, serverPagination, sortBy]);
|
||||
|
||||
return (
|
||||
<TableViewStyles {...props} ref={tableRef}>
|
||||
|
||||
@@ -97,8 +97,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, act } from '@testing-library/react';
|
||||
import { useElementOnScreen } from './useElementOnScreen';
|
||||
|
||||
const observeMock = jest.fn();
|
||||
@@ -46,10 +46,9 @@ test('should return isSticky as true when intersectionRatio < 1', async () => {
|
||||
useElementOnScreen({ rootMargin: '-50px 0px 0px 0px' }),
|
||||
);
|
||||
const callback = IntersectionObserverMock.mock.calls[0][0];
|
||||
const callBack = callback([{ isIntersecting: true, intersectionRatio: 0.5 }]);
|
||||
const observer = new IntersectionObserverMock(callBack, {});
|
||||
const newDiv = document.createElement('div');
|
||||
observer.observe(newDiv);
|
||||
act(() => {
|
||||
callback([{ isIntersecting: true, intersectionRatio: 0.5 }]);
|
||||
});
|
||||
expect(hook.result.current[1]).toEqual(true);
|
||||
});
|
||||
|
||||
@@ -58,10 +57,9 @@ test('should return isSticky as false when intersectionRatio >= 1', async () =>
|
||||
useElementOnScreen({ rootMargin: '-50px 0px 0px 0px' }),
|
||||
);
|
||||
const callback = IntersectionObserverMock.mock.calls[0][0];
|
||||
const callBack = callback([{ isIntersecting: true, intersectionRatio: 1 }]);
|
||||
const observer = new IntersectionObserverMock(callBack, {});
|
||||
const newDiv = document.createElement('div');
|
||||
observer.observe(newDiv);
|
||||
act(() => {
|
||||
callback([{ isIntersecting: true, intersectionRatio: 1 }]);
|
||||
});
|
||||
expect(hook.result.current[1]).toEqual(false);
|
||||
});
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -249,7 +249,8 @@ 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<{ children?: ReactNode }>;
|
||||
|
||||
'welcome.message': ComponentType;
|
||||
'welcome.banner': ComponentType;
|
||||
'welcome.main.replacement': ComponentType;
|
||||
|
||||
@@ -143,7 +143,7 @@ describe('SuperChart', () => {
|
||||
);
|
||||
|
||||
expect(await screen.findByText('Custom Fallback!')).toBeInTheDocument();
|
||||
expect(CustomFallbackComponent).toHaveBeenCalledTimes(1);
|
||||
expect(CustomFallbackComponent).toHaveBeenCalled();
|
||||
});
|
||||
test('call onErrorBoundary', async () => {
|
||||
expectedErrors = 1;
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -34,6 +34,6 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -36,6 +36,6 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -39,6 +39,6 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,6 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"dayjs": "^1.11.19",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,14 +39,13 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
"@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": "*",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -247,7 +247,7 @@ const AgGridDataTable: FunctionComponent<AgGridTableProps> = memo(
|
||||
[serverPagination, debouncedSearch, searchId],
|
||||
);
|
||||
|
||||
const handleColSort = (colId: string, sortDir: string) => {
|
||||
const handleColSort = (colId: string, sortDir: string | null) => {
|
||||
const isSortable = shouldSort({
|
||||
colId,
|
||||
sortDir,
|
||||
@@ -301,10 +301,12 @@ const AgGridDataTable: FunctionComponent<AgGridTableProps> = memo(
|
||||
};
|
||||
|
||||
const handleColumnHeaderClick = useCallback(
|
||||
params => {
|
||||
(params: { column?: { colId?: string; sort?: string | null } }) => {
|
||||
const colId = params?.column?.colId;
|
||||
const sortDir = params?.column?.sort;
|
||||
handleColSort(colId, sortDir);
|
||||
if (colId && sortDir !== undefined) {
|
||||
handleColSort(colId, sortDir);
|
||||
}
|
||||
},
|
||||
[serverPagination, gridInitialState, percentMetrics, onSortChange],
|
||||
);
|
||||
|
||||
@@ -147,7 +147,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
||||
]);
|
||||
|
||||
const handleColumnStateChange = useCallback(
|
||||
agGridState => {
|
||||
(agGridState: Record<string, unknown>) => {
|
||||
if (onChartStateChange) {
|
||||
onChartStateChange(agGridState);
|
||||
}
|
||||
|
||||
@@ -70,5 +70,9 @@ export const TextCellRenderer = (params: CellRendererProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
return <div>{valueFormatted ?? value}</div>;
|
||||
return (
|
||||
<div>
|
||||
{valueFormatted ?? (value instanceof Date ? value.toISOString() : value)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ export const shouldSort = ({
|
||||
gridInitialState,
|
||||
}: {
|
||||
colId: string;
|
||||
sortDir: string;
|
||||
sortDir: string | null;
|
||||
percentMetrics: string[];
|
||||
serverPagination: boolean;
|
||||
gridInitialState: GridState;
|
||||
|
||||
@@ -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 { GenericDataType } from '@apache-superset/core/common';
|
||||
import {
|
||||
supersetTheme,
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"geostyler-wfs-parser": "^3.0.1",
|
||||
"ol": "^10.8.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, Root } from 'react-dom/client';
|
||||
import { SupersetTheme } from '@apache-superset/core/theme';
|
||||
import { ChartConfig, ChartLayerOptions, ChartSizeValues } from '../types';
|
||||
import { createChartComponent } from '../util/chartUtil';
|
||||
@@ -31,7 +31,14 @@ import Loader from '../images/loading.gif';
|
||||
* Custom OpenLayers layer that displays charts on given locations.
|
||||
*/
|
||||
export class ChartLayer extends Layer {
|
||||
charts: any[] = [];
|
||||
charts: {
|
||||
htmlElement: HTMLDivElement;
|
||||
root: Root;
|
||||
coordinate: number[];
|
||||
width: number;
|
||||
height: number;
|
||||
feature: any;
|
||||
}[] = [];
|
||||
|
||||
chartConfigs: ChartConfig = {
|
||||
type: 'FeatureCollection',
|
||||
@@ -166,7 +173,7 @@ export class ChartLayer extends Layer {
|
||||
*/
|
||||
removeAllChartElements() {
|
||||
this.charts.forEach(chart => {
|
||||
ReactDOM.unmountComponentAtNode(chart.htmlElement);
|
||||
chart.root.unmount();
|
||||
chart.htmlElement.remove();
|
||||
});
|
||||
this.charts = [];
|
||||
@@ -191,10 +198,12 @@ export class ChartLayer extends Layer {
|
||||
this.theme,
|
||||
this.locale,
|
||||
);
|
||||
ReactDOM.render(chartComponent, container);
|
||||
const root = createRoot(container);
|
||||
root.render(chartComponent);
|
||||
|
||||
return {
|
||||
htmlElement: container,
|
||||
root,
|
||||
coordinate: getProjectedCoordinateFromPointGeoJson(feature.geometry),
|
||||
width: chartWidth,
|
||||
height: chartHeight,
|
||||
@@ -227,7 +236,7 @@ export class ChartLayer extends Layer {
|
||||
this.theme,
|
||||
this.locale,
|
||||
);
|
||||
ReactDOM.render(chartComponent, chart.htmlElement);
|
||||
chart.root.render(chartComponent);
|
||||
|
||||
return {
|
||||
...chart,
|
||||
|
||||
@@ -41,6 +41,11 @@ describe('ChartLayer', () => {
|
||||
chartLayer.charts = [
|
||||
{
|
||||
htmlElement: document.createElement('div'),
|
||||
root: { render: jest.fn(), unmount: jest.fn() } as any,
|
||||
coordinate: [0, 0],
|
||||
width: 100,
|
||||
height: 100,
|
||||
feature: {},
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"dayjs": "^1.11.19",
|
||||
"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: string, seriesIndex: number) => {
|
||||
const selected: string[] = Object.values(selectedValues || {});
|
||||
let values: string[];
|
||||
if (selected.includes(seriesName)) {
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
import { useCallback } from 'react';
|
||||
import Echart from '../components/Echart';
|
||||
import { NULL_STRING } from '../constants';
|
||||
import { EventHandlers } from '../types';
|
||||
import { EventHandlers, TreePathInfo } from '../types';
|
||||
import { extractTreePathInfo } from './constants';
|
||||
import { TreemapTransformedProps } from './types';
|
||||
import { formatSeriesName } from '../utils/series';
|
||||
@@ -46,7 +46,7 @@ export default function EchartsTreemap({
|
||||
coltypeMapping,
|
||||
}: TreemapTransformedProps) {
|
||||
const getCrossFilterDataMask = useCallback(
|
||||
(data, treePathInfo) => {
|
||||
(data: Record<string, unknown>, treePathInfo: TreePathInfo[]) => {
|
||||
if (data?.children) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -96,7 +96,7 @@ export default function EchartsTreemap({
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(data, treePathInfo) => {
|
||||
(data: Record<string, unknown>, treePathInfo: TreePathInfo[]) => {
|
||||
if (!emitCrossFilters || groupby.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -39,9 +39,9 @@
|
||||
"handlebars": "^4.7.8",
|
||||
"lodash": "^4.18.1",
|
||||
"dayjs": "^1.11.19",
|
||||
"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",
|
||||
|
||||
@@ -71,7 +71,7 @@ ${helperDescriptions
|
||||
<div>
|
||||
<ControlHeader>
|
||||
<div>
|
||||
{props.label}
|
||||
{typeof props.label === 'function' ? null : props.label}
|
||||
<InfoTooltip
|
||||
iconStyle={{ marginLeft: theme.sizeUnit }}
|
||||
tooltip={<SafeMarkdown source={helpersTooltipContent} />}
|
||||
|
||||
@@ -49,7 +49,7 @@ const StyleControl = (props: CustomControlConfig<StyleCustomControlProps>) => {
|
||||
<div>
|
||||
<ControlHeader>
|
||||
<div>
|
||||
{props.label}
|
||||
{typeof props.label === 'function' ? null : props.label}
|
||||
{htmlSanitization && (
|
||||
<InfoTooltip
|
||||
iconStyle={{ marginLeft: theme.sizeUnit }}
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
"@superset-ui/core": "*",
|
||||
"lodash": "^4.18.1",
|
||||
"prop-types": "*",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.29.0",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^17.0.2 || ^19.0.0",
|
||||
"react-dom": "^17.0.2 || ^19.0.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -39,15 +39,14 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
"@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": "*",
|
||||
"match-sorter": "^8.2.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"@superset-ui/core": "*",
|
||||
"@types/lodash": "*",
|
||||
"@types/react": "*",
|
||||
"react": "^17.0.2"
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/d3-cloud": "^1.2.9"
|
||||
|
||||
@@ -67,8 +67,8 @@
|
||||
"@superset-ui/core": "*",
|
||||
"dayjs": "^1.11.19",
|
||||
"mapbox-gl": ">=1.0.0",
|
||||
"react": "^17.0.2 || ^19.0.0",
|
||||
"react-dom": "^17.0.2 || ^19.0.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"mapbox-gl": {
|
||||
|
||||
@@ -139,7 +139,8 @@ const CategoricalDeckGLContainer = (props: CategoricalDeckGLContainerProps) => {
|
||||
const setTooltip = useCallback((tooltip: TooltipProps['tooltip']) => {
|
||||
const { current } = containerRef;
|
||||
if (current) {
|
||||
current.setTooltip(tooltip);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(current as any).setTooltip(tooltip);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -194,5 +194,5 @@ export const DeckGLContainerStyledWrapper = styled(DeckGLContainer)`
|
||||
`;
|
||||
|
||||
export type DeckGLContainerHandle = typeof DeckGLContainer & {
|
||||
setTooltip: (tooltip: ReactNode) => void;
|
||||
setTooltip: (tooltip: TooltipProps['tooltip']) => void;
|
||||
};
|
||||
|
||||
@@ -97,10 +97,10 @@ describe('getAggFunc', () => {
|
||||
});
|
||||
|
||||
describe('commonLayerProps', () => {
|
||||
const mockSetTooltip = jest.fn();
|
||||
const mockSetTooltip = jest.fn() as any;
|
||||
const mockSetTooltipContent = jest.fn(
|
||||
() => (o: JsonObject) => `Tooltip for ${o}`,
|
||||
);
|
||||
) as any;
|
||||
const mockOnSelect = jest.fn();
|
||||
|
||||
test('returns correct props when js_tooltip is provided', () => {
|
||||
|
||||
@@ -97,6 +97,7 @@ export function createWrapper(options?: Options) {
|
||||
}
|
||||
|
||||
if (useDnd) {
|
||||
// @ts-expect-error react-dnd types not updated for React 18
|
||||
result = <DndProvider backend={HTML5Backend}>{result}</DndProvider>;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ const ContentWrapper = styled.div`
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
const AppLayout: React.FC = ({ children }) => {
|
||||
const AppLayout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||
const queryEditorId = useSelector<SqlLabRootState, string>(
|
||||
({ sqlLab: { tabHistory } }) => tabHistory.slice(-1)[0],
|
||||
);
|
||||
|
||||
@@ -19,16 +19,14 @@
|
||||
import { isValidElement } from 'react';
|
||||
import { render } from 'spec/helpers/testing-library';
|
||||
import ColumnElement from 'src/SqlLab/components/ColumnElement';
|
||||
import { mockedActions, table } from 'src/SqlLab/fixtures';
|
||||
import { table } from 'src/SqlLab/fixtures';
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('ColumnElement', () => {
|
||||
const mockedProps = {
|
||||
actions: mockedActions,
|
||||
column: table.columns[0],
|
||||
};
|
||||
test('is valid with props', () => {
|
||||
expect(isValidElement(<ColumnElement {...mockedProps} />)).toBe(true);
|
||||
expect(isValidElement(<ColumnElement column={table.columns[0]} />)).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
test('renders a proper primary key', () => {
|
||||
const { container } = render(<ColumnElement column={table.columns[0]} />);
|
||||
|
||||
@@ -116,19 +116,31 @@ describe('EditorWrapper', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('skips rerendering for updating cursor position', () => {
|
||||
test('skips rerendering for updating cursor position', async () => {
|
||||
const store = createStore(initialState, reducerIndex);
|
||||
setup(defaultQueryEditor, store);
|
||||
|
||||
expect(MockEditorHost).toHaveBeenCalled();
|
||||
const renderCount = MockEditorHost.mock.calls.length;
|
||||
await waitFor(() => expect(MockEditorHost).toHaveBeenCalled());
|
||||
const renderCountBeforeCursor = MockEditorHost.mock.calls.length;
|
||||
const updatedCursorPosition = { row: 1, column: 9 };
|
||||
store.dispatch(
|
||||
queryEditorSetCursorPosition(defaultQueryEditor, updatedCursorPosition),
|
||||
act(() => {
|
||||
store.dispatch(
|
||||
queryEditorSetCursorPosition(defaultQueryEditor, updatedCursorPosition),
|
||||
);
|
||||
});
|
||||
// Cursor position change should NOT trigger a re-render
|
||||
expect(MockEditorHost).toHaveBeenCalledTimes(renderCountBeforeCursor);
|
||||
|
||||
const renderCountBeforeDb = MockEditorHost.mock.calls.length;
|
||||
act(() => {
|
||||
store.dispatch(queryEditorSetDb(defaultQueryEditor, 2));
|
||||
});
|
||||
// DB change SHOULD trigger a re-render
|
||||
await waitFor(() =>
|
||||
expect(MockEditorHost.mock.calls.length).toBeGreaterThan(
|
||||
renderCountBeforeDb,
|
||||
),
|
||||
);
|
||||
expect(MockEditorHost).toHaveBeenCalledTimes(renderCount);
|
||||
store.dispatch(queryEditorSetDb(defaultQueryEditor, 2));
|
||||
expect(MockEditorHost).toHaveBeenCalledTimes(renderCount + 1);
|
||||
});
|
||||
|
||||
test('clears selectedText when selection becomes empty', async () => {
|
||||
|
||||
@@ -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,
|
||||
@@ -121,7 +121,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.callHistory.calls(queryValidationApiRoute)).toHaveLength(
|
||||
1,
|
||||
@@ -143,7 +143,7 @@ test('returns server error description', async () => {
|
||||
fetchMock.post(queryValidationApiRoute, {
|
||||
throws: new Error(errorMessage),
|
||||
});
|
||||
const { result, waitFor } = initialize(true);
|
||||
const { result } = initialize(true);
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(result.current.data).toEqual([
|
||||
@@ -164,7 +164,7 @@ test('returns session expire description when CSRF token expired', async () => {
|
||||
fetchMock.post(queryValidationApiRoute, {
|
||||
throws: new Error(errorMessage),
|
||||
});
|
||||
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',
|
||||
@@ -241,7 +241,7 @@ test('returns column keywords among selected tables', async () => {
|
||||
);
|
||||
});
|
||||
|
||||
const { result, waitFor } = renderHook(
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useKeywords({
|
||||
queryEditorId: expectQueryEditorId,
|
||||
@@ -302,7 +302,7 @@ test('returns long keywords with detail', 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 '@apache-superset/core/translation';
|
||||
import { styled, css } from '@apache-superset/core/theme';
|
||||
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={
|
||||
|
||||
@@ -39,7 +39,9 @@ import getBootstrapData from 'src/utils/getBootstrapData';
|
||||
|
||||
const SQL_LAB_URL = '/sqllab';
|
||||
|
||||
const PopEditorTab: React.FC = ({ children }) => {
|
||||
const PopEditorTab: React.FC<{ children?: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [queryEditorId, setQueryEditorId] = useState<string>();
|
||||
const { requestedQuery } = useLocationState();
|
||||
|
||||
@@ -50,14 +50,23 @@ import { StaticPosition, StyledTooltip, ModalResultSetWrapper } from './styles';
|
||||
|
||||
interface QueryTableQuery extends Omit<
|
||||
QueryResponse,
|
||||
'state' | 'sql' | 'progress' | 'results' | 'duration' | 'started'
|
||||
| 'state'
|
||||
| 'sql'
|
||||
| 'progress'
|
||||
| 'results'
|
||||
| 'duration'
|
||||
| 'started'
|
||||
| 'user'
|
||||
| 'db'
|
||||
> {
|
||||
state?: Record<string, any>;
|
||||
sql?: Record<string, any>;
|
||||
progress?: Record<string, any>;
|
||||
results?: Record<string, any>;
|
||||
state?: ReactNode;
|
||||
sql?: ReactNode;
|
||||
progress?: ReactNode;
|
||||
results?: ReactNode;
|
||||
duration?: ReactNode;
|
||||
started?: ReactNode;
|
||||
user?: ReactNode;
|
||||
db?: ReactNode;
|
||||
}
|
||||
|
||||
interface QueryTableProps {
|
||||
@@ -249,7 +258,7 @@ const QueryTable = ({
|
||||
|
||||
return queries
|
||||
.map(query => {
|
||||
const { state, sql, progress, ...rest } = query;
|
||||
const { state, sql, progress, results: _results, ...rest } = query;
|
||||
const q = rest as QueryTableQuery;
|
||||
|
||||
const status = statusAttributes[state] || statusAttributes.error;
|
||||
@@ -265,7 +274,7 @@ const QueryTable = ({
|
||||
buttonStyle="link"
|
||||
onClick={() => onUserClicked(q.userId)}
|
||||
>
|
||||
{q.user}
|
||||
{q.user as ReactNode}
|
||||
</Button>
|
||||
);
|
||||
q.db = (
|
||||
@@ -274,7 +283,7 @@ const QueryTable = ({
|
||||
buttonStyle="link"
|
||||
onClick={() => onDbClicked(q.dbId)}
|
||||
>
|
||||
{q.db}
|
||||
{q.db as ReactNode}
|
||||
</Button>
|
||||
);
|
||||
q.started = (
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useMemo, FC, ReactElement } from 'react';
|
||||
import { useMemo, FC, ReactElement, type ReactNode } from 'react';
|
||||
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { styled, useTheme, SupersetTheme } from '@apache-superset/core/theme';
|
||||
|
||||
import { Button, DropdownButton } from '@superset-ui/core/components';
|
||||
import { IconType, Icons } from '@superset-ui/core/components/Icons';
|
||||
import { Icons } from '@superset-ui/core/components/Icons';
|
||||
import { detectOS } from 'src/utils/common';
|
||||
import { QueryButtonProps } from 'src/SqlLab/types';
|
||||
import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
|
||||
@@ -45,9 +45,9 @@ const buildTextAndIcon = (
|
||||
shouldShowStopButton: boolean,
|
||||
selectedText: string | undefined,
|
||||
theme: SupersetTheme,
|
||||
): { text: string; icon?: IconType } => {
|
||||
): { text: string; icon?: ReactNode } => {
|
||||
let text = t('Run');
|
||||
let icon: IconType | undefined = <Icons.CaretRightOutlined />;
|
||||
let icon: ReactNode = <Icons.CaretRightOutlined />;
|
||||
if (selectedText) {
|
||||
text = t('Run selection');
|
||||
icon = <Icons.StepForwardOutlined />;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import * as reactRedux from 'react-redux';
|
||||
import { act } from 'react';
|
||||
import {
|
||||
cleanup,
|
||||
fireEvent,
|
||||
@@ -136,7 +137,7 @@ describe('SaveDatasetModal', () => {
|
||||
const overwriteRadioBtn = screen.getByRole('radio', {
|
||||
name: /overwrite existing/i,
|
||||
});
|
||||
userEvent.click(overwriteRadioBtn);
|
||||
await userEvent.click(overwriteRadioBtn);
|
||||
|
||||
// Overwrite confirmation button should be disabled at this point
|
||||
const overwriteConfirmationBtn = screen.getByRole('button', {
|
||||
@@ -146,15 +147,21 @@ describe('SaveDatasetModal', () => {
|
||||
|
||||
// Click the overwrite select component
|
||||
const select = screen.getByRole('combobox', { name: /existing dataset/i })!;
|
||||
userEvent.click(select);
|
||||
await userEvent.click(select);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByText('Loading...')).not.toBeVisible(),
|
||||
);
|
||||
// Advance timers to flush debounced fetches in AsyncSelect
|
||||
await act(async () => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const loading = screen.queryByText('Loading...');
|
||||
expect(loading === null || !loading.checkVisibility()).toBe(true);
|
||||
});
|
||||
|
||||
// Select the first "existing dataset" from the listbox
|
||||
const option = screen.getAllByText('coolest table 0')[1];
|
||||
userEvent.click(option);
|
||||
await userEvent.click(option);
|
||||
|
||||
// Overwrite button should now be enabled
|
||||
expect(overwriteConfirmationBtn).toBeEnabled();
|
||||
@@ -168,25 +175,31 @@ describe('SaveDatasetModal', () => {
|
||||
const overwriteRadioBtn = screen.getByRole('radio', {
|
||||
name: /overwrite existing/i,
|
||||
});
|
||||
userEvent.click(overwriteRadioBtn);
|
||||
await userEvent.click(overwriteRadioBtn);
|
||||
|
||||
// Click the overwrite select component
|
||||
const select = screen.getByRole('combobox', { name: /existing dataset/i });
|
||||
userEvent.click(select);
|
||||
await userEvent.click(select);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByText('Loading...')).not.toBeVisible(),
|
||||
);
|
||||
// Advance timers to flush debounced fetches in AsyncSelect
|
||||
await act(async () => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const loading = screen.queryByText('Loading...');
|
||||
expect(loading === null || !loading.checkVisibility()).toBe(true);
|
||||
});
|
||||
|
||||
// Select the first "existing dataset" from the listbox
|
||||
const option = screen.getAllByText('coolest table 0')[1];
|
||||
userEvent.click(option);
|
||||
await userEvent.click(option);
|
||||
|
||||
// Click the overwrite button to access the confirmation screen
|
||||
const overwriteConfirmationBtn = screen.getByRole('button', {
|
||||
name: /overwrite/i,
|
||||
});
|
||||
userEvent.click(overwriteConfirmationBtn);
|
||||
await userEvent.click(overwriteConfirmationBtn);
|
||||
|
||||
// Overwrite screen text
|
||||
expect(screen.getByText(/save or overwrite dataset/i)).toBeInTheDocument();
|
||||
|
||||
@@ -140,7 +140,7 @@ const SouthPane = ({
|
||||
logAction(LOG_ACTIONS_SQLLAB_SWITCH_SOUTH_PANE_TAB, { tab: id });
|
||||
};
|
||||
const removeTable = useCallback(
|
||||
(key, action) => {
|
||||
(key: string, action: string) => {
|
||||
if (action === 'remove') {
|
||||
const table = pinnedTables.find(
|
||||
({ dbId, catalog, schema, name }) =>
|
||||
|
||||
@@ -292,7 +292,10 @@ const SqlEditor: FC<Props> = ({
|
||||
const SqlFormExtension = extensionsRegistry.get('sqleditor.extension.form');
|
||||
|
||||
const startQuery = useCallback(
|
||||
(ctasArg = false, ctas_method = CtasEnum.Table) => {
|
||||
(
|
||||
ctasArg = false,
|
||||
ctas_method: (typeof CtasEnum)[keyof typeof CtasEnum] = CtasEnum.Table,
|
||||
) => {
|
||||
if (!database) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
import configureStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { createWrapper } from 'spec/helpers/testing-library';
|
||||
import { initialState, defaultQueryEditor } from 'src/SqlLab/fixtures';
|
||||
import * as localStorageHelpers from 'src/utils/localStorageHelpers';
|
||||
|
||||
@@ -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 { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { SupersetClient } from '@superset-ui/core';
|
||||
import { useDeckLayerMetadata } from './useDeckLayerMetadata';
|
||||
|
||||
@@ -52,15 +52,14 @@ test('fetches layer metadata successfully', async () => {
|
||||
};
|
||||
mockSupersetClientGet.mockResolvedValue(mockResponse);
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook(() =>
|
||||
useDeckLayerMetadata([1, 2]),
|
||||
);
|
||||
const { result } = renderHook(() => useDeckLayerMetadata([1, 2]));
|
||||
|
||||
expect(result.current.isLoading).toBe(true);
|
||||
|
||||
await waitForNextUpdate();
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.layers).toEqual([
|
||||
{ sliceId: 1, name: 'Layer 1', type: 'deck_scatter' },
|
||||
{ sliceId: 2, name: 'Layer 2', type: 'deck_arc' },
|
||||
@@ -75,13 +74,12 @@ test('handles API error and returns fallback layers', async () => {
|
||||
const errorMessage = 'Network error';
|
||||
mockSupersetClientGet.mockRejectedValue(new Error(errorMessage));
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook(() =>
|
||||
useDeckLayerMetadata([1, 2, 3]),
|
||||
);
|
||||
const { result } = renderHook(() => useDeckLayerMetadata([1, 2, 3]));
|
||||
|
||||
await waitForNextUpdate();
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.error).toBe(errorMessage);
|
||||
expect(result.current.layers).toEqual([
|
||||
{ sliceId: 1, name: 'Layer 1', type: 'unknown' },
|
||||
@@ -93,13 +91,12 @@ test('handles API error and returns fallback layers', async () => {
|
||||
test('handles non-Error object rejection', async () => {
|
||||
mockSupersetClientGet.mockRejectedValue('String error');
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook(() =>
|
||||
useDeckLayerMetadata([1]),
|
||||
);
|
||||
const { result } = renderHook(() => useDeckLayerMetadata([1]));
|
||||
|
||||
await waitForNextUpdate();
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.error).toBe('Unknown error');
|
||||
expect(result.current.layers).toEqual([
|
||||
{ sliceId: 1, name: 'Layer 1', type: 'unknown' },
|
||||
@@ -125,22 +122,25 @@ test('refetches when sliceIds change', async () => {
|
||||
.mockResolvedValueOnce(mockResponse1)
|
||||
.mockResolvedValueOnce(mockResponse2);
|
||||
|
||||
const { result, rerender, waitForNextUpdate } = renderHook(
|
||||
const { result, rerender } = renderHook(
|
||||
({ ids }) => useDeckLayerMetadata(ids),
|
||||
{
|
||||
initialProps: { ids: [1] },
|
||||
},
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.layers).toHaveLength(1);
|
||||
expect(result.current.layers[0].sliceId).toBe(1);
|
||||
|
||||
rerender({ ids: [2, 3] });
|
||||
|
||||
await waitForNextUpdate();
|
||||
await waitFor(() => {
|
||||
expect(result.current.layers).toHaveLength(2);
|
||||
});
|
||||
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.layers).toHaveLength(2);
|
||||
@@ -157,13 +157,12 @@ test('handles empty result from API', async () => {
|
||||
};
|
||||
mockSupersetClientGet.mockResolvedValue(mockResponse);
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook(() =>
|
||||
useDeckLayerMetadata([1, 2]),
|
||||
);
|
||||
const { result } = renderHook(() => useDeckLayerMetadata([1, 2]));
|
||||
|
||||
await waitForNextUpdate();
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.layers).toEqual([]);
|
||||
expect(result.current.error).toBe(null);
|
||||
});
|
||||
@@ -176,16 +175,17 @@ test('clears isLoading when sliceIds transitions from non-empty to empty', async
|
||||
};
|
||||
mockSupersetClientGet.mockResolvedValue(mockResponse);
|
||||
|
||||
const { result, rerender, waitForNextUpdate } = renderHook(
|
||||
const { result, rerender } = renderHook(
|
||||
({ ids }) => useDeckLayerMetadata(ids),
|
||||
{
|
||||
initialProps: { ids: [1] },
|
||||
},
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.layers).toHaveLength(1);
|
||||
|
||||
act(() => {
|
||||
@@ -204,16 +204,16 @@ test('does not refetch when sliceIds array has same values', async () => {
|
||||
};
|
||||
mockSupersetClientGet.mockResolvedValue(mockResponse);
|
||||
|
||||
const { result, rerender, waitForNextUpdate } = renderHook(
|
||||
const { result, rerender } = renderHook(
|
||||
({ ids }) => useDeckLayerMetadata(ids),
|
||||
{
|
||||
initialProps: { ids: [1] },
|
||||
},
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
const callCount = mockSupersetClientGet.mock.calls.length;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { PureComponent } from 'react';
|
||||
import { ErrorInfo, PureComponent } from 'react';
|
||||
import { logging } from '@apache-superset/core/utils';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import {
|
||||
@@ -235,16 +235,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?.componentStack ?? 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, act } from '@testing-library/react';
|
||||
import mockState from 'spec/fixtures/mockState';
|
||||
import { sliceId } from 'spec/fixtures/mockChartQueries';
|
||||
import { noOp } from 'src/utils/common';
|
||||
@@ -95,7 +95,9 @@ beforeEach(() => {
|
||||
test('Context menu renders', () => {
|
||||
const result = setup();
|
||||
expect(screen.queryByTestId(CONTEXT_MENU_TEST_ID)).not.toBeInTheDocument();
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
act(() => {
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
});
|
||||
expect(screen.getByTestId(CONTEXT_MENU_TEST_ID)).toBeInTheDocument();
|
||||
expect(screen.getByText('Add cross-filter')).toBeInTheDocument();
|
||||
expect(screen.getByText('Drill to detail')).toBeInTheDocument();
|
||||
@@ -106,7 +108,9 @@ test('Context menu contains all displayed items only', () => {
|
||||
const result = setup({
|
||||
displayedItems: [ContextMenuItem.DrillToDetail, ContextMenuItem.DrillBy],
|
||||
});
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
act(() => {
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
});
|
||||
expect(screen.queryByText('Add cross-filter')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('Drill to detail')).toBeInTheDocument();
|
||||
expect(screen.getByText('Drill by')).toBeInTheDocument();
|
||||
@@ -122,7 +126,9 @@ test('Context menu shows "Drill by" with `can_drill`, `can_write` & `can_get_dri
|
||||
],
|
||||
},
|
||||
});
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
act(() => {
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
});
|
||||
expect(screen.getByText('Drill by')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -137,7 +143,9 @@ test('Context menu shows "Drill by" with `can_drill`, `can_get_drill_info` & `ca
|
||||
],
|
||||
},
|
||||
});
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
act(() => {
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
});
|
||||
expect(screen.getByText('Drill by')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -147,7 +155,9 @@ test('Context menu does not show "Drill by" with neither of required perms', ()
|
||||
Admin: [['invalid_permission', 'Dashboard']],
|
||||
},
|
||||
});
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
act(() => {
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
});
|
||||
expect(screen.queryByText('Drill by')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -157,7 +167,9 @@ test('Context menu does not show "Drill by" with just `can_dril` perm', () => {
|
||||
Admin: [['can_drill', 'Dashboard']],
|
||||
},
|
||||
});
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
act(() => {
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
});
|
||||
expect(screen.queryByText('Drill by')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -170,7 +182,9 @@ test('Context menu does not show "Drill by" with just `can_dril` & `can_write` p
|
||||
],
|
||||
},
|
||||
});
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
act(() => {
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
});
|
||||
expect(screen.queryByText('Drill by')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -184,7 +198,9 @@ test('Context menu does not show "Drill by" with just `can_drill`, `can_explore`
|
||||
],
|
||||
},
|
||||
});
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
act(() => {
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
});
|
||||
expect(screen.queryByText('Drill by')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -198,7 +214,9 @@ test('Context menu shows "Drill to detail" with `can_samples`, `can_explore` & `
|
||||
],
|
||||
},
|
||||
});
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
act(() => {
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
});
|
||||
expect(screen.getByText('Drill to detail')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -212,7 +230,9 @@ test('Context menu shows "Drill to detail" with `can_drill`, `can_samples` & `ca
|
||||
],
|
||||
},
|
||||
});
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
act(() => {
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
});
|
||||
expect(screen.getByText('Drill to detail')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -227,7 +247,9 @@ test('Context menu shows "Drill to detail" with `can_drill`, `can_get_drill_info
|
||||
],
|
||||
},
|
||||
});
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
act(() => {
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
});
|
||||
expect(screen.getByText('Drill to detail')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -237,7 +259,9 @@ test('Context menu does not show "Drill to detail" with neither of required perm
|
||||
Admin: [['invalid_permission', 'Dashboard']],
|
||||
},
|
||||
});
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
act(() => {
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
});
|
||||
expect(screen.queryByText('Drill to detail')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -247,7 +271,9 @@ test('Context menu does not show "Drill to detail" with just `can_drill` perm',
|
||||
Admin: [['can_drill', 'Dashboard']],
|
||||
},
|
||||
});
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
act(() => {
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
});
|
||||
expect(screen.queryByText('Drill to detail')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -260,7 +286,9 @@ test('Context menu does not show "Drill to detail" with just `can_drill` & `can_
|
||||
],
|
||||
},
|
||||
});
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
act(() => {
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
});
|
||||
expect(screen.queryByText('Drill to detail')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -273,7 +301,9 @@ test('Context menu does not show "Drill to detail" with `can_samples` & `can_exp
|
||||
],
|
||||
},
|
||||
});
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
act(() => {
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
});
|
||||
expect(screen.queryByText('Drill to detail')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -287,7 +317,9 @@ test('Context menu does not show "Drill to detail" with `can_drill`, `can_explor
|
||||
],
|
||||
},
|
||||
});
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
act(() => {
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
});
|
||||
expect(screen.queryByText('Drill to detail')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -303,7 +335,9 @@ test('Dataset drill info API call is made when user has drill permissions', asyn
|
||||
},
|
||||
});
|
||||
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
act(() => {
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
@@ -321,7 +355,9 @@ test('Dataset drill info API call is not made when user lacks drill permissions'
|
||||
},
|
||||
});
|
||||
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
act(() => {
|
||||
result.current.onContextMenu(0, 0, {});
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ export interface DrillBySubmenuProps {
|
||||
drillByConfig?: ContextMenuFilters['drillBy'];
|
||||
formData: BaseFormData & { [key: string]: any };
|
||||
onSelection?: (...args: any) => void;
|
||||
onClick?: (event: MouseEvent) => void;
|
||||
onClick?: (event: React.MouseEvent) => void;
|
||||
onCloseMenu?: () => void;
|
||||
openNewModal?: boolean;
|
||||
excludedColumns?: Column[];
|
||||
@@ -93,8 +93,8 @@ export const DrillBySubmenu = ({
|
||||
const showSearch = columns.length > SHOW_COLUMNS_SEARCH_THRESHOLD;
|
||||
|
||||
const handleSelection = useCallback(
|
||||
(event, column) => {
|
||||
onClick(event as MouseEvent);
|
||||
(event: React.MouseEvent, column: Column) => {
|
||||
onClick(event);
|
||||
onSelection(column, drillByConfig);
|
||||
if (openNewModal && onDrillBy && dataset) {
|
||||
onDrillBy(column, dataset);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
|
||||
@@ -74,7 +74,7 @@ export default function TableControls({
|
||||
);
|
||||
|
||||
const removeFilter = useCallback(
|
||||
colName => {
|
||||
(colName: string) => {
|
||||
const updatedFilterMap = { ...filterMap };
|
||||
delete updatedFilterMap[colName];
|
||||
setFilters(Object.values(updatedFilterMap));
|
||||
@@ -125,7 +125,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: BinaryQueryObjectFilterClause[], event: MouseEvent) => {
|
||||
onClick(event);
|
||||
onSelection();
|
||||
setFilters(filters);
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
act,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
@@ -120,11 +119,9 @@ describe('DatasourceModal', () => {
|
||||
onDatasourceSave as unknown as typeof mockedProps.onDatasourceSave,
|
||||
});
|
||||
const saveButton = screen.getByTestId('datasource-modal-save');
|
||||
await act(async () => {
|
||||
fireEvent.click(saveButton);
|
||||
const okButton = await screen.findByRole('button', { name: 'OK' });
|
||||
okButton.click();
|
||||
});
|
||||
fireEvent.click(saveButton);
|
||||
const okButton = await screen.findByRole('button', { name: 'OK' });
|
||||
fireEvent.click(okButton);
|
||||
await waitFor(() => {
|
||||
expect(onDatasourceSave).toHaveBeenCalled();
|
||||
});
|
||||
@@ -135,18 +132,14 @@ describe('DatasourceModal', () => {
|
||||
.spyOn(SupersetClient, 'put')
|
||||
.mockRejectedValue(new Error('Something went wrong'));
|
||||
|
||||
await act(async () => {
|
||||
const saveButton = screen.getByTestId('datasource-modal-save');
|
||||
fireEvent.click(saveButton);
|
||||
const okButton = await screen.findByRole('button', { name: 'OK' });
|
||||
okButton.click();
|
||||
});
|
||||
const saveButton = screen.getByTestId('datasource-modal-save');
|
||||
fireEvent.click(saveButton);
|
||||
const okButton = await screen.findByRole('button', { name: 'OK' });
|
||||
fireEvent.click(okButton);
|
||||
|
||||
await act(async () => {
|
||||
const errorElements = await screen.findAllByText('Error saving dataset');
|
||||
const errorDiv = errorElements.find(el => el.closest('div'));
|
||||
expect(errorDiv).toBeInTheDocument();
|
||||
});
|
||||
const errorElements = await screen.findAllByText('Error saving dataset');
|
||||
const errorDiv = errorElements.find(el => el.closest('div'));
|
||||
expect(errorDiv).toBeInTheDocument();
|
||||
putSpy.mockRestore();
|
||||
});
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import type { DragStartEvent } from '@dnd-kit/core';
|
||||
import { FlattenedTreeItem } from '../constants';
|
||||
import { FoldersEditorItemType } from '../../types';
|
||||
|
||||
@@ -741,7 +741,7 @@ function ColumnCollectionTable({
|
||||
{v}
|
||||
</StyledLabelWrapper>
|
||||
),
|
||||
type: d => (d ? <Label>{d}</Label> : null),
|
||||
type: d => (d ? <Label>{String(d)}</Label> : null),
|
||||
advanced_data_type: d => <Label>{d as string}</Label>,
|
||||
is_dttm: checkboxGenerator,
|
||||
filterable: checkboxGenerator,
|
||||
@@ -770,7 +770,7 @@ function ColumnCollectionTable({
|
||||
{v}
|
||||
</StyledLabelWrapper>
|
||||
),
|
||||
type: d => (d ? <Label>{d}</Label> : null),
|
||||
type: d => (d ? <Label>{String(d)}</Label> : null),
|
||||
is_dttm: checkboxGenerator,
|
||||
filterable: checkboxGenerator,
|
||||
groupby: checkboxGenerator,
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function Field<V>({
|
||||
errorMessage,
|
||||
}: FieldProps<V>) {
|
||||
const onControlChange = useCallback(
|
||||
newValue => {
|
||||
(newValue: V) => {
|
||||
onChange(fieldKey, newValue);
|
||||
},
|
||||
[onChange, fieldKey],
|
||||
|
||||
@@ -16,7 +16,14 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useContext, useEffect, useReducer, createContext, FC } from 'react';
|
||||
import {
|
||||
useContext,
|
||||
useEffect,
|
||||
useReducer,
|
||||
createContext,
|
||||
FC,
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
ChartMetadata,
|
||||
@@ -122,7 +129,9 @@ const sharedModules = {
|
||||
'@superset-ui/core': () => import('@superset-ui/core'),
|
||||
};
|
||||
|
||||
export const DynamicPluginProvider: FC = ({ children }) => {
|
||||
export const DynamicPluginProvider: FC<{ children?: ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [pluginState, dispatch] = useReducer(
|
||||
pluginContextReducer,
|
||||
dummyPluginContext,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useMemo, useCallback, memo } from 'react';
|
||||
import { useMemo, useCallback, useRef, memo } from 'react';
|
||||
import { GridSize } from 'src/components/GridTable/constants';
|
||||
import { GridTable } from 'src/components/GridTable';
|
||||
import { type ColDef } from 'src/components/GridTable/types';
|
||||
@@ -109,15 +109,15 @@ export const FilterableTable = ({
|
||||
[orderedColumnKeys, allowHTML, getCellContent],
|
||||
);
|
||||
|
||||
const keywordFilter = useCallback(
|
||||
node => {
|
||||
if (filterText && node.data) {
|
||||
return hasMatch(filterText, node.data);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[filterText],
|
||||
);
|
||||
const keyword = useRef<string | undefined>(filterText);
|
||||
keyword.current = filterText;
|
||||
|
||||
const keywordFilter = useCallback((node: { data: Datum }) => {
|
||||
if (keyword.current && node.data) {
|
||||
return hasMatch(keyword.current, node.data);
|
||||
}
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="filterable-table-container" data-test="table-container">
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -16,7 +16,13 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
type MouseEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { styled, useTheme } from '@apache-superset/core/theme';
|
||||
import type { Column, GridApi } from 'ag-grid-community';
|
||||
@@ -98,7 +104,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: MouseEvent) => {
|
||||
sortOption.current = (sortOption.current + 1) % SORT_DIRECTION.length;
|
||||
const sort = SORT_DIRECTION[sortOption.current];
|
||||
setSort(sort, event.shiftKey);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user