mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-07-27 00:19:55 +03:00
feat(rtk-query): add actions tab
This commit is contained in:
parent
71b483bf02
commit
7a274d9025
|
@ -19,6 +19,10 @@ import { NoRtkQueryApi } from './NoRtkQueryApi';
|
||||||
import { InspectorSelectors } from '../selectors';
|
import { InspectorSelectors } from '../selectors';
|
||||||
import { StylingFunction } from 'react-base16-styling';
|
import { StylingFunction } from 'react-base16-styling';
|
||||||
import { mapProps } from '../containers/mapProps';
|
import { mapProps } from '../containers/mapProps';
|
||||||
|
import {
|
||||||
|
QueryPreviewActions,
|
||||||
|
QueryPreviewActionsProps,
|
||||||
|
} from './QueryPreviewActions';
|
||||||
|
|
||||||
export interface QueryPreviewProps<S = unknown> {
|
export interface QueryPreviewProps<S = unknown> {
|
||||||
readonly selectedTab: QueryPreviewTabs;
|
readonly selectedTab: QueryPreviewTabs;
|
||||||
|
@ -70,12 +74,25 @@ const MappedApiPreview = mapProps<QueryPreviewTabProps, QueryPreviewApiProps>(
|
||||||
})
|
})
|
||||||
)(QueryPreviewApi);
|
)(QueryPreviewApi);
|
||||||
|
|
||||||
|
const MappedQueryPreviewActions = mapProps<
|
||||||
|
QueryPreviewTabProps,
|
||||||
|
QueryPreviewActionsProps
|
||||||
|
>(({ isWideLayout, selectorsSource, selectors }) => ({
|
||||||
|
isWideLayout,
|
||||||
|
actionsOfQuery: selectors.selectActionsOfCurrentQuery(selectorsSource),
|
||||||
|
}))(QueryPreviewActions);
|
||||||
|
|
||||||
const tabs: ReadonlyArray<TabOption<QueryPreviewTabs, QueryPreviewTabProps>> = [
|
const tabs: ReadonlyArray<TabOption<QueryPreviewTabs, QueryPreviewTabProps>> = [
|
||||||
{
|
{
|
||||||
label: 'query',
|
label: 'query',
|
||||||
value: QueryPreviewTabs.queryinfo,
|
value: QueryPreviewTabs.queryinfo,
|
||||||
component: MappedQueryPreviewInfo,
|
component: MappedQueryPreviewInfo,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'actions',
|
||||||
|
value: QueryPreviewTabs.actions,
|
||||||
|
component: MappedQueryPreviewActions,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'tags',
|
label: 'tags',
|
||||||
value: QueryPreviewTabs.queryTags,
|
value: QueryPreviewTabs.queryTags,
|
||||||
|
@ -112,7 +129,7 @@ export class QueryPreview<S> extends React.PureComponent<QueryPreviewProps<S>> {
|
||||||
counterAsString = counterAsString.slice(0, 2) + '...';
|
counterAsString = counterAsString.slice(0, 2) + '...';
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${label} (${counterAsString})`;
|
return `${label}(${counterAsString})`;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderTabLabel = (tab: TabOption<QueryPreviewTabs, unknown>): ReactNode => {
|
renderTabLabel = (tab: TabOption<QueryPreviewTabs, unknown>): ReactNode => {
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import React, { ReactNode, PureComponent } from 'react';
|
||||||
|
import { AnyAction } from 'redux';
|
||||||
|
import { TreeView } from './TreeView';
|
||||||
|
|
||||||
|
export interface QueryPreviewActionsProps {
|
||||||
|
isWideLayout: boolean;
|
||||||
|
actionsOfQuery: AnyAction[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class QueryPreviewActions extends PureComponent<QueryPreviewActionsProps> {
|
||||||
|
render(): ReactNode {
|
||||||
|
const { isWideLayout, actionsOfQuery } = this.props;
|
||||||
|
|
||||||
|
return <TreeView data={actionsOfQuery} isWideLayout={isWideLayout} />;
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,7 +21,7 @@ import { QueryPreview } from '../components/QueryPreview';
|
||||||
|
|
||||||
type ForwardedMonitorProps<S, A extends Action<unknown>> = Pick<
|
type ForwardedMonitorProps<S, A extends Action<unknown>> = Pick<
|
||||||
LiftedState<S, A, RtkQueryMonitorState>,
|
LiftedState<S, A, RtkQueryMonitorState>,
|
||||||
'monitorState' | 'currentStateIndex' | 'computedStates'
|
'monitorState' | 'currentStateIndex' | 'computedStates' | 'actionsById'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export interface RtkQueryInspectorProps<S, A extends Action<unknown>>
|
export interface RtkQueryInspectorProps<S, A extends Action<unknown>>
|
||||||
|
@ -124,6 +124,9 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends PureComponent<
|
||||||
|
|
||||||
const hasNoApi = apiStates == null;
|
const hasNoApi = apiStates == null;
|
||||||
|
|
||||||
|
const actionsOfCurrentQuery =
|
||||||
|
this.selectors.selectActionsOfCurrentQuery(selectorsSource);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={this.inspectorRef}
|
ref={this.inspectorRef}
|
||||||
|
|
|
@ -55,8 +55,13 @@ class RtkQueryMonitor<S, A extends Action<unknown>> extends Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { currentStateIndex, computedStates, monitorState, dispatch } =
|
const {
|
||||||
this.props;
|
currentStateIndex,
|
||||||
|
computedStates,
|
||||||
|
monitorState,
|
||||||
|
dispatch,
|
||||||
|
actionsById,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyleUtilsContext.Provider value={this.state.styleUtils}>
|
<StyleUtilsContext.Provider value={this.state.styleUtils}>
|
||||||
|
@ -66,6 +71,7 @@ class RtkQueryMonitor<S, A extends Action<unknown>> extends Component<
|
||||||
monitorState={monitorState}
|
monitorState={monitorState}
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
styleUtils={this.state.styleUtils}
|
styleUtils={this.state.styleUtils}
|
||||||
|
actionsById={actionsById}
|
||||||
/>
|
/>
|
||||||
</StyleUtilsContext.Provider>
|
</StyleUtilsContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {
|
||||||
flipComparator,
|
flipComparator,
|
||||||
getQueryTagsOf,
|
getQueryTagsOf,
|
||||||
generateApiStatsOfCurrentQuery,
|
generateApiStatsOfCurrentQuery,
|
||||||
|
getActionsOfCurrentQuery,
|
||||||
} from './utils/rtk-query';
|
} from './utils/rtk-query';
|
||||||
|
|
||||||
type InspectorSelector<S, Output> = Selector<SelectorsSource<S>, Output>;
|
type InspectorSelector<S, Output> = Selector<SelectorsSource<S>, Output>;
|
||||||
|
@ -27,7 +28,8 @@ export function computeSelectorSource<S, A extends Action<unknown>>(
|
||||||
props: RtkQueryInspectorProps<S, A>,
|
props: RtkQueryInspectorProps<S, A>,
|
||||||
previous: SelectorsSource<S> | null = null
|
previous: SelectorsSource<S> | null = null
|
||||||
): SelectorsSource<S> {
|
): SelectorsSource<S> {
|
||||||
const { computedStates, currentStateIndex, monitorState } = props;
|
const { computedStates, currentStateIndex, monitorState, actionsById } =
|
||||||
|
props;
|
||||||
|
|
||||||
const userState =
|
const userState =
|
||||||
computedStates.length > 0 ? computedStates[currentStateIndex].state : null;
|
computedStates.length > 0 ? computedStates[currentStateIndex].state : null;
|
||||||
|
@ -35,11 +37,13 @@ export function computeSelectorSource<S, A extends Action<unknown>>(
|
||||||
if (
|
if (
|
||||||
!previous ||
|
!previous ||
|
||||||
previous.userState !== userState ||
|
previous.userState !== userState ||
|
||||||
previous.monitorState !== monitorState
|
previous.monitorState !== monitorState ||
|
||||||
|
previous.actionsById !== actionsById
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
userState,
|
userState,
|
||||||
monitorState,
|
monitorState,
|
||||||
|
actionsById,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +77,10 @@ export interface InspectorSelectors<S> {
|
||||||
S,
|
S,
|
||||||
RtkQueryApiState['subscriptions'][string]
|
RtkQueryApiState['subscriptions'][string]
|
||||||
>;
|
>;
|
||||||
|
readonly selectActionsOfCurrentQuery: InspectorSelector<
|
||||||
|
S,
|
||||||
|
ReturnType<typeof getActionsOfCurrentQuery>
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
||||||
|
@ -88,6 +96,9 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
||||||
return queryListFilters[monitorState.queryForm.values.queryFilter];
|
return queryListFilters[monitorState.queryForm.values.queryFilter];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectActionsById = ({ actionsById }: SelectorsSource<S>) =>
|
||||||
|
actionsById;
|
||||||
|
|
||||||
const selectApiStates = createSelector(
|
const selectApiStates = createSelector(
|
||||||
({ userState }: SelectorsSource<S>) => userState,
|
({ userState }: SelectorsSource<S>) => userState,
|
||||||
getApiStatesOf
|
getApiStatesOf
|
||||||
|
@ -192,20 +203,29 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
||||||
generateApiStatsOfCurrentQuery
|
generateApiStatsOfCurrentQuery
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectTabCounters = (
|
const selectActionsOfCurrentQuery = createSelector(
|
||||||
selectorsSource: SelectorsSource<S>
|
selectCurrentQueryInfo,
|
||||||
): Record<QueryPreviewTabs, number> => {
|
selectActionsById,
|
||||||
const subscriptions = selectSubscriptionsOfCurrentQuery(selectorsSource);
|
getActionsOfCurrentQuery
|
||||||
const subsLen = Object.keys(subscriptions ?? {}).length;
|
);
|
||||||
|
|
||||||
return {
|
const selectTabCounters = createSelector(
|
||||||
[QueryPreviewTabs.queryTags]:
|
[
|
||||||
selectCurrentQueryTags(selectorsSource).length,
|
selectSubscriptionsOfCurrentQuery,
|
||||||
[QueryPreviewTabs.querySubscriptions]: subsLen,
|
selectActionsOfCurrentQuery,
|
||||||
[QueryPreviewTabs.apiConfig]: 0,
|
selectCurrentQueryTags,
|
||||||
[QueryPreviewTabs.queryinfo]: 0,
|
],
|
||||||
};
|
(subscriptions, actions, tags) => {
|
||||||
};
|
return {
|
||||||
|
[QueryPreviewTabs.queryTags]: tags.length,
|
||||||
|
[QueryPreviewTabs.querySubscriptions]: Object.keys(subscriptions ?? {})
|
||||||
|
.length,
|
||||||
|
[QueryPreviewTabs.apiConfig]: 0,
|
||||||
|
[QueryPreviewTabs.queryinfo]: 0,
|
||||||
|
[QueryPreviewTabs.actions]: actions.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectQueryComparator,
|
selectQueryComparator,
|
||||||
|
@ -219,5 +239,6 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
||||||
selectSubscriptionsOfCurrentQuery,
|
selectSubscriptionsOfCurrentQuery,
|
||||||
selectApiOfCurrentQuery,
|
selectApiOfCurrentQuery,
|
||||||
selectTabCounters,
|
selectTabCounters,
|
||||||
|
selectActionsOfCurrentQuery,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ const getSheetFromColorMap = (map: ColorMap) => {
|
||||||
|
|
||||||
'&[data-wide-layout="1"]': {
|
'&[data-wide-layout="1"]': {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
width: '45%',
|
width: '44%',
|
||||||
borderRightWidth: 1,
|
borderRightWidth: 1,
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
},
|
},
|
||||||
|
@ -180,7 +180,7 @@ const getSheetFromColorMap = (map: ColorMap) => {
|
||||||
selectorButton: {
|
selectorButton: {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
padding: '6.5px 10px',
|
padding: '6.5px 8px',
|
||||||
color: map.TEXT_COLOR,
|
color: map.TEXT_COLOR,
|
||||||
'border-style': 'solid',
|
'border-style': 'solid',
|
||||||
'border-width': '1px',
|
'border-width': '1px',
|
||||||
|
@ -351,7 +351,7 @@ const getSheetFromColorMap = (map: ColorMap) => {
|
||||||
|
|
||||||
previewHeader: {
|
previewHeader: {
|
||||||
flex: '0 0 30px',
|
flex: '0 0 30px',
|
||||||
padding: '5px 10px',
|
padding: '5px 4px',
|
||||||
'align-items': 'center',
|
'align-items': 'center',
|
||||||
'border-bottom-width': '1px',
|
'border-bottom-width': '1px',
|
||||||
'border-bottom-style': 'solid',
|
'border-bottom-style': 'solid',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { LiftedAction, LiftedState } from '@redux-devtools/instrument';
|
import type { LiftedAction, LiftedState } from '@redux-devtools/instrument';
|
||||||
import type { createApi, QueryStatus } from '@reduxjs/toolkit/query';
|
import type { createApi, QueryStatus } from '@reduxjs/toolkit/query';
|
||||||
import type { Action, Dispatch } from '@reduxjs/toolkit';
|
import type { Action, AnyAction, Dispatch } from '@reduxjs/toolkit';
|
||||||
import type { ComponentType } from 'react';
|
import type { ComponentType } from 'react';
|
||||||
import type { Base16Theme, StylingFunction } from 'react-base16-styling';
|
import type { Base16Theme, StylingFunction } from 'react-base16-styling';
|
||||||
import type * as themes from 'redux-devtools-themes';
|
import type * as themes from 'redux-devtools-themes';
|
||||||
|
@ -12,6 +12,7 @@ export enum QueryPreviewTabs {
|
||||||
apiConfig,
|
apiConfig,
|
||||||
querySubscriptions,
|
querySubscriptions,
|
||||||
queryTags,
|
queryTags,
|
||||||
|
actions,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryFormValues {
|
export interface QueryFormValues {
|
||||||
|
@ -83,6 +84,7 @@ export interface SelectOption<T = string> {
|
||||||
export interface SelectorsSource<S> {
|
export interface SelectorsSource<S> {
|
||||||
userState: S | null;
|
userState: S | null;
|
||||||
monitorState: RtkQueryMonitorState;
|
monitorState: RtkQueryMonitorState;
|
||||||
|
actionsById: LiftedState<unknown, AnyAction, unknown>['actionsById'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StyleUtils {
|
export interface StyleUtils {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { AnyAction, isPlainObject } from '@reduxjs/toolkit';
|
import { AnyAction, isAnyOf, isPlainObject } from '@reduxjs/toolkit';
|
||||||
import { QueryStatus } from '@reduxjs/toolkit/query';
|
import { QueryStatus } from '@reduxjs/toolkit/query';
|
||||||
import {
|
import {
|
||||||
QueryInfo,
|
QueryInfo,
|
||||||
|
@ -14,6 +14,7 @@ import {
|
||||||
RtkQueryProvided,
|
RtkQueryProvided,
|
||||||
ApiTimings,
|
ApiTimings,
|
||||||
QueryTimings,
|
QueryTimings,
|
||||||
|
SelectorsSource,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { missingTagId } from '../monitor-config';
|
import { missingTagId } from '../monitor-config';
|
||||||
import { Comparator } from './comparators';
|
import { Comparator } from './comparators';
|
||||||
|
@ -423,3 +424,39 @@ export function getQueryStatusFlags({
|
||||||
isError: status === QueryStatus.rejected,
|
isError: status === QueryStatus.rejected,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* endpoint matcher
|
||||||
|
* @param endpointName
|
||||||
|
* @see https://github.com/reduxjs/redux-toolkit/blob/b718e01d323d3ab4b913e5d88c9b90aa790bb975/src/query/core/buildThunks.ts#L415
|
||||||
|
*/
|
||||||
|
export function matchesEndpoint(endpointName: unknown) {
|
||||||
|
return (action: any): action is AnyAction =>
|
||||||
|
endpointName != null && action?.meta?.arg?.endpointName === endpointName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchesQueryKey(queryKey: string) {
|
||||||
|
return (action: any): action is AnyAction =>
|
||||||
|
action?.meta?.arg?.queryCacheKey === queryKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getActionsOfCurrentQuery(
|
||||||
|
currentQuery: QueryInfo | null,
|
||||||
|
actionById: SelectorsSource<unknown>['actionsById']
|
||||||
|
): AnyAction[] {
|
||||||
|
if (!currentQuery) {
|
||||||
|
return emptyArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matcher = isAnyOf(matchesQueryKey(currentQuery.queryKey));
|
||||||
|
|
||||||
|
const output: AnyAction[] = [];
|
||||||
|
|
||||||
|
for (const [, liftedAction] of Object.entries(actionById)) {
|
||||||
|
if (matcher(liftedAction?.action)) {
|
||||||
|
output.push(liftedAction.action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.length === 0 ? emptyArray : output;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user