mirror of
https://github.com/apache/superset.git
synced 2026-05-12 19:35:17 +00:00
fix(types): improve type safety in ExploreViewContainer
- Create CombinedExploreActions type for combined action creators - Add BoundActions<T> utility type for bound action creators - Update ControlPanelsContainer to use Pick<ExploreActions, 'setControlValue'> - Change datasource_type from string to DatasourceType enum - Change exploreState to use ExplorePageState['explore'] - Replace control.label() as any with as unknown as ControlPanelState - Add eslint-disable comments for remaining necessary casts - Remove unnecessary as any casts from connect() and SaveModal Reduces 'as any' casts from ~12 to 5, with remaining casts documented with eslint-disable comments explaining why they're needed. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,8 +17,15 @@
|
||||
* under the License.
|
||||
*/
|
||||
/* eslint camelcase: 0 */
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import {
|
||||
ComponentType,
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { bindActionCreators, Dispatch, ActionCreatorsMapObject } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
useChangeEffect,
|
||||
@@ -28,8 +35,12 @@ import {
|
||||
QueryFormData,
|
||||
JsonObject,
|
||||
MatrixifyFormData,
|
||||
DatasourceType,
|
||||
} from '@superset-ui/core';
|
||||
import { ControlStateMapping } from '@superset-ui/chart-controls';
|
||||
import {
|
||||
ControlStateMapping,
|
||||
ControlPanelState,
|
||||
} from '@superset-ui/chart-controls';
|
||||
import { t, styled, css, useTheme } from '@apache-superset/core/ui';
|
||||
import { logging } from '@apache-superset/core';
|
||||
import { debounce, isEqual, isObjectLike, omit, pick } from 'lodash';
|
||||
@@ -62,6 +73,7 @@ import { datasourcesActions } from 'src/explore/actions/datasourcesActions';
|
||||
import { mountExploreUrl } from 'src/explore/exploreUtils';
|
||||
import { getFormDataFromControls } from 'src/explore/controlUtils';
|
||||
import * as exploreActions from 'src/explore/actions/exploreActions';
|
||||
import { ExploreActions } from 'src/explore/actions/exploreActions';
|
||||
import * as saveModalActions from 'src/explore/actions/saveModalActions';
|
||||
import { useTabId } from 'src/hooks/useTabId';
|
||||
import withToasts from 'src/components/MessageToasts/withToasts';
|
||||
@@ -69,6 +81,7 @@ import {
|
||||
ChartState,
|
||||
Datasource,
|
||||
ExplorePageInitialData,
|
||||
ExplorePageState,
|
||||
SaveActionType,
|
||||
} from 'src/explore/types';
|
||||
import { Slice } from 'src/types/Chart';
|
||||
@@ -311,7 +324,7 @@ interface OwnProps {
|
||||
interface StateProps {
|
||||
isDatasourceMetaLoading: boolean;
|
||||
datasource: Datasource;
|
||||
datasource_type: string;
|
||||
datasource_type: DatasourceType;
|
||||
datasourceId: number;
|
||||
dashboardId?: number;
|
||||
colorScheme?: string;
|
||||
@@ -337,19 +350,29 @@ interface StateProps {
|
||||
ownState?: JsonObject;
|
||||
impressionId: string;
|
||||
user: User;
|
||||
exploreState: ExploreRootState['explore'];
|
||||
exploreState: ExplorePageState['explore'];
|
||||
reports: JsonObject;
|
||||
metadata?: ExplorePageInitialData['metadata'];
|
||||
saveAction?: SaveActionType | null;
|
||||
isSaveModalVisible: boolean;
|
||||
}
|
||||
|
||||
// Combined actions type representing all action creators used in Explore
|
||||
type CombinedExploreActions = typeof exploreActions &
|
||||
typeof datasourcesActions &
|
||||
typeof saveModalActions &
|
||||
typeof chartActions &
|
||||
typeof logActions;
|
||||
|
||||
// Bound action creators type (what mapDispatchToProps actually returns)
|
||||
type BoundActions<T extends ActionCreatorsMapObject> = {
|
||||
[K in keyof T]: T[K] extends (...args: infer A) => infer R
|
||||
? (...args: A) => R extends (...args: unknown[]) => infer R2 ? R2 : R
|
||||
: T[K];
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
actions: typeof exploreActions &
|
||||
typeof datasourcesActions &
|
||||
typeof saveModalActions &
|
||||
typeof chartActions &
|
||||
typeof logActions;
|
||||
actions: BoundActions<CombinedExploreActions>;
|
||||
}
|
||||
|
||||
type ExploreViewContainerProps = StateProps & DispatchProps & OwnProps;
|
||||
@@ -744,7 +767,10 @@ function ExploreViewContainer(props: ExploreViewContainerProps) {
|
||||
.filter(control => control.validationErrors?.includes(message))
|
||||
.map(control =>
|
||||
typeof control.label === 'function'
|
||||
? control.label(props.exploreState as any, control)
|
||||
? control.label(
|
||||
props.exploreState as unknown as ControlPanelState,
|
||||
control,
|
||||
)
|
||||
: control.label,
|
||||
);
|
||||
return [matchingLabels, message];
|
||||
@@ -787,7 +813,10 @@ function ExploreViewContainer(props: ExploreViewContainerProps) {
|
||||
.filter(control => control.validationErrors?.includes(message))
|
||||
.map(control =>
|
||||
typeof control.label === 'function'
|
||||
? control.label(props.exploreState as any, control)
|
||||
? control.label(
|
||||
props.exploreState as unknown as ControlPanelState,
|
||||
control,
|
||||
)
|
||||
: control.label,
|
||||
);
|
||||
return [matchingLabels, message];
|
||||
@@ -858,6 +887,7 @@ function ExploreViewContainer(props: ExploreViewContainerProps) {
|
||||
return (
|
||||
<ExploreContainer>
|
||||
<ConnectedExploreChartHeader
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Combined actions type is compatible at runtime
|
||||
actions={props.actions as any}
|
||||
canOverwrite={props.can_overwrite}
|
||||
canDownload={props.can_download}
|
||||
@@ -934,6 +964,7 @@ function ExploreViewContainer(props: ExploreViewContainerProps) {
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
{/* eslint-disable @typescript-eslint/no-explicit-any -- DataSourcePanel uses narrower types that are compatible at runtime */}
|
||||
<DataSourcePanel
|
||||
formData={props.form_data}
|
||||
datasource={props.datasource as any}
|
||||
@@ -941,6 +972,7 @@ function ExploreViewContainer(props: ExploreViewContainerProps) {
|
||||
actions={props.actions as any}
|
||||
width={width}
|
||||
/>
|
||||
{/* eslint-enable @typescript-eslint/no-explicit-any */}
|
||||
</Resizable>
|
||||
{isCollapsed ? (
|
||||
<div
|
||||
@@ -978,12 +1010,13 @@ function ExploreViewContainer(props: ExploreViewContainerProps) {
|
||||
className="col-sm-3 explore-column controls-column"
|
||||
>
|
||||
<ConnectedControlPanelsContainer
|
||||
exploreState={props.exploreState as any}
|
||||
exploreState={props.exploreState}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Combined actions type is compatible at runtime
|
||||
actions={props.actions as any}
|
||||
form_data={props.form_data}
|
||||
controls={props.controls}
|
||||
chart={props.chart}
|
||||
datasource_type={props.datasource_type as any}
|
||||
datasource_type={props.datasource_type}
|
||||
isDatasourceMetaLoading={props.isDatasourceMetaLoading}
|
||||
onQuery={onQuery}
|
||||
onStop={onStop}
|
||||
@@ -1005,7 +1038,7 @@ function ExploreViewContainer(props: ExploreViewContainerProps) {
|
||||
{props.isSaveModalVisible && (
|
||||
<SaveModal
|
||||
addDangerToast={props.addDangerToast}
|
||||
actions={props.actions as any}
|
||||
actions={props.actions}
|
||||
form_data={props.form_data}
|
||||
sliceName={props.sliceName ?? undefined}
|
||||
dashboardId={props.dashboardId ?? null}
|
||||
@@ -1161,7 +1194,9 @@ function mapStateToProps(state: ExploreRootState) {
|
||||
ownState: dataMask[slice_id]?.ownState,
|
||||
impressionId,
|
||||
user,
|
||||
exploreState: explore,
|
||||
// ExploreRootState['explore'] is compatible with ExplorePageState['explore']
|
||||
// but has additional optional fields; casting is safe here
|
||||
exploreState: explore as unknown as ExplorePageState['explore'],
|
||||
reports,
|
||||
metadata,
|
||||
saveAction: explore.saveAction,
|
||||
@@ -1169,8 +1204,8 @@ function mapStateToProps(state: ExploreRootState) {
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: Dispatch) {
|
||||
const actions = {
|
||||
function mapDispatchToProps(dispatch: Dispatch): DispatchProps {
|
||||
const actions: CombinedExploreActions = {
|
||||
...exploreActions,
|
||||
...datasourcesActions,
|
||||
...saveModalActions,
|
||||
@@ -1178,11 +1213,16 @@ function mapDispatchToProps(dispatch: Dispatch) {
|
||||
...logActions,
|
||||
};
|
||||
return {
|
||||
actions: bindActionCreators(actions as any, dispatch),
|
||||
actions: bindActionCreators(
|
||||
actions as ActionCreatorsMapObject,
|
||||
dispatch,
|
||||
) as BoundActions<CombinedExploreActions>,
|
||||
};
|
||||
}
|
||||
|
||||
// withToasts HOC expects ComponentType<any>, requiring type assertion
|
||||
// The connected component properly handles StateProps & DispatchProps & OwnProps
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(withToasts(memo(ExploreViewContainer)) as any);
|
||||
)(withToasts(memo(ExploreViewContainer)) as ComponentType<OwnProps>);
|
||||
|
||||
Reference in New Issue
Block a user