mirror of
https://github.com/apache/superset.git
synced 2026-06-10 18:19:28 +00:00
Compare commits
23 Commits
enxdev/cha
...
msyavuz/ch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2dedfc2139 | ||
|
|
ba87f127af | ||
|
|
838c4f1384 | ||
|
|
3b30328690 | ||
|
|
4c8269fe91 | ||
|
|
4cf0fbb05a | ||
|
|
beda35f63b | ||
|
|
471f01356b | ||
|
|
77d63e183d | ||
|
|
cbb84778dc | ||
|
|
660b83d8be | ||
|
|
f6bb9f3e7c | ||
|
|
5e64af51a8 | ||
|
|
cd01d1d433 | ||
|
|
84178bf4e3 | ||
|
|
daf78c9374 | ||
|
|
705653f3ba | ||
|
|
c6442adde1 | ||
|
|
bf4840dea1 | ||
|
|
f80000102b | ||
|
|
f67bd7eadf | ||
|
|
bcea2c2668 | ||
|
|
8cfbf24b81 |
@@ -106,7 +106,13 @@ module.exports = {
|
||||
[
|
||||
'babel-plugin-jsx-remove-data-test-id',
|
||||
{
|
||||
attributes: 'data-test',
|
||||
// The plugin matches attribute names exactly (no prefix match),
|
||||
// so each data-test* attribute must be listed explicitly.
|
||||
attributes: [
|
||||
'data-test',
|
||||
'data-test-drag-source-id',
|
||||
'data-test-drop-target-id',
|
||||
],
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
85
superset-frontend/package-lock.json
generated
85
superset-frontend/package-lock.json
generated
@@ -120,13 +120,13 @@
|
||||
"ol": "^10.9.0",
|
||||
"query-string": "9.4.0",
|
||||
"re-resizable": "^6.11.2",
|
||||
"react": "^18.2.0",
|
||||
"react": "^18.3.0",
|
||||
"react-arborist": "^3.8.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": "^18.2.0",
|
||||
"react-dom": "^18.3.0",
|
||||
"react-google-recaptcha": "^3.1.0",
|
||||
"react-intersection-observer": "^10.0.3",
|
||||
"react-json-tree": "^0.20.0",
|
||||
@@ -210,8 +210,8 @@
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@types/mousetrap": "^1.6.15",
|
||||
"@types/node": "^25.9.1",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"@types/react": "^18.3.0",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-loadable": "^5.5.11",
|
||||
"@types/react-redux": "^7.1.10",
|
||||
"@types/react-resizable": "^4.0.0",
|
||||
@@ -275,6 +275,7 @@
|
||||
"prettier": "3.8.3",
|
||||
"prettier-plugin-packagejson": "^3.0.2",
|
||||
"process": "^0.11.10",
|
||||
"react-dnd-test-backend": "^11.1.3",
|
||||
"react-refresh": "^0.18.0",
|
||||
"react-resizable": "^4.0.1",
|
||||
"redux-mock-store": "^1.5.4",
|
||||
@@ -40478,6 +40479,16 @@
|
||||
"dnd-core": "^11.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dnd-test-backend": {
|
||||
"version": "11.1.3",
|
||||
"resolved": "https://registry.npmjs.org/react-dnd-test-backend/-/react-dnd-test-backend-11.1.3.tgz",
|
||||
"integrity": "sha512-5qFm+NI2GdWIUfiYun0A8Gv0xjbq0NGOPS+f6z3x/3nTuliApjmqcM1lfTgePoz1FDG47ZofF58N8y96If62+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dnd-core": "^11.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-docgen": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-7.1.1.tgz",
|
||||
@@ -49284,8 +49295,8 @@
|
||||
"jed": "^1.1.1",
|
||||
"lodash": "^4.18.1",
|
||||
"nanoid": "^5.0.9",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0",
|
||||
"react-loadable": "^5.5.0",
|
||||
"tinycolor2": "*"
|
||||
}
|
||||
@@ -49311,9 +49322,9 @@
|
||||
"ace-builds": "^1.4.14",
|
||||
"brace": "^0.11.1",
|
||||
"memoize-one": "^5.1.1",
|
||||
"react": "^18.2.0",
|
||||
"react": "^18.3.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"packages/superset-ui-core": {
|
||||
@@ -49354,7 +49365,7 @@
|
||||
"react-js-cron": "^5.2.0",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-resize-detector": "^7.1.2",
|
||||
"react-syntax-highlighter": "^16.1.0",
|
||||
"react-syntax-highlighter": "^16.1.1",
|
||||
"react-ultimate-pagination": "^1.3.2",
|
||||
"regenerator-runtime": "^0.14.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
@@ -49398,8 +49409,8 @@
|
||||
"@types/tinycolor2": "*",
|
||||
"antd": "^5.26.0",
|
||||
"nanoid": "^5.0.9",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0",
|
||||
"react-loadable": "^5.5.0",
|
||||
"tinycolor2": "*"
|
||||
}
|
||||
@@ -49579,7 +49590,7 @@
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-calendar/node_modules/d3-array": {
|
||||
@@ -49640,7 +49651,7 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-country-map/node_modules/d3-array": {
|
||||
@@ -49668,7 +49679,7 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-horizon/node_modules/d3-array": {
|
||||
@@ -49702,7 +49713,7 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-parallel-coordinates": {
|
||||
@@ -49717,7 +49728,7 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-partition": {
|
||||
@@ -49735,8 +49746,8 @@
|
||||
"@superset-ui/core": "*",
|
||||
"@testing-library/jest-dom": "*",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-rose": {
|
||||
@@ -49753,7 +49764,7 @@
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-world-map": {
|
||||
@@ -49771,7 +49782,7 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-world-map/node_modules/d3-array": {
|
||||
@@ -49858,7 +49869,7 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"dayjs": "^1.11.21",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-preset-chart-nvd3/node_modules/dompurify": {
|
||||
@@ -49895,8 +49906,8 @@
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "*",
|
||||
"@types/react": "*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-ag-grid-table/node_modules/d3-array": {
|
||||
@@ -49940,8 +49951,8 @@
|
||||
"geostyler-wfs-parser": "^3.0.1",
|
||||
"ol": "^10.8.0",
|
||||
"polished": "*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-echarts": {
|
||||
@@ -49963,7 +49974,7 @@
|
||||
"dayjs": "^1.11.21",
|
||||
"echarts": "*",
|
||||
"memoize-one": "*",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-echarts/node_modules/acorn": {
|
||||
@@ -50021,9 +50032,9 @@
|
||||
"dayjs": "^1.11.21",
|
||||
"handlebars": "^4.7.8",
|
||||
"lodash": "^4.18.1",
|
||||
"react": "^18.2.0",
|
||||
"react": "^18.3.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-handlebars/node_modules/just-handlebars-helpers": {
|
||||
@@ -50054,8 +50065,8 @@
|
||||
"@superset-ui/core": "*",
|
||||
"lodash": "^4.18.1",
|
||||
"prop-types": "*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-point-cluster-map": {
|
||||
@@ -50073,8 +50084,8 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-point-cluster-map/node_modules/react-map-gl": {
|
||||
@@ -50127,8 +50138,8 @@
|
||||
"@testing-library/user-event": "*",
|
||||
"@types/react": "*",
|
||||
"match-sorter": "^8.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-table/node_modules/d3-array": {
|
||||
@@ -50167,7 +50178,7 @@
|
||||
"@superset-ui/core": "*",
|
||||
"@types/lodash": "*",
|
||||
"@types/react": "*",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-word-cloud/node_modules/@types/d3-scale": {
|
||||
@@ -50236,8 +50247,8 @@
|
||||
"@superset-ui/core": "*",
|
||||
"dayjs": "^1.11.21",
|
||||
"mapbox-gl": ">=1.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"mapbox-gl": {
|
||||
|
||||
@@ -203,13 +203,13 @@
|
||||
"ol": "^10.9.0",
|
||||
"query-string": "9.4.0",
|
||||
"re-resizable": "^6.11.2",
|
||||
"react": "^18.2.0",
|
||||
"react": "^18.3.0",
|
||||
"react-arborist": "^3.8.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": "^18.2.0",
|
||||
"react-dom": "^18.3.0",
|
||||
"react-google-recaptcha": "^3.1.0",
|
||||
"react-intersection-observer": "^10.0.3",
|
||||
"react-json-tree": "^0.20.0",
|
||||
@@ -293,8 +293,8 @@
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@types/mousetrap": "^1.6.15",
|
||||
"@types/node": "^25.9.1",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"@types/react": "^18.3.0",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-loadable": "^5.5.11",
|
||||
"@types/react-redux": "^7.1.10",
|
||||
"@types/react-resizable": "^4.0.0",
|
||||
@@ -358,6 +358,7 @@
|
||||
"prettier": "3.8.3",
|
||||
"prettier-plugin-packagejson": "^3.0.2",
|
||||
"process": "^0.11.10",
|
||||
"react-dnd-test-backend": "^11.1.3",
|
||||
"react-refresh": "^0.18.0",
|
||||
"react-resizable": "^4.0.1",
|
||||
"redux-mock-store": "^1.5.4",
|
||||
|
||||
@@ -97,8 +97,8 @@
|
||||
"@fontsource/ibm-plex-mono": "^5.2.7",
|
||||
"@fontsource/inter": "^5.2.6",
|
||||
"nanoid": "^5.0.9",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0",
|
||||
"react-loadable": "^5.5.0",
|
||||
"tinycolor2": "*",
|
||||
"lodash": "^4.18.1",
|
||||
|
||||
@@ -16,13 +16,26 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export {}; // ensure this file is treated as a module so top-level declarations don't leak into global scope
|
||||
|
||||
type LoggingModule = typeof import('./index');
|
||||
|
||||
const loadLogging = (): LoggingModule['logging'] => {
|
||||
let logging: LoggingModule['logging'] | undefined;
|
||||
jest.isolateModules(() => {
|
||||
({ logging } = jest.requireActual<LoggingModule>(
|
||||
'@apache-superset/core/utils',
|
||||
));
|
||||
});
|
||||
return logging!;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
test('should pipe to `console` methods', () => {
|
||||
const { logging } = require('@apache-superset/core/utils');
|
||||
const logging = loadLogging();
|
||||
|
||||
jest.spyOn(logging, 'debug').mockImplementation();
|
||||
jest.spyOn(logging, 'log').mockImplementation();
|
||||
@@ -50,20 +63,24 @@ test('should pipe to `console` methods', () => {
|
||||
});
|
||||
|
||||
test('should use noop functions when console unavailable', () => {
|
||||
const originalConsole = window.console;
|
||||
Object.assign(window, { console: undefined });
|
||||
const { logging } = require('@apache-superset/core/utils');
|
||||
try {
|
||||
const logging = loadLogging();
|
||||
|
||||
expect(() => {
|
||||
logging.debug();
|
||||
logging.log();
|
||||
logging.info();
|
||||
logging.warn('warn');
|
||||
logging.error('error');
|
||||
logging.trace();
|
||||
logging.table([
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
]);
|
||||
}).not.toThrow();
|
||||
Object.assign(window, { console });
|
||||
expect(() => {
|
||||
logging.debug();
|
||||
logging.log();
|
||||
logging.info();
|
||||
logging.warn('warn');
|
||||
logging.error('error');
|
||||
logging.trace();
|
||||
logging.table([
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
]);
|
||||
}).not.toThrow();
|
||||
} finally {
|
||||
Object.assign(window, { console: originalConsole });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -40,9 +40,9 @@
|
||||
"ace-builds": "^1.4.14",
|
||||
"brace": "^0.11.1",
|
||||
"memoize-one": "^5.1.1",
|
||||
"react": "^18.2.0",
|
||||
"react": "^18.3.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "^18.3.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -101,8 +101,8 @@
|
||||
"@types/tinycolor2": "*",
|
||||
"antd": "^5.26.0",
|
||||
"nanoid": "^5.0.9",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0",
|
||||
"react-loadable": "^5.5.0",
|
||||
"tinycolor2": "*"
|
||||
},
|
||||
|
||||
@@ -17,15 +17,23 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { forwardRef } from 'react';
|
||||
import { Avatar as AntdAvatar } from 'antd';
|
||||
import type { AvatarProps, GroupProps as AvatarGroupProps } from './types';
|
||||
|
||||
export function Avatar(props: AvatarProps) {
|
||||
return <AntdAvatar {...props} />;
|
||||
}
|
||||
export const Avatar = forwardRef<HTMLSpanElement, AvatarProps>((props, ref) => (
|
||||
<AntdAvatar ref={ref} {...props} />
|
||||
));
|
||||
|
||||
export function AvatarGroup(props: AvatarGroupProps) {
|
||||
return <AntdAvatar.Group {...props} />;
|
||||
}
|
||||
// antd Avatar.Group is a plain function component without forwardRef; wrap in
|
||||
// a span so this component can be a Tooltip / Popover trigger and skip the
|
||||
// findDOMNode fallback.
|
||||
export const AvatarGroup = forwardRef<HTMLSpanElement, AvatarGroupProps>(
|
||||
(props, ref) => (
|
||||
<span ref={ref}>
|
||||
<AntdAvatar.Group {...props} />
|
||||
</span>
|
||||
),
|
||||
);
|
||||
|
||||
export type { AvatarProps, AvatarGroupProps };
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { Children, ReactElement, Fragment } from 'react';
|
||||
import { Children, ReactElement, Fragment, forwardRef, Ref } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { Button as AntdButton } from 'antd';
|
||||
import { useTheme } from '@apache-superset/core/theme';
|
||||
@@ -100,7 +100,7 @@ const BUTTON_STYLE_MAP: Record<
|
||||
link: { type: 'link' },
|
||||
};
|
||||
|
||||
export function Button(props: ButtonProps) {
|
||||
function ButtonInner(props: ButtonProps, ref: Ref<HTMLElement>) {
|
||||
const {
|
||||
tooltip,
|
||||
placement,
|
||||
@@ -160,6 +160,7 @@ export function Button(props: ButtonProps) {
|
||||
|
||||
const button = (
|
||||
<AntdButton
|
||||
ref={ref as Ref<HTMLButtonElement & HTMLAnchorElement>}
|
||||
href={disabled ? undefined : href}
|
||||
disabled={disabled}
|
||||
type={antdType}
|
||||
@@ -235,4 +236,6 @@ export function Button(props: ButtonProps) {
|
||||
return button;
|
||||
}
|
||||
|
||||
export const Button = forwardRef<HTMLElement, ButtonProps>(ButtonInner);
|
||||
|
||||
export type { ButtonProps, OnClickHandler };
|
||||
|
||||
@@ -75,7 +75,10 @@ export const DropdownButton = ({
|
||||
id={`${kebabCase(tooltip)}-tooltip`}
|
||||
title={tooltip}
|
||||
>
|
||||
{button}
|
||||
{/* antd Dropdown.Button is a plain function component without
|
||||
forwardRef; wrap in a span so the Tooltip can attach a ref to a
|
||||
real DOM node and skip the findDOMNode fallback. */}
|
||||
<span>{button}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -240,7 +240,10 @@ export function EditableTitle({
|
||||
t("You don't have the rights to alter this title.")
|
||||
}
|
||||
>
|
||||
{titleComponent}
|
||||
{/* Wrap in span so the Tooltip can attach a ref to a DOM element.
|
||||
antd's Input.TextArea forwards a non-DOM imperative handle, which
|
||||
triggers a React 18 findDOMNode deprecation warning. */}
|
||||
<span>{titleComponent}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,47 +16,54 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { forwardRef } from 'react';
|
||||
import { Tooltip } from '../Tooltip';
|
||||
import { Button } from '../Button';
|
||||
import type { IconTooltipProps } from './types';
|
||||
|
||||
export const IconTooltip = ({
|
||||
children = null,
|
||||
className = '',
|
||||
onClick = () => undefined,
|
||||
placement = 'top',
|
||||
style = {},
|
||||
tooltip = null,
|
||||
mouseEnterDelay = 0.3,
|
||||
mouseLeaveDelay = 0.15,
|
||||
}: IconTooltipProps) => {
|
||||
const iconTooltip = (
|
||||
<Button
|
||||
onClick={onClick}
|
||||
style={{
|
||||
padding: 0,
|
||||
...style,
|
||||
}}
|
||||
buttonStyle="link"
|
||||
className={`IconTooltip ${className}`}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
if (tooltip) {
|
||||
return (
|
||||
<Tooltip
|
||||
id="tooltip"
|
||||
title={tooltip}
|
||||
placement={placement}
|
||||
mouseEnterDelay={mouseEnterDelay}
|
||||
mouseLeaveDelay={mouseLeaveDelay}
|
||||
export const IconTooltip = forwardRef<HTMLElement, IconTooltipProps>(
|
||||
(
|
||||
{
|
||||
children = null,
|
||||
className = '',
|
||||
onClick = () => undefined,
|
||||
placement = 'top',
|
||||
style = {},
|
||||
tooltip = null,
|
||||
mouseEnterDelay = 0.3,
|
||||
mouseLeaveDelay = 0.15,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const iconTooltip = (
|
||||
<Button
|
||||
ref={ref}
|
||||
onClick={onClick}
|
||||
style={{
|
||||
padding: 0,
|
||||
...style,
|
||||
}}
|
||||
buttonStyle="link"
|
||||
className={`IconTooltip ${className}`}
|
||||
>
|
||||
{iconTooltip}
|
||||
</Tooltip>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
return iconTooltip;
|
||||
};
|
||||
if (tooltip) {
|
||||
return (
|
||||
<Tooltip
|
||||
id="tooltip"
|
||||
title={tooltip}
|
||||
placement={placement}
|
||||
mouseEnterDelay={mouseEnterDelay}
|
||||
mouseLeaveDelay={mouseLeaveDelay}
|
||||
>
|
||||
{iconTooltip}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
return iconTooltip;
|
||||
},
|
||||
);
|
||||
|
||||
export type { IconTooltipProps };
|
||||
|
||||
@@ -165,7 +165,7 @@ import {
|
||||
SlackOutlined,
|
||||
ApiOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { FC } from 'react';
|
||||
import { ForwardRefExoticComponent, RefAttributes, forwardRef } from 'react';
|
||||
import { IconType } from './types';
|
||||
import { BaseIconComponent } from './BaseIcon';
|
||||
|
||||
@@ -323,19 +323,25 @@ type AntdIconNames = keyof typeof AntdIcons;
|
||||
|
||||
export const antdEnhancedIcons: Record<
|
||||
AntdIconNames,
|
||||
FC<IconType>
|
||||
ForwardRefExoticComponent<IconType & RefAttributes<HTMLSpanElement>>
|
||||
> = Object.keys(AntdIcons)
|
||||
.filter(key => !EXCLUDED_ICONS.some(excluded => key.includes(excluded)))
|
||||
.reduce(
|
||||
(acc, key) => {
|
||||
acc[key as AntdIconNames] = (props: IconType) => (
|
||||
<BaseIconComponent
|
||||
component={AntdIcons[key as AntdIconNames]}
|
||||
fileName={key}
|
||||
{...props}
|
||||
/>
|
||||
acc[key as AntdIconNames] = forwardRef<HTMLSpanElement, IconType>(
|
||||
(props, ref) => (
|
||||
<BaseIconComponent
|
||||
ref={ref}
|
||||
component={AntdIcons[key as AntdIconNames]}
|
||||
fileName={key}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<AntdIconNames, FC<IconType>>,
|
||||
{} as Record<
|
||||
AntdIconNames,
|
||||
ForwardRefExoticComponent<IconType & RefAttributes<HTMLSpanElement>>
|
||||
>,
|
||||
);
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { FC, SVGProps, useEffect, useRef, useState } from 'react';
|
||||
import { FC, SVGProps, forwardRef, useEffect, useRef, useState } from 'react';
|
||||
import TransparentIcon from './svgs/transparent.svg';
|
||||
import { IconType } from './types';
|
||||
import { BaseIconComponent } from './BaseIcon';
|
||||
|
||||
const AsyncIcon = (props: IconType) => {
|
||||
const AsyncIcon = forwardRef<HTMLSpanElement, IconType>((props, ref) => {
|
||||
const [, setLoaded] = useState(false);
|
||||
const ImportedSVG = useRef<FC<SVGProps<SVGSVGElement>>>();
|
||||
const { fileName, customIcons, iconSize, iconColor, viewBox, ...restProps } =
|
||||
@@ -46,6 +46,7 @@ const AsyncIcon = (props: IconType) => {
|
||||
|
||||
return (
|
||||
<BaseIconComponent
|
||||
ref={ref}
|
||||
component={ImportedSVG.current || TransparentIcon}
|
||||
fileName={fileName}
|
||||
customIcons={customIcons}
|
||||
@@ -55,6 +56,6 @@ const AsyncIcon = (props: IconType) => {
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default AsyncIcon;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { forwardRef, type ComponentType } from 'react';
|
||||
import { css, useTheme, getFontSize } from '@apache-superset/core/theme';
|
||||
import { AntdIconType, BaseIconProps, CustomIconType, IconType } from './types';
|
||||
|
||||
@@ -35,65 +36,78 @@ const genAriaLabel = (fileName: string) => {
|
||||
return name.toLowerCase();
|
||||
};
|
||||
|
||||
export const BaseIconComponent: React.FC<
|
||||
export const BaseIconComponent = forwardRef<
|
||||
HTMLSpanElement | SVGSVGElement,
|
||||
BaseIconProps & Omit<IconType, 'component'>
|
||||
> = ({
|
||||
component: Component,
|
||||
iconColor,
|
||||
iconSize,
|
||||
viewBox,
|
||||
customIcons,
|
||||
fileName,
|
||||
...rest
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const whatRole = rest?.onClick ? 'button' : 'img';
|
||||
const ariaLabel = genAriaLabel(fileName || '');
|
||||
const style = {
|
||||
color: iconColor,
|
||||
fontSize: iconSize
|
||||
? `${getFontSize(theme, iconSize)}px`
|
||||
: `${theme.fontSize}px`,
|
||||
cursor: rest?.onClick ? 'pointer' : undefined,
|
||||
};
|
||||
>(
|
||||
(
|
||||
{
|
||||
component: Component,
|
||||
iconColor,
|
||||
iconSize,
|
||||
viewBox,
|
||||
customIcons,
|
||||
fileName,
|
||||
...rest
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const theme = useTheme();
|
||||
const whatRole = rest?.onClick ? 'button' : 'img';
|
||||
const ariaLabel = genAriaLabel(fileName || '');
|
||||
const style = {
|
||||
color: iconColor,
|
||||
fontSize: iconSize
|
||||
? `${getFontSize(theme, iconSize)}px`
|
||||
: `${theme.fontSize}px`,
|
||||
cursor: rest?.onClick ? 'pointer' : undefined,
|
||||
};
|
||||
|
||||
return customIcons ? (
|
||||
<span
|
||||
role={whatRole}
|
||||
aria-label={ariaLabel}
|
||||
data-test={ariaLabel}
|
||||
css={[
|
||||
css`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
line-height: 0;
|
||||
vertical-align: middle;
|
||||
`,
|
||||
]}
|
||||
>
|
||||
<Component
|
||||
viewBox={viewBox || '0 0 24 24'}
|
||||
const AntdComponent = Component as ComponentType<
|
||||
Record<string, unknown> & {
|
||||
ref?: React.Ref<HTMLSpanElement | SVGSVGElement>;
|
||||
}
|
||||
>;
|
||||
return customIcons ? (
|
||||
<span
|
||||
ref={ref as React.Ref<HTMLSpanElement>}
|
||||
role={whatRole}
|
||||
aria-label={ariaLabel}
|
||||
data-test={ariaLabel}
|
||||
css={[
|
||||
css`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
line-height: 0;
|
||||
vertical-align: middle;
|
||||
`,
|
||||
]}
|
||||
>
|
||||
<Component
|
||||
viewBox={viewBox || '0 0 24 24'}
|
||||
style={style}
|
||||
width={
|
||||
iconSize
|
||||
? `${getFontSize(theme, iconSize) || theme.fontSize}px`
|
||||
: `${theme.fontSize}px`
|
||||
}
|
||||
height={
|
||||
iconSize
|
||||
? `${getFontSize(theme, iconSize) || theme.fontSize}px`
|
||||
: `${theme.fontSize}px`
|
||||
}
|
||||
{...(rest as CustomIconType)}
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
<AntdComponent
|
||||
ref={ref}
|
||||
role={whatRole}
|
||||
style={style}
|
||||
width={
|
||||
iconSize
|
||||
? `${getFontSize(theme, iconSize) || theme.fontSize}px`
|
||||
: `${theme.fontSize}px`
|
||||
}
|
||||
height={
|
||||
iconSize
|
||||
? `${getFontSize(theme, iconSize) || theme.fontSize}px`
|
||||
: `${theme.fontSize}px`
|
||||
}
|
||||
{...(rest as CustomIconType)}
|
||||
aria-label={ariaLabel}
|
||||
data-test={ariaLabel}
|
||||
{...(rest as AntdIconType)}
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
<Component
|
||||
role={whatRole}
|
||||
style={style}
|
||||
aria-label={ariaLabel}
|
||||
data-test={ariaLabel}
|
||||
{...(rest as AntdIconType)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -17,12 +17,16 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { FC } from 'react';
|
||||
import { ForwardRefExoticComponent, RefAttributes, forwardRef } from 'react';
|
||||
import { antdEnhancedIcons } from './AntdEnhanced';
|
||||
import AsyncIcon from './AsyncIcon';
|
||||
|
||||
import type { IconType } from './types';
|
||||
|
||||
type IconComponent = ForwardRefExoticComponent<
|
||||
IconType & RefAttributes<HTMLSpanElement>
|
||||
>;
|
||||
|
||||
/**
|
||||
* Filename is going to be inferred from the icon name.
|
||||
* i.e. BigNumberChartTile => assets/images/icons/big_number_chart_tile
|
||||
@@ -58,15 +62,17 @@ const customIcons = [
|
||||
'Undo',
|
||||
] as const;
|
||||
|
||||
type CustomIconType = Record<(typeof customIcons)[number], FC<IconType>>;
|
||||
type CustomIconType = Record<(typeof customIcons)[number], IconComponent>;
|
||||
|
||||
const iconOverrides: CustomIconType = {} as CustomIconType;
|
||||
customIcons.forEach(customIcon => {
|
||||
const fileName = customIcon
|
||||
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
||||
.toLowerCase();
|
||||
iconOverrides[customIcon] = (props: IconType) => (
|
||||
<AsyncIcon customIcons fileName={fileName} {...props} />
|
||||
iconOverrides[customIcon] = forwardRef<HTMLSpanElement, IconType>(
|
||||
(props, ref) => (
|
||||
<AsyncIcon ref={ref} customIcons fileName={fileName} {...props} />
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -74,7 +80,7 @@ export type IconNameType =
|
||||
| keyof typeof antdEnhancedIcons
|
||||
| keyof typeof iconOverrides;
|
||||
|
||||
type IconComponentType = Record<IconNameType, FC<IconType>>;
|
||||
type IconComponentType = Record<IconNameType, IconComponent>;
|
||||
|
||||
export const Icons: IconComponentType = {
|
||||
...antdEnhancedIcons,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { forwardRef } from 'react';
|
||||
import { Tag } from '@superset-ui/core/components/Tag';
|
||||
import { css } from '@emotion/react';
|
||||
import { useTheme, getColorVariants } from '@apache-superset/core/theme';
|
||||
@@ -23,7 +24,7 @@ import { DatasetTypeLabel } from './reusable/DatasetTypeLabel';
|
||||
import { PublishedLabel } from './reusable/PublishedLabel';
|
||||
import type { LabelProps } from './types';
|
||||
|
||||
export function Label(props: LabelProps) {
|
||||
export const Label = forwardRef<HTMLSpanElement, LabelProps>((props, ref) => {
|
||||
const theme = useTheme();
|
||||
// Use Ant Design's motion duration instead of deprecated transitionTiming
|
||||
const {
|
||||
@@ -71,6 +72,7 @@ export function Label(props: LabelProps) {
|
||||
|
||||
return (
|
||||
<Tag
|
||||
ref={ref}
|
||||
onClick={onClick}
|
||||
role={onClick ? 'button' : undefined}
|
||||
style={style}
|
||||
@@ -81,6 +83,6 @@ export function Label(props: LabelProps) {
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
});
|
||||
export { DatasetTypeLabel, PublishedLabel };
|
||||
export type { LabelType } from './types';
|
||||
|
||||
@@ -371,6 +371,9 @@ const CustomModal = ({
|
||||
disabled={!draggable || dragDisabled}
|
||||
bounds={bounds}
|
||||
onStart={(event, uiData) => onDragStart(event, uiData)}
|
||||
// Pass nodeRef so react-draggable does not fall back to
|
||||
// ReactDOM.findDOMNode (deprecated in React 18+ Strict Mode).
|
||||
nodeRef={draggableRef}
|
||||
{...draggableConfig}
|
||||
>
|
||||
{resizable ? (
|
||||
|
||||
@@ -16,11 +16,15 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { forwardRef } from 'react';
|
||||
import { Popover as AntdPopover } from 'antd';
|
||||
import { PopoverProps as AntdPopoverProps } from 'antd/es/popover';
|
||||
import type { TooltipRef } from 'antd/es/tooltip';
|
||||
|
||||
export interface PopoverProps extends AntdPopoverProps {
|
||||
forceRender?: boolean;
|
||||
}
|
||||
|
||||
export const Popover = (props: PopoverProps) => <AntdPopover {...props} />;
|
||||
export const Popover = forwardRef<TooltipRef, PopoverProps>((props, ref) => (
|
||||
<AntdPopover ref={ref} {...props} />
|
||||
));
|
||||
|
||||
@@ -16,10 +16,9 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { MouseEventHandler, forwardRef } from 'react';
|
||||
import { MouseEventHandler } from 'react';
|
||||
import { SupersetTheme } from '@apache-superset/core/theme';
|
||||
import { Icons } from '@superset-ui/core/components/Icons';
|
||||
import type { IconType } from '@superset-ui/core/components/Icons/types';
|
||||
import { Tooltip } from '../Tooltip';
|
||||
|
||||
export interface RefreshLabelProps {
|
||||
@@ -32,25 +31,19 @@ const RefreshLabel = ({
|
||||
onClick,
|
||||
tooltipContent,
|
||||
disabled,
|
||||
}: RefreshLabelProps) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const IconWithoutRef = forwardRef((props: IconType, ref: any) => (
|
||||
<Icons.SyncOutlined iconSize="l" {...props} />
|
||||
));
|
||||
|
||||
return (
|
||||
<Tooltip title={tooltipContent}>
|
||||
<IconWithoutRef
|
||||
role="button"
|
||||
onClick={disabled ? undefined : onClick}
|
||||
css={(theme: SupersetTheme) => ({
|
||||
cursor: 'pointer',
|
||||
color: theme.colorIcon,
|
||||
'&:hover': { color: theme.colorPrimary },
|
||||
})}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
}: RefreshLabelProps) => (
|
||||
<Tooltip title={tooltipContent}>
|
||||
<Icons.SyncOutlined
|
||||
iconSize="l"
|
||||
role="button"
|
||||
onClick={disabled ? undefined : onClick}
|
||||
css={(theme: SupersetTheme) => ({
|
||||
cursor: 'pointer',
|
||||
color: theme.colorIcon,
|
||||
'&:hover': { color: theme.colorPrimary },
|
||||
})}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
export default RefreshLabel;
|
||||
|
||||
@@ -16,17 +16,22 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { forwardRef } from 'react';
|
||||
import { Tooltip as AntdTooltip } from 'antd';
|
||||
import type { TooltipRef } from 'antd/es/tooltip';
|
||||
|
||||
import type { TooltipProps, TooltipPlacement } from './types';
|
||||
|
||||
export const Tooltip = ({ overlayStyle, ...props }: TooltipProps) => (
|
||||
<AntdTooltip
|
||||
styles={{
|
||||
body: { overflow: 'hidden', textOverflow: 'ellipsis' },
|
||||
root: overlayStyle ?? {},
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
export const Tooltip = forwardRef<TooltipRef, TooltipProps>(
|
||||
({ overlayStyle, ...props }, ref) => (
|
||||
<AntdTooltip
|
||||
ref={ref}
|
||||
styles={{
|
||||
body: { overflow: 'hidden', textOverflow: 'ellipsis' },
|
||||
root: overlayStyle ?? {},
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
export type { TooltipProps, TooltipPlacement };
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -34,6 +34,6 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -36,6 +36,6 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@testing-library/jest-dom": "*",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -39,6 +39,6 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@apache-superset/core": "*",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,6 @@
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"dayjs": "^1.11.21",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "*",
|
||||
"@types/react": "*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"geostyler-wfs-parser": "^3.0.1",
|
||||
"ol": "^10.8.0",
|
||||
"polished": "*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"dayjs": "^1.11.21",
|
||||
"echarts": "*",
|
||||
"memoize-one": "*",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -39,9 +39,9 @@
|
||||
"handlebars": "^4.7.8",
|
||||
"lodash": "^4.18.1",
|
||||
"dayjs": "^1.11.21",
|
||||
"react": "^18.2.0",
|
||||
"react": "^18.3.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "^18.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^30.0.0",
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
"@superset-ui/core": "*",
|
||||
"lodash": "^4.18.1",
|
||||
"prop-types": "*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.29.7",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
"@testing-library/user-event": "*",
|
||||
"@types/react": "*",
|
||||
"match-sorter": "^8.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"@superset-ui/core": "*",
|
||||
"@types/lodash": "*",
|
||||
"@types/react": "*",
|
||||
"react": "^18.2.0"
|
||||
"react": "^18.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/d3-cloud": "^1.2.9"
|
||||
|
||||
@@ -67,8 +67,8 @@
|
||||
"@superset-ui/core": "*",
|
||||
"dayjs": "^1.11.21",
|
||||
"mapbox-gl": ">=1.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"mapbox-gl": {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { AriaAttributes } from 'react';
|
||||
import { AriaAttributes, Ref } from 'react';
|
||||
import 'core-js/stable';
|
||||
import 'regenerator-runtime/runtime';
|
||||
import jQuery from 'jquery';
|
||||
@@ -98,31 +98,39 @@ jest.mock('rehype-raw', () => () => jest.fn());
|
||||
// Tests should override this when needed
|
||||
jest.mock('@superset-ui/core/components/Icons/AsyncIcon', () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
fileName,
|
||||
role,
|
||||
'aria-label': ariaLabel,
|
||||
onClick,
|
||||
...rest
|
||||
}: {
|
||||
fileName: string;
|
||||
role?: string;
|
||||
'aria-label'?: AriaAttributes['aria-label'];
|
||||
onClick?: () => void;
|
||||
}) => {
|
||||
// Simple mock that provides the essential attributes for testing
|
||||
const label = ariaLabel || fileName?.replace(/_/g, '-').toLowerCase() || '';
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||
<span
|
||||
role={role || (onClick ? 'button' : 'img')}
|
||||
aria-label={label}
|
||||
data-test={label}
|
||||
onClick={onClick}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
},
|
||||
// eslint-disable-next-line global-require
|
||||
default: require('react').forwardRef(
|
||||
(
|
||||
{
|
||||
fileName,
|
||||
role,
|
||||
'aria-label': ariaLabel,
|
||||
onClick,
|
||||
...rest
|
||||
}: {
|
||||
fileName: string;
|
||||
role?: string;
|
||||
'aria-label'?: AriaAttributes['aria-label'];
|
||||
onClick?: () => void;
|
||||
},
|
||||
ref: Ref<HTMLSpanElement>,
|
||||
) => {
|
||||
// Simple mock that provides the essential attributes for testing
|
||||
const label =
|
||||
ariaLabel || fileName?.replace(/_/g, '-').toLowerCase() || '';
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||
<span
|
||||
ref={ref}
|
||||
role={role || (onClick ? 'button' : 'img')}
|
||||
aria-label={label}
|
||||
data-test={label}
|
||||
onClick={onClick}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
},
|
||||
),
|
||||
StyledIcon: ({
|
||||
component: Component,
|
||||
role,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { act } from 'react';
|
||||
import { QueryState } from '@superset-ui/core';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
@@ -189,15 +189,19 @@ const SqlEditorLeftBar = ({ queryEditorId }: SqlEditorLeftBarProps) => {
|
||||
placement="bottomLeft"
|
||||
trigger="click"
|
||||
>
|
||||
<DatabaseSelector
|
||||
key={`db-selector-${db ? db.id : 'no-db'}:${catalog ?? 'no-catalog'}:${
|
||||
schema ?? 'no-schema'
|
||||
}`}
|
||||
{...dbSelectorProps}
|
||||
emptyState={<EmptyState />}
|
||||
sqlLabMode
|
||||
onOpenModal={openSelectorModal}
|
||||
/>
|
||||
{/* Wrap in a span so the Popover can attach a ref without relying
|
||||
on findDOMNode (deprecated in React 18+). */}
|
||||
<span>
|
||||
<DatabaseSelector
|
||||
key={`db-selector-${db ? db.id : 'no-db'}:${catalog ?? 'no-catalog'}:${
|
||||
schema ?? 'no-schema'
|
||||
}`}
|
||||
{...dbSelectorProps}
|
||||
emptyState={<EmptyState />}
|
||||
sqlLabMode
|
||||
onOpenModal={openSelectorModal}
|
||||
/>
|
||||
</span>
|
||||
</Popover>
|
||||
<StyledDivider />
|
||||
<TableExploreTree queryEditorId={activeQEId} />
|
||||
|
||||
@@ -98,7 +98,10 @@ class CopyToClip extends Component<CopyToClipboardProps> {
|
||||
trigger={['hover']}
|
||||
arrow={{ pointAtCenter: true }}
|
||||
>
|
||||
{this.getDecoratedCopyNode()}
|
||||
{/* Wrap in a span so antd Tooltip has a real DOM ref target;
|
||||
avoids findDOMNode fallback when copyNode is a function
|
||||
component without forwardRef. */}
|
||||
<span>{this.getDecoratedCopyNode()}</span>
|
||||
</Tooltip>
|
||||
) : (
|
||||
this.getDecoratedCopyNode()
|
||||
|
||||
@@ -17,21 +17,25 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
import { PropsWithoutRef, RefAttributes } from 'react';
|
||||
import { forwardRef, PropsWithoutRef, Ref, RefAttributes } from 'react';
|
||||
import { Link, LinkProps } from 'react-router-dom';
|
||||
import { isUrlExternal, parseUrl } from 'src/utils/urlUtils';
|
||||
|
||||
export const GenericLink = <S,>({
|
||||
to,
|
||||
component,
|
||||
replace,
|
||||
innerRef,
|
||||
children,
|
||||
...rest
|
||||
}: PropsWithoutRef<LinkProps<S>> & RefAttributes<HTMLAnchorElement>) => {
|
||||
type GenericLinkProps<S> = PropsWithoutRef<LinkProps<S>> &
|
||||
RefAttributes<HTMLAnchorElement>;
|
||||
|
||||
const GenericLinkInner = <S,>(
|
||||
{ to, component, replace, innerRef, children, ...rest }: GenericLinkProps<S>,
|
||||
ref: Ref<HTMLAnchorElement>,
|
||||
) => {
|
||||
if (typeof to === 'string' && isUrlExternal(to)) {
|
||||
return (
|
||||
<a data-test="external-link" href={sanitizeUrl(parseUrl(to))} {...rest}>
|
||||
<a
|
||||
ref={ref}
|
||||
data-test="external-link"
|
||||
href={sanitizeUrl(parseUrl(to))}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
@@ -42,10 +46,14 @@ export const GenericLink = <S,>({
|
||||
to={to}
|
||||
component={component}
|
||||
replace={replace}
|
||||
innerRef={innerRef}
|
||||
innerRef={innerRef ?? ref}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export const GenericLink = forwardRef(GenericLinkInner) as <S>(
|
||||
props: GenericLinkProps<S> & { ref?: Ref<HTMLAnchorElement> },
|
||||
) => ReturnType<typeof GenericLinkInner>;
|
||||
|
||||
@@ -193,52 +193,57 @@ export default function getControlItemsMap({
|
||||
t('Populate "Default value" to enable this control')
|
||||
}
|
||||
>
|
||||
<StyledRowFormItem
|
||||
expanded={expanded}
|
||||
key={controlItem.name}
|
||||
name={['filters', filterId, 'controlValues', controlItem.name]}
|
||||
initialValue={initialValue}
|
||||
valuePropName="checked"
|
||||
colon={false}
|
||||
>
|
||||
<Checkbox
|
||||
disabled={controlItem.config.affectsDataMask && disabled}
|
||||
onChange={checked => {
|
||||
if (controlItem.config.requiredFirst) {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
requiredFirst: {
|
||||
...formFilter?.requiredFirst,
|
||||
[controlItem.name]: checked,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (controlItem.config.resetConfig) {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
defaultDataMask: null,
|
||||
});
|
||||
}
|
||||
formChanged();
|
||||
forceUpdate();
|
||||
}}
|
||||
{/* Wrap in span so antd Tooltip can attach a ref without
|
||||
relying on findDOMNode (deprecated in React 18+). */}
|
||||
<span>
|
||||
<StyledRowFormItem
|
||||
expanded={expanded}
|
||||
key={controlItem.name}
|
||||
name={['filters', filterId, 'controlValues', controlItem.name]}
|
||||
initialValue={initialValue}
|
||||
valuePropName="checked"
|
||||
colon={false}
|
||||
>
|
||||
<>
|
||||
{typeof controlItem.config.label === 'function'
|
||||
? (controlItem.config.label as Function)()
|
||||
: controlItem.config.label}
|
||||
|
||||
{controlItem.config.description && (
|
||||
<InfoTooltip
|
||||
placement="top"
|
||||
tooltip={
|
||||
typeof controlItem.config.description === 'function'
|
||||
? (controlItem.config.description as Function)()
|
||||
: (controlItem.config.description as React.ReactNode)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</Checkbox>
|
||||
</StyledRowFormItem>
|
||||
<Checkbox
|
||||
disabled={controlItem.config.affectsDataMask && disabled}
|
||||
onChange={checked => {
|
||||
if (controlItem.config.requiredFirst) {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
requiredFirst: {
|
||||
...formFilter?.requiredFirst,
|
||||
[controlItem.name]: checked,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (controlItem.config.resetConfig) {
|
||||
setNativeFilterFieldValues(form, filterId, {
|
||||
defaultDataMask: null,
|
||||
});
|
||||
}
|
||||
formChanged();
|
||||
forceUpdate();
|
||||
}}
|
||||
>
|
||||
<>
|
||||
{typeof controlItem.config.label === 'function'
|
||||
? (controlItem.config.label as Function)()
|
||||
: controlItem.config.label}
|
||||
|
||||
{controlItem.config.description && (
|
||||
<InfoTooltip
|
||||
placement="top"
|
||||
tooltip={
|
||||
typeof controlItem.config.description === 'function'
|
||||
? (controlItem.config.description as Function)()
|
||||
: (controlItem.config
|
||||
.description as React.ReactNode)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</Checkbox>
|
||||
</StyledRowFormItem>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -54,7 +54,9 @@ const ColorBreakpointsPopoverTrigger = ({
|
||||
onOpenChange={setVisibility}
|
||||
destroyOnHidden
|
||||
>
|
||||
{props.children}
|
||||
{/* Wrap in span so the Popover can attach a ref without relying
|
||||
on findDOMNode (deprecated in React 18+). */}
|
||||
<span>{props.children}</span>
|
||||
</ControlPopover>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -52,7 +52,9 @@ const ContourPopoverTrigger = ({
|
||||
onOpenChange={setVisibility}
|
||||
destroyOnHidden
|
||||
>
|
||||
{props.children}
|
||||
{/* Wrap in span so the Popover can attach a ref without relying
|
||||
on findDOMNode (deprecated in React 18+). */}
|
||||
<span>{props.children}</span>
|
||||
</ControlPopover>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -369,16 +369,21 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
||||
overlayClassName="time-range-popover"
|
||||
>
|
||||
<Tooltip placement="top" title={tooltipTitle}>
|
||||
<DateLabel
|
||||
name={name}
|
||||
aria-labelledby={`filter-name-${props.name}`}
|
||||
aria-describedby={`date-label-${props.name}`}
|
||||
label={actualTimeRange}
|
||||
isActive={show}
|
||||
isPlaceholder={actualTimeRange === NO_TIME_RANGE}
|
||||
data-test={DateFilterTestKey.PopoverOverlay}
|
||||
ref={labelRef}
|
||||
/>
|
||||
{/* Wrap in a span so the Popover gets a stable DOM ref target;
|
||||
DateLabel forwards its ref to an inner span used for measuring
|
||||
text truncation, which would otherwise become the popover's
|
||||
positioning anchor in React 18. */}
|
||||
<span data-test={DateFilterTestKey.PopoverOverlay}>
|
||||
<DateLabel
|
||||
name={name}
|
||||
aria-labelledby={`filter-name-${props.name}`}
|
||||
aria-describedby={`date-label-${props.name}`}
|
||||
label={actualTimeRange}
|
||||
isActive={show}
|
||||
isPlaceholder={actualTimeRange === NO_TIME_RANGE}
|
||||
ref={labelRef}
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</ControlPopover>
|
||||
);
|
||||
|
||||
@@ -201,7 +201,9 @@ const ColumnSelectPopoverTriggerInner = ({
|
||||
title={popoverTitle}
|
||||
destroyOnHidden
|
||||
>
|
||||
{children}
|
||||
{/* Wrap in span so the Popover can attach a ref without relying
|
||||
on findDOMNode (deprecated in React 18+). */}
|
||||
<span>{children}</span>
|
||||
</ControlPopover>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -104,14 +104,6 @@ interface AdhocFilterControlState {
|
||||
|
||||
const { warning } = Modal;
|
||||
|
||||
const defaultProps = {
|
||||
name: '',
|
||||
onChange: () => {},
|
||||
columns: [],
|
||||
savedMetrics: [],
|
||||
selectedMetrics: [],
|
||||
};
|
||||
|
||||
function optionsForSelect(props: AdhocFilterControlProps): FilterOption[] {
|
||||
const options = [
|
||||
...(props.columns || []),
|
||||
@@ -391,7 +383,7 @@ class AdhocFilterControl extends Component<
|
||||
return (
|
||||
<div className="metrics-select" data-test="adhoc-filter-control">
|
||||
<HeaderContainer>
|
||||
<ControlHeader {...this.props} />
|
||||
<ControlHeader {...this.props} name={this.props.name ?? ''} />
|
||||
</HeaderContainer>
|
||||
<LabelsContainer>
|
||||
{[
|
||||
@@ -413,7 +405,4 @@ class AdhocFilterControl extends Component<
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error - defaultProps for backward compatibility
|
||||
AdhocFilterControl.defaultProps = defaultProps;
|
||||
|
||||
export default withTheme(AdhocFilterControl);
|
||||
|
||||
@@ -112,7 +112,9 @@ class AdhocFilterPopoverTrigger extends PureComponent<
|
||||
onOpenChange={togglePopover}
|
||||
destroyOnHidden
|
||||
>
|
||||
{this.props.children}
|
||||
{/* Wrap in span so the Popover can attach a ref without relying
|
||||
on findDOMNode (deprecated in React 18+). */}
|
||||
<span>{this.props.children}</span>
|
||||
</ControlPopover>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,10 +64,7 @@ interface FixedOrMetricControlState {
|
||||
metricValue: MetricValue | null;
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
onChange: () => {},
|
||||
default: { type: controlTypes.fixed, value: 5 },
|
||||
};
|
||||
const DEFAULT_VALUE: ControlValue = { type: controlTypes.fixed, value: 5 };
|
||||
|
||||
export default class FixedOrMetricControl extends Component<
|
||||
FixedOrMetricControlProps,
|
||||
@@ -79,10 +76,11 @@ export default class FixedOrMetricControl extends Component<
|
||||
this.setType = this.setType.bind(this);
|
||||
this.setFixedValue = this.setFixedValue.bind(this);
|
||||
this.setMetric = this.setMetric.bind(this);
|
||||
const defaultValue = props.default ?? DEFAULT_VALUE;
|
||||
const type = (props.value?.type ??
|
||||
props.default?.type ??
|
||||
defaultValue.type ??
|
||||
controlTypes.fixed) as 'fix' | 'metric';
|
||||
const rawValue = props.value?.value ?? props.default?.value ?? '100';
|
||||
const rawValue = props.value?.value ?? defaultValue.value ?? '100';
|
||||
const fixedValue =
|
||||
type === controlTypes.fixed && typeof rawValue !== 'object'
|
||||
? rawValue
|
||||
@@ -121,7 +119,7 @@ export default class FixedOrMetricControl extends Component<
|
||||
}
|
||||
|
||||
render() {
|
||||
const value = this.props.value ?? this.props.default;
|
||||
const value = this.props.value ?? this.props.default ?? DEFAULT_VALUE;
|
||||
const type = value?.type ?? controlTypes.fixed;
|
||||
const columns = this.props.datasource
|
||||
? this.props.datasource.columns
|
||||
@@ -203,6 +201,3 @@ export default class FixedOrMetricControl extends Component<
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error - defaultProps for backward compatibility
|
||||
FixedOrMetricControl.defaultProps = defaultProps;
|
||||
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
Tooltip,
|
||||
} from '@superset-ui/core/components';
|
||||
import sqlKeywords from 'src/SqlLab/utils/sqlKeywords';
|
||||
import { noOp } from 'src/utils/common';
|
||||
import {
|
||||
AGGREGATES_OPTIONS,
|
||||
POPOVER_INITIAL_HEIGHT,
|
||||
@@ -106,12 +105,6 @@ interface AdhocMetricEditPopoverState {
|
||||
height: number;
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
columns: [],
|
||||
getCurrentTab: noOp,
|
||||
isNewMetric: false,
|
||||
};
|
||||
|
||||
const StyledSelect = styled(Select)`
|
||||
.metric-option {
|
||||
& > svg {
|
||||
@@ -199,8 +192,12 @@ class AdhocMetricEditPopover extends PureComponent<
|
||||
}
|
||||
|
||||
getDefaultTab() {
|
||||
const { adhocMetric, savedMetric, savedMetricsOptions, isNewMetric } =
|
||||
this.props;
|
||||
const {
|
||||
adhocMetric,
|
||||
savedMetric,
|
||||
savedMetricsOptions,
|
||||
isNewMetric = false,
|
||||
} = this.props;
|
||||
if (isDefined(adhocMetric.column) || isDefined(adhocMetric.sqlExpression)) {
|
||||
return adhocMetric.expressionType;
|
||||
}
|
||||
@@ -353,7 +350,7 @@ class AdhocMetricEditPopover extends PureComponent<
|
||||
onClose,
|
||||
onResize,
|
||||
datasource,
|
||||
isNewMetric,
|
||||
isNewMetric = false,
|
||||
isLabelModified,
|
||||
...popoverProps
|
||||
} = this.props;
|
||||
@@ -606,8 +603,6 @@ class AdhocMetricEditPopover extends PureComponent<
|
||||
);
|
||||
}
|
||||
}
|
||||
// @ts-expect-error - defaultProps for backward compatibility
|
||||
AdhocMetricEditPopover.defaultProps = defaultProps;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Thin functional wrapper that injects compatibility data from Redux.
|
||||
|
||||
@@ -274,7 +274,9 @@ class AdhocMetricPopoverTrigger extends PureComponent<
|
||||
title={popoverTitle}
|
||||
destroyOnHidden
|
||||
>
|
||||
{this.props.children}
|
||||
{/* Wrap in span so the Popover can attach a ref without relying
|
||||
on findDOMNode (deprecated in React 18+). */}
|
||||
<span>{this.props.children}</span>
|
||||
</ControlPopover>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -32,13 +32,6 @@ import MetricDefinitionValue from './MetricDefinitionValue';
|
||||
import AdhocMetric from './AdhocMetric';
|
||||
import AdhocMetricPopoverTrigger from './AdhocMetricPopoverTrigger';
|
||||
|
||||
const defaultProps = {
|
||||
onChange: () => {},
|
||||
clearable: true,
|
||||
savedMetrics: [],
|
||||
columns: [],
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function getOptionsForSavedMetrics(
|
||||
savedMetrics: any,
|
||||
@@ -122,11 +115,11 @@ export interface MetricsControlProps {
|
||||
}
|
||||
|
||||
const MetricsControl = ({
|
||||
onChange,
|
||||
onChange = () => {},
|
||||
multi,
|
||||
value: propsValue,
|
||||
columns,
|
||||
savedMetrics,
|
||||
columns = [],
|
||||
savedMetrics = [],
|
||||
datasource,
|
||||
...props
|
||||
}: MetricsControlProps) => {
|
||||
@@ -351,6 +344,4 @@ const MetricsControl = ({
|
||||
);
|
||||
};
|
||||
|
||||
MetricsControl.defaultProps = defaultProps;
|
||||
|
||||
export default MetricsControl;
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from 'spec/helpers/testing-library';
|
||||
import { VizType } from '@superset-ui/core';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { act } from 'react';
|
||||
import handleResourceExport from 'src/utils/export';
|
||||
import { LocalStorageKeys } from 'src/utils/localStorageHelpers';
|
||||
import ChartTable from './ChartTable';
|
||||
|
||||
@@ -111,12 +111,15 @@ const createController = (
|
||||
...options,
|
||||
});
|
||||
|
||||
// Shared console spies
|
||||
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
|
||||
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
||||
// Shared console spies — re-installed in beforeEach so each test starts
|
||||
// with a fresh call count and a clean implementation.
|
||||
let consoleSpy: jest.SpyInstance;
|
||||
let consoleErrorSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
|
||||
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
||||
|
||||
// Setup DOM environment
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
|
||||
@@ -454,7 +454,7 @@ def print_report(reports: list[MetadataReport], verbose: bool = False) -> None:
|
||||
print("=" * 60)
|
||||
|
||||
all_fields = {**REQUIRED_FIELDS, **RECOMMENDED_FIELDS, **OPTIONAL_FIELDS}
|
||||
field_counts: dict[str, int] = {f: 0 for f in all_fields}
|
||||
field_counts: dict[str, int] = dict.fromkeys(all_fields, 0)
|
||||
|
||||
for r in reports:
|
||||
for field in r.present_fields:
|
||||
|
||||
@@ -59,7 +59,7 @@ ALLOWED_TAGS = {
|
||||
"ul",
|
||||
}.union(TABLE_TAGS)
|
||||
|
||||
ALLOWED_TABLE_ATTRIBUTES = {tag: TABLE_ATTRIBUTES for tag in TABLE_TAGS}
|
||||
ALLOWED_TABLE_ATTRIBUTES = dict.fromkeys(TABLE_TAGS, TABLE_ATTRIBUTES)
|
||||
ALLOWED_ATTRIBUTES = {
|
||||
"a": {"href", "title"},
|
||||
"abbr": {"title"},
|
||||
|
||||
@@ -94,7 +94,7 @@ class SlackMixin:
|
||||
# need to truncate the data
|
||||
for i in range(len(df) - 1):
|
||||
truncated_df = df[: i + 1].fillna("")
|
||||
truncated_row = pd.Series({k: "..." for k in df.columns})
|
||||
truncated_row = pd.Series(dict.fromkeys(df.columns, "..."))
|
||||
truncated_df = pd.concat(
|
||||
[truncated_df, truncated_row.to_frame().T], ignore_index=True
|
||||
)
|
||||
@@ -104,7 +104,7 @@ class SlackMixin:
|
||||
if len(message) > MAXIMUM_MESSAGE_SIZE:
|
||||
# Decrement i and build a message that is under the limit
|
||||
truncated_df = df[:i].fillna("")
|
||||
truncated_row = pd.Series({k: "..." for k in df.columns})
|
||||
truncated_row = pd.Series(dict.fromkeys(df.columns, "..."))
|
||||
truncated_df = pd.concat(
|
||||
[truncated_df, truncated_row.to_frame().T], ignore_index=True
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user