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 { StylingFunction } from 'react-base16-styling';
|
||||
import { mapProps } from '../containers/mapProps';
|
||||
import {
|
||||
QueryPreviewActions,
|
||||
QueryPreviewActionsProps,
|
||||
} from './QueryPreviewActions';
|
||||
|
||||
export interface QueryPreviewProps<S = unknown> {
|
||||
readonly selectedTab: QueryPreviewTabs;
|
||||
|
@ -70,12 +74,25 @@ const MappedApiPreview = mapProps<QueryPreviewTabProps, QueryPreviewApiProps>(
|
|||
})
|
||||
)(QueryPreviewApi);
|
||||
|
||||
const MappedQueryPreviewActions = mapProps<
|
||||
QueryPreviewTabProps,
|
||||
QueryPreviewActionsProps
|
||||
>(({ isWideLayout, selectorsSource, selectors }) => ({
|
||||
isWideLayout,
|
||||
actionsOfQuery: selectors.selectActionsOfCurrentQuery(selectorsSource),
|
||||
}))(QueryPreviewActions);
|
||||
|
||||
const tabs: ReadonlyArray<TabOption<QueryPreviewTabs, QueryPreviewTabProps>> = [
|
||||
{
|
||||
label: 'query',
|
||||
value: QueryPreviewTabs.queryinfo,
|
||||
component: MappedQueryPreviewInfo,
|
||||
},
|
||||
{
|
||||
label: 'actions',
|
||||
value: QueryPreviewTabs.actions,
|
||||
component: MappedQueryPreviewActions,
|
||||
},
|
||||
{
|
||||
label: 'tags',
|
||||
value: QueryPreviewTabs.queryTags,
|
||||
|
|
|
@ -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<
|
||||
LiftedState<S, A, RtkQueryMonitorState>,
|
||||
'monitorState' | 'currentStateIndex' | 'computedStates'
|
||||
'monitorState' | 'currentStateIndex' | 'computedStates' | 'actionsById'
|
||||
>;
|
||||
|
||||
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 actionsOfCurrentQuery =
|
||||
this.selectors.selectActionsOfCurrentQuery(selectorsSource);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.inspectorRef}
|
||||
|
|
|
@ -55,8 +55,13 @@ class RtkQueryMonitor<S, A extends Action<unknown>> extends Component<
|
|||
}
|
||||
|
||||
render() {
|
||||
const { currentStateIndex, computedStates, monitorState, dispatch } =
|
||||
this.props;
|
||||
const {
|
||||
currentStateIndex,
|
||||
computedStates,
|
||||
monitorState,
|
||||
dispatch,
|
||||
actionsById,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<StyleUtilsContext.Provider value={this.state.styleUtils}>
|
||||
|
@ -66,6 +71,7 @@ class RtkQueryMonitor<S, A extends Action<unknown>> extends Component<
|
|||
monitorState={monitorState}
|
||||
dispatch={dispatch}
|
||||
styleUtils={this.state.styleUtils}
|
||||
actionsById={actionsById}
|
||||
/>
|
||||
</StyleUtilsContext.Provider>
|
||||
);
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
flipComparator,
|
||||
getQueryTagsOf,
|
||||
generateApiStatsOfCurrentQuery,
|
||||
getActionsOfCurrentQuery,
|
||||
} from './utils/rtk-query';
|
||||
|
||||
type InspectorSelector<S, Output> = Selector<SelectorsSource<S>, Output>;
|
||||
|
@ -27,7 +28,8 @@ export function computeSelectorSource<S, A extends Action<unknown>>(
|
|||
props: RtkQueryInspectorProps<S, A>,
|
||||
previous: SelectorsSource<S> | null = null
|
||||
): SelectorsSource<S> {
|
||||
const { computedStates, currentStateIndex, monitorState } = props;
|
||||
const { computedStates, currentStateIndex, monitorState, actionsById } =
|
||||
props;
|
||||
|
||||
const userState =
|
||||
computedStates.length > 0 ? computedStates[currentStateIndex].state : null;
|
||||
|
@ -35,11 +37,13 @@ export function computeSelectorSource<S, A extends Action<unknown>>(
|
|||
if (
|
||||
!previous ||
|
||||
previous.userState !== userState ||
|
||||
previous.monitorState !== monitorState
|
||||
previous.monitorState !== monitorState ||
|
||||
previous.actionsById !== actionsById
|
||||
) {
|
||||
return {
|
||||
userState,
|
||||
monitorState,
|
||||
actionsById,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -73,6 +77,10 @@ export interface InspectorSelectors<S> {
|
|||
S,
|
||||
RtkQueryApiState['subscriptions'][string]
|
||||
>;
|
||||
readonly selectActionsOfCurrentQuery: InspectorSelector<
|
||||
S,
|
||||
ReturnType<typeof getActionsOfCurrentQuery>
|
||||
>;
|
||||
}
|
||||
|
||||
export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
||||
|
@ -88,6 +96,9 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
|||
return queryListFilters[monitorState.queryForm.values.queryFilter];
|
||||
};
|
||||
|
||||
const selectActionsById = ({ actionsById }: SelectorsSource<S>) =>
|
||||
actionsById;
|
||||
|
||||
const selectApiStates = createSelector(
|
||||
({ userState }: SelectorsSource<S>) => userState,
|
||||
getApiStatesOf
|
||||
|
@ -192,20 +203,29 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
|||
generateApiStatsOfCurrentQuery
|
||||
);
|
||||
|
||||
const selectTabCounters = (
|
||||
selectorsSource: SelectorsSource<S>
|
||||
): Record<QueryPreviewTabs, number> => {
|
||||
const subscriptions = selectSubscriptionsOfCurrentQuery(selectorsSource);
|
||||
const subsLen = Object.keys(subscriptions ?? {}).length;
|
||||
const selectActionsOfCurrentQuery = createSelector(
|
||||
selectCurrentQueryInfo,
|
||||
selectActionsById,
|
||||
getActionsOfCurrentQuery
|
||||
);
|
||||
|
||||
const selectTabCounters = createSelector(
|
||||
[
|
||||
selectSubscriptionsOfCurrentQuery,
|
||||
selectActionsOfCurrentQuery,
|
||||
selectCurrentQueryTags,
|
||||
],
|
||||
(subscriptions, actions, tags) => {
|
||||
return {
|
||||
[QueryPreviewTabs.queryTags]:
|
||||
selectCurrentQueryTags(selectorsSource).length,
|
||||
[QueryPreviewTabs.querySubscriptions]: subsLen,
|
||||
[QueryPreviewTabs.queryTags]: tags.length,
|
||||
[QueryPreviewTabs.querySubscriptions]: Object.keys(subscriptions ?? {})
|
||||
.length,
|
||||
[QueryPreviewTabs.apiConfig]: 0,
|
||||
[QueryPreviewTabs.queryinfo]: 0,
|
||||
[QueryPreviewTabs.actions]: actions.length,
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
selectQueryComparator,
|
||||
|
@ -219,5 +239,6 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
|||
selectSubscriptionsOfCurrentQuery,
|
||||
selectApiOfCurrentQuery,
|
||||
selectTabCounters,
|
||||
selectActionsOfCurrentQuery,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ const getSheetFromColorMap = (map: ColorMap) => {
|
|||
|
||||
'&[data-wide-layout="1"]': {
|
||||
height: '100%',
|
||||
width: '45%',
|
||||
width: '44%',
|
||||
borderRightWidth: 1,
|
||||
borderStyle: 'solid',
|
||||
},
|
||||
|
@ -180,7 +180,7 @@ const getSheetFromColorMap = (map: ColorMap) => {
|
|||
selectorButton: {
|
||||
cursor: 'pointer',
|
||||
position: 'relative',
|
||||
padding: '6.5px 10px',
|
||||
padding: '6.5px 8px',
|
||||
color: map.TEXT_COLOR,
|
||||
'border-style': 'solid',
|
||||
'border-width': '1px',
|
||||
|
@ -351,7 +351,7 @@ const getSheetFromColorMap = (map: ColorMap) => {
|
|||
|
||||
previewHeader: {
|
||||
flex: '0 0 30px',
|
||||
padding: '5px 10px',
|
||||
padding: '5px 4px',
|
||||
'align-items': 'center',
|
||||
'border-bottom-width': '1px',
|
||||
'border-bottom-style': 'solid',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { LiftedAction, LiftedState } from '@redux-devtools/instrument';
|
||||
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 { Base16Theme, StylingFunction } from 'react-base16-styling';
|
||||
import type * as themes from 'redux-devtools-themes';
|
||||
|
@ -12,6 +12,7 @@ export enum QueryPreviewTabs {
|
|||
apiConfig,
|
||||
querySubscriptions,
|
||||
queryTags,
|
||||
actions,
|
||||
}
|
||||
|
||||
export interface QueryFormValues {
|
||||
|
@ -83,6 +84,7 @@ export interface SelectOption<T = string> {
|
|||
export interface SelectorsSource<S> {
|
||||
userState: S | null;
|
||||
monitorState: RtkQueryMonitorState;
|
||||
actionsById: LiftedState<unknown, AnyAction, unknown>['actionsById'];
|
||||
}
|
||||
|
||||
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 {
|
||||
QueryInfo,
|
||||
|
@ -14,6 +14,7 @@ import {
|
|||
RtkQueryProvided,
|
||||
ApiTimings,
|
||||
QueryTimings,
|
||||
SelectorsSource,
|
||||
} from '../types';
|
||||
import { missingTagId } from '../monitor-config';
|
||||
import { Comparator } from './comparators';
|
||||
|
@ -423,3 +424,39 @@ export function getQueryStatusFlags({
|
|||
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