chore(frontend): comprehensive TypeScript quality improvements (#37625)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Evan Rusackas
2026-02-06 16:16:57 -05:00
committed by GitHub
parent e9ae212c1c
commit fc5506e466
441 changed files with 14136 additions and 9956 deletions

View File

@@ -17,27 +17,67 @@
* specific language governing permissions and limitations
* under the License.
*/
// @ts-nocheck -- Legacy D3 visualization using d3-hierarchy v1 APIs without proper type definitions.
// Uses the same approach as NVD3Vis.ts and other heavily D3-dependent files.
/* eslint no-param-reassign: [2, {"props": false}] */
import d3 from 'd3';
import PropTypes from 'prop-types';
import { hierarchy } from 'd3-hierarchy';
import { hierarchy, HierarchyNode } from 'd3-hierarchy';
import {
getNumberFormatter,
getTimeFormatter,
CategoricalColorNamespace,
} from '@superset-ui/core';
interface PartitionDataNode {
name: string;
val: number;
children?: PartitionDataNode[];
}
interface PartitionNode extends HierarchyNode<PartitionDataNode> {
x: number;
dx: number;
y: number;
dy: number;
weight: number;
sum: number;
name: string;
disp: string | number;
color: string;
children?: PartitionNode[];
parent: PartitionNode | null;
}
interface IcicleProps {
data: PartitionDataNode[];
width: number;
height: number;
colorScheme: string;
dateTimeFormat: string;
equalDateSize: boolean;
levels: string[];
metrics: (string | Record<string, unknown>)[];
numberFormat: string;
partitionLimit: number;
partitionThreshold: number;
timeSeriesOption: string;
useLogScale: boolean;
useRichTooltip: boolean;
sliceId: number;
}
// Compute dx, dy, x, y for each node and
// return an array of nodes in breadth-first order
function init(root) {
const flat = [];
function init(root: PartitionNode): PartitionNode[] {
const flat: PartitionNode[] = [];
const dy = 1 / (root.height + 1);
let prev = null;
root.each(n => {
let prev: PartitionNode | null = null;
root.each((n: PartitionNode) => {
n.y = dy * n.depth;
n.dy = dy;
if (n.parent) {
n.x = prev.depth === n.parent.depth ? 0 : prev.x + prev.dx;
n.x = prev!.depth === n.parent.depth ? 0 : prev!.x + prev!.dx;
n.dx = (n.weight / n.parent.sum) * n.parent.dx;
} else {
n.x = 0;
@@ -52,8 +92,11 @@ function init(root) {
// Declare PropTypes for recursive data structures
// https://github.com/facebook/react/issues/5676
/* eslint-disable-next-line no-undef */
const lazyFunction = f => () => f().apply(this, arguments);
// eslint-disable-next-line func-names
const lazyFunction = (f: () => Record<string, unknown>) =>
function (...args: unknown[]) {
return f().apply(this, args);
};
const leafType = PropTypes.shape({
name: PropTypes.string,
val: PropTypes.number.isRequired,
@@ -89,9 +132,9 @@ const propTypes = {
useRichTooltip: PropTypes.bool,
};
function getAncestors(d) {
const ancestors = [d];
let node = d;
function getAncestors(d: PartitionNode): PartitionNode[] {
const ancestors: PartitionNode[] = [d];
let node: PartitionNode = d;
while (node.parent) {
ancestors.push(node.parent);
node = node.parent;
@@ -102,7 +145,7 @@ function getAncestors(d) {
// This vis is based on
// http://mbostock.github.io/d3/talk/20111018/partition.html
function Icicle(element, props) {
function Icicle(element: HTMLElement, props: IcicleProps): void {
const {
width,
height,
@@ -134,11 +177,11 @@ function Icicle(element, props) {
div.selectAll('*').remove();
const tooltip = div.append('div').classed('partition-tooltip', true);
function hasDateNode(n) {
function hasDateNode(n: PartitionNode): boolean {
return metrics.includes(n.data.name) && hasTime;
}
function getCategory(depth) {
function getCategory(depth: number): string {
if (!depth) {
return 'Metric';
}
@@ -149,7 +192,7 @@ function Icicle(element, props) {
return levels[depth - (hasTime ? 2 : 1)];
}
function drawVis(i, dat) {
function drawVis(i: number, dat: PartitionDataNode[]): void {
const datum = dat[i];
const w = width;
const h = height / data.length;
@@ -262,7 +305,10 @@ function Icicle(element, props) {
: 1;
});
function positionAndPopulate(tip, d) {
function positionAndPopulate(
tip: ReturnType<typeof div.append>,
d: PartitionNode,
): void {
let t = '<table>';
if (useRichTooltip) {
const nodes = getAncestors(d);
@@ -310,7 +356,7 @@ function Icicle(element, props) {
let zoomY = h / 1;
// Keep text centered in its division
function transform(d) {
function transform(d: PartitionNode): string {
return `translate(8,${(d.dx * zoomY) / 2})`;
}
@@ -332,7 +378,7 @@ function Icicle(element, props) {
});
// When clicking a subdivision, the vis will zoom into it
function click(d) {
function click(d: PartitionNode): boolean {
if (!d.children) {
if (d.parent) {
// Clicking on the rightmost level should zoom in

View File

@@ -20,9 +20,22 @@ import { reactify } from '@superset-ui/core';
import { styled } from '@apache-superset/core/ui';
import Component from './Partition';
const ReactComponent = reactify(Component);
// Type-erase the render function to allow flexible prop spreading in the wrapper.
// The Partition render function has typed props, but the wrapper passes props via spread
// which TypeScript cannot verify at compile time. Props are validated at runtime.
const ReactComponent = reactify(
Component as unknown as (
container: HTMLDivElement,
props: Record<string, unknown>,
) => void,
);
const Partition = ({ className, ...otherProps }) => (
interface PartitionWrapperProps {
className?: string;
[key: string]: unknown;
}
const Partition = ({ className, ...otherProps }: PartitionWrapperProps) => (
<div className={className}>
<ReactComponent {...otherProps} />
</div>
@@ -41,7 +54,7 @@ export default styled(Partition)`
}
.superset-legacy-chart-partition rect {
stroke: ${theme.borderColorSecondary};
stroke: ${theme.colorBorderSecondary};
fill: ${theme.colorBgLayout};
fill-opacity: 80%;
transition: fill-opacity 180ms linear;

View File

@@ -16,7 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
export default function transformProps(chartProps) {
import { ChartProps } from '@superset-ui/core';
export default function transformProps(chartProps: ChartProps) {
const { width, height, datasource, formData, queriesData } = chartProps;
const {
colorScheme,
@@ -32,7 +34,7 @@ export default function transformProps(chartProps) {
timeSeriesOption,
sliceId,
} = formData;
const { verboseMap } = datasource;
const { verboseMap = {} } = datasource;
return {
width,
@@ -41,7 +43,7 @@ export default function transformProps(chartProps) {
colorScheme,
dateTimeFormat,
equalDateSize,
levels: groupby.map(g => verboseMap[g] || g),
levels: groupby.map((g: string) => verboseMap[g] || g),
metrics,
numberFormat,
partitionLimit: partitionLimit && parseInt(partitionLimit, 10),

View File

@@ -18,13 +18,14 @@
*/
import '@testing-library/jest-dom';
import { screen, render, fireEvent, act } from '@superset-ui/core/spec';
import type { ColumnMeta } from '@superset-ui/chart-controls';
import OptionDescription from '../src/OptionDescription';
const defaultProps = {
option: {
label: 'Some option',
description: 'Description for some option',
},
} as unknown as ColumnMeta,
};
beforeEach(() => {

View File

@@ -0,0 +1,28 @@
/**
* 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.
*/
declare module '*.png' {
const value: string;
export default value;
}
declare module '*.jpg' {
const value: string;
export default value;
}