mirror of
https://github.com/apache/superset.git
synced 2026-05-12 19:35:17 +00:00
fix(types): add TypeScript interfaces to control components
- Add TestDatasource interface with flexible typing for tests - Add AdhocFilterControlProps/State interfaces with full type coverage - Type all AdhocFilterControl methods with proper signatures - Add SelectOption interface and type helper functions - Add SortComparator type for sort comparison functions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,7 +28,7 @@ import {
|
||||
waitFor,
|
||||
} from 'spec/helpers/testing-library';
|
||||
import { fallbackExploreInitialData } from 'src/explore/fixtures';
|
||||
import type { DatasetObject, ColumnObject } from 'src/features/datasets/types';
|
||||
import type { ColumnObject } from 'src/features/datasets/types';
|
||||
import DatasourceControl from '.';
|
||||
|
||||
const SupersetClientGet = jest.spyOn(SupersetClient, 'get');
|
||||
@@ -46,16 +46,19 @@ afterEach(() => {
|
||||
jest.clearAllMocks(); // Clears mock history but keeps spy in place
|
||||
});
|
||||
|
||||
type TestDatasource = Omit<
|
||||
Partial<DatasetObject>,
|
||||
'columns' | 'main_dttm_col'
|
||||
> & {
|
||||
interface TestDatasource {
|
||||
id?: number;
|
||||
name: string;
|
||||
datasource_name?: string;
|
||||
database: { name: string };
|
||||
columns?: Partial<ColumnObject>[];
|
||||
type?: DatasourceType;
|
||||
main_dttm_col?: string | null;
|
||||
};
|
||||
owners?: Array<{ first_name: string; last_name: string; id: number; username?: string }>;
|
||||
sql?: string;
|
||||
metrics?: Array<{ id: number; metric_name: string }>;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
const mockDatasource: TestDatasource = {
|
||||
id: 25,
|
||||
@@ -69,7 +72,9 @@ const mockDatasource: TestDatasource = {
|
||||
owners: [{ first_name: 'john', last_name: 'doe', id: 1, username: 'jd' }],
|
||||
sql: 'SELECT * FROM mock_datasource_sql',
|
||||
};
|
||||
const createProps = (overrides: JsonObject = {}) => ({
|
||||
|
||||
// Use type assertion for test props since the component is wrapped with withTheme
|
||||
const createProps = (overrides: JsonObject = {}): Record<string, unknown> => ({
|
||||
hovered: false,
|
||||
type: 'DatasourceControl',
|
||||
label: 'Datasource',
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { Component } from 'react';
|
||||
import { Component, ReactNode } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { t, logging, SupersetClient, ensureIsArray } from '@superset-ui/core';
|
||||
import { withTheme } from '@apache-superset/core/ui';
|
||||
import { withTheme, type SupersetTheme } from '@apache-superset/core/ui';
|
||||
|
||||
import ControlHeader from 'src/explore/components/ControlHeader';
|
||||
import adhocMetricType from 'src/explore/components/controls/MetricControl/adhocMetricType';
|
||||
@@ -45,6 +45,59 @@ import columnType from 'src/explore/components/controls/FilterControl/columnType
|
||||
import { toQueryString } from 'src/utils/urlUtils';
|
||||
import { Clauses, ExpressionTypes } from '../types';
|
||||
|
||||
interface ColumnMeta {
|
||||
column_name: string;
|
||||
verbose_name?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface SavedMetric {
|
||||
metric_name: string;
|
||||
expression: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface Datasource {
|
||||
id?: number;
|
||||
type?: string;
|
||||
database?: { id: number };
|
||||
datasource_name?: string;
|
||||
catalog?: string;
|
||||
schema?: string;
|
||||
is_sqllab_view?: boolean;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface AdhocFilterControlProps {
|
||||
label?: ReactNode;
|
||||
name?: string;
|
||||
sections?: string[];
|
||||
operators?: string[];
|
||||
onChange?: (values: AdhocFilter[]) => void;
|
||||
value?: AdhocFilter[];
|
||||
datasource?: Datasource;
|
||||
columns?: ColumnMeta[];
|
||||
savedMetrics?: SavedMetric[];
|
||||
selectedMetrics?: string | AdhocMetric | (string | AdhocMetric)[];
|
||||
isLoading?: boolean;
|
||||
canDelete?: (filter: AdhocFilter, allFilters: AdhocFilter[]) => string | boolean | undefined;
|
||||
theme?: SupersetTheme;
|
||||
}
|
||||
|
||||
interface FilterOption {
|
||||
column_name?: string;
|
||||
saved_metric_name?: string;
|
||||
label?: string;
|
||||
filterOptionName?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface AdhocFilterControlState {
|
||||
values: AdhocFilter[];
|
||||
options: FilterOption[];
|
||||
partitionColumn: string | null;
|
||||
}
|
||||
|
||||
const { warning } = Modal;
|
||||
|
||||
const selectedMetricType = PropTypes.oneOfType([
|
||||
@@ -78,11 +131,11 @@ const defaultProps = {
|
||||
selectedMetrics: [],
|
||||
};
|
||||
|
||||
function isDictionaryForAdhocFilter(value) {
|
||||
return value && !(value instanceof AdhocFilter) && value.expressionType;
|
||||
function isDictionaryForAdhocFilter(value: unknown): boolean {
|
||||
return !!(value && !(value instanceof AdhocFilter) && (value as Record<string, unknown>).expressionType);
|
||||
}
|
||||
|
||||
function optionsForSelect(props) {
|
||||
function optionsForSelect(props: AdhocFilterControlProps): FilterOption[] {
|
||||
const options = [
|
||||
...props.columns,
|
||||
...ensureIsArray(props.selectedMetrics).map(
|
||||
@@ -121,8 +174,11 @@ function optionsForSelect(props) {
|
||||
);
|
||||
}
|
||||
|
||||
class AdhocFilterControl extends Component {
|
||||
constructor(props) {
|
||||
class AdhocFilterControl extends Component<AdhocFilterControlProps, AdhocFilterControlState> {
|
||||
optionRenderer: (option: FilterOption) => JSX.Element;
|
||||
valueRenderer: (adhocFilter: AdhocFilter, index: number) => JSX.Element;
|
||||
|
||||
constructor(props: AdhocFilterControlProps) {
|
||||
super(props);
|
||||
this.onRemoveFilter = this.onRemoveFilter.bind(this);
|
||||
this.onNewFilter = this.onNewFilter.bind(this);
|
||||
@@ -206,7 +262,7 @@ class AdhocFilterControl extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps: AdhocFilterControlProps): void {
|
||||
if (this.props.columns !== prevProps.columns) {
|
||||
this.setState({ options: optionsForSelect(this.props) });
|
||||
}
|
||||
@@ -219,7 +275,7 @@ class AdhocFilterControl extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
removeFilter(index) {
|
||||
removeFilter(index: number): void {
|
||||
const valuesCopy = [...this.state.values];
|
||||
valuesCopy.splice(index, 1);
|
||||
this.setState(prevState => ({
|
||||
@@ -229,7 +285,7 @@ class AdhocFilterControl extends Component {
|
||||
this.props.onChange(valuesCopy);
|
||||
}
|
||||
|
||||
onRemoveFilter(index) {
|
||||
onRemoveFilter(index: number): void {
|
||||
const { canDelete } = this.props;
|
||||
const { values } = this.state;
|
||||
const result = canDelete?.(values[index], values);
|
||||
@@ -240,7 +296,7 @@ class AdhocFilterControl extends Component {
|
||||
this.removeFilter(index);
|
||||
}
|
||||
|
||||
onNewFilter(newFilter) {
|
||||
onNewFilter(newFilter: FilterOption): void {
|
||||
const mappedOption = this.mapOption(newFilter);
|
||||
if (mappedOption) {
|
||||
this.setState(
|
||||
@@ -255,7 +311,7 @@ class AdhocFilterControl extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
onFilterEdit(changedFilter) {
|
||||
onFilterEdit(changedFilter: AdhocFilter): void {
|
||||
this.props.onChange(
|
||||
this.state.values.map(value => {
|
||||
if (value.filterOptionName === changedFilter.filterOptionName) {
|
||||
@@ -266,20 +322,20 @@ class AdhocFilterControl extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
onChange(opts) {
|
||||
onChange(opts: FilterOption[] | null): void {
|
||||
const options = (opts || [])
|
||||
.map(option => this.mapOption(option))
|
||||
.filter(option => option);
|
||||
this.props.onChange(options);
|
||||
}
|
||||
|
||||
getMetricExpression(savedMetricName) {
|
||||
getMetricExpression(savedMetricName: string): string {
|
||||
return this.props.savedMetrics.find(
|
||||
savedMetric => savedMetric.metric_name === savedMetricName,
|
||||
).expression;
|
||||
}
|
||||
|
||||
moveLabel(dragIndex, hoverIndex) {
|
||||
moveLabel(dragIndex: number, hoverIndex: number): void {
|
||||
const { values } = this.state;
|
||||
|
||||
const newValues = [...values];
|
||||
@@ -290,7 +346,7 @@ class AdhocFilterControl extends Component {
|
||||
this.setState({ values: newValues });
|
||||
}
|
||||
|
||||
mapOption(option) {
|
||||
mapOption(option: FilterOption | AdhocFilter): AdhocFilter | null {
|
||||
// already a AdhocFilter, skip
|
||||
if (option instanceof AdhocFilter) {
|
||||
return option;
|
||||
@@ -331,7 +387,7 @@ class AdhocFilterControl extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
addNewFilterPopoverTrigger(trigger) {
|
||||
addNewFilterPopoverTrigger(trigger: ReactNode): JSX.Element {
|
||||
return (
|
||||
<AdhocFilterPopoverTrigger
|
||||
operators={this.props.operators}
|
||||
|
||||
@@ -129,9 +129,15 @@ const defaultProps = {
|
||||
valueKey: 'value',
|
||||
};
|
||||
|
||||
const numberComparator = (a, b) => a.value - b.value;
|
||||
interface SelectOption {
|
||||
value: string | number;
|
||||
label: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export const areAllValuesNumbers = (items, valueKey = 'value') => {
|
||||
const numberComparator = (a: SelectOption, b: SelectOption): number => a.value as number - (b.value as number);
|
||||
|
||||
export const areAllValuesNumbers = (items: unknown[], valueKey = 'value'): boolean => {
|
||||
if (!items || items.length === 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -147,12 +153,14 @@ export const areAllValuesNumbers = (items, valueKey = 'value') => {
|
||||
});
|
||||
};
|
||||
|
||||
type SortComparator = ((a: SelectOption, b: SelectOption) => number) | undefined;
|
||||
|
||||
export const getSortComparator = (
|
||||
choices,
|
||||
options,
|
||||
valueKey,
|
||||
explicitComparator,
|
||||
) => {
|
||||
choices: unknown[] | undefined,
|
||||
options: unknown[] | undefined,
|
||||
valueKey: string | undefined,
|
||||
explicitComparator: SortComparator,
|
||||
): SortComparator => {
|
||||
if (explicitComparator) {
|
||||
return explicitComparator;
|
||||
}
|
||||
@@ -167,7 +175,7 @@ export const getSortComparator = (
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const innerGetOptions = props => {
|
||||
export const innerGetOptions = (props: SelectControlProps): SelectOption[] => {
|
||||
const { choices, optionRenderer, valueKey } = props;
|
||||
let options = [];
|
||||
if (props.options) {
|
||||
|
||||
Reference in New Issue
Block a user