diff --git a/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreview.tsx b/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreview.tsx index 5aae4f12..d0cf19c5 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreview.tsx +++ b/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreview.tsx @@ -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 { readonly selectedTab: QueryPreviewTabs; @@ -70,12 +74,25 @@ const MappedApiPreview = mapProps( }) )(QueryPreviewApi); +const MappedQueryPreviewActions = mapProps< + QueryPreviewTabProps, + QueryPreviewActionsProps +>(({ isWideLayout, selectorsSource, selectors }) => ({ + isWideLayout, + actionsOfQuery: selectors.selectActionsOfCurrentQuery(selectorsSource), +}))(QueryPreviewActions); + const tabs: ReadonlyArray> = [ { label: 'query', value: QueryPreviewTabs.queryinfo, component: MappedQueryPreviewInfo, }, + { + label: 'actions', + value: QueryPreviewTabs.actions, + component: MappedQueryPreviewActions, + }, { label: 'tags', value: QueryPreviewTabs.queryTags, @@ -112,7 +129,7 @@ export class QueryPreview extends React.PureComponent> { counterAsString = counterAsString.slice(0, 2) + '...'; } - return `${label} (${counterAsString})`; + return `${label}(${counterAsString})`; }; renderTabLabel = (tab: TabOption): ReactNode => { diff --git a/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreviewActions.tsx b/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreviewActions.tsx new file mode 100644 index 00000000..cea3e6b9 --- /dev/null +++ b/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreviewActions.tsx @@ -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 { + render(): ReactNode { + const { isWideLayout, actionsOfQuery } = this.props; + + return ; + } +} diff --git a/packages/redux-devtools-rtk-query-monitor/src/containers/RtkQueryInspector.tsx b/packages/redux-devtools-rtk-query-monitor/src/containers/RtkQueryInspector.tsx index 8651c2fe..5d772374 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/containers/RtkQueryInspector.tsx +++ b/packages/redux-devtools-rtk-query-monitor/src/containers/RtkQueryInspector.tsx @@ -21,7 +21,7 @@ import { QueryPreview } from '../components/QueryPreview'; type ForwardedMonitorProps> = Pick< LiftedState, - 'monitorState' | 'currentStateIndex' | 'computedStates' + 'monitorState' | 'currentStateIndex' | 'computedStates' | 'actionsById' >; export interface RtkQueryInspectorProps> @@ -124,6 +124,9 @@ class RtkQueryInspector> extends PureComponent< const hasNoApi = apiStates == null; + const actionsOfCurrentQuery = + this.selectors.selectActionsOfCurrentQuery(selectorsSource); + return (
> extends Component< } render() { - const { currentStateIndex, computedStates, monitorState, dispatch } = - this.props; + const { + currentStateIndex, + computedStates, + monitorState, + dispatch, + actionsById, + } = this.props; return ( @@ -66,6 +71,7 @@ class RtkQueryMonitor> extends Component< monitorState={monitorState} dispatch={dispatch} styleUtils={this.state.styleUtils} + actionsById={actionsById} /> ); diff --git a/packages/redux-devtools-rtk-query-monitor/src/selectors.ts b/packages/redux-devtools-rtk-query-monitor/src/selectors.ts index 94dbe503..57d0ff61 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/selectors.ts +++ b/packages/redux-devtools-rtk-query-monitor/src/selectors.ts @@ -19,6 +19,7 @@ import { flipComparator, getQueryTagsOf, generateApiStatsOfCurrentQuery, + getActionsOfCurrentQuery, } from './utils/rtk-query'; type InspectorSelector = Selector, Output>; @@ -27,7 +28,8 @@ export function computeSelectorSource>( props: RtkQueryInspectorProps, previous: SelectorsSource | null = null ): SelectorsSource { - 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>( 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, RtkQueryApiState['subscriptions'][string] >; + readonly selectActionsOfCurrentQuery: InspectorSelector< + S, + ReturnType + >; } export function createInspectorSelectors(): InspectorSelectors { @@ -88,6 +96,9 @@ export function createInspectorSelectors(): InspectorSelectors { return queryListFilters[monitorState.queryForm.values.queryFilter]; }; + const selectActionsById = ({ actionsById }: SelectorsSource) => + actionsById; + const selectApiStates = createSelector( ({ userState }: SelectorsSource) => userState, getApiStatesOf @@ -192,20 +203,29 @@ export function createInspectorSelectors(): InspectorSelectors { generateApiStatsOfCurrentQuery ); - const selectTabCounters = ( - selectorsSource: SelectorsSource - ): Record => { - const subscriptions = selectSubscriptionsOfCurrentQuery(selectorsSource); - const subsLen = Object.keys(subscriptions ?? {}).length; + const selectActionsOfCurrentQuery = createSelector( + selectCurrentQueryInfo, + selectActionsById, + getActionsOfCurrentQuery + ); - return { - [QueryPreviewTabs.queryTags]: - selectCurrentQueryTags(selectorsSource).length, - [QueryPreviewTabs.querySubscriptions]: subsLen, - [QueryPreviewTabs.apiConfig]: 0, - [QueryPreviewTabs.queryinfo]: 0, - }; - }; + const selectTabCounters = createSelector( + [ + selectSubscriptionsOfCurrentQuery, + selectActionsOfCurrentQuery, + selectCurrentQueryTags, + ], + (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 { selectQueryComparator, @@ -219,5 +239,6 @@ export function createInspectorSelectors(): InspectorSelectors { selectSubscriptionsOfCurrentQuery, selectApiOfCurrentQuery, selectTabCounters, + selectActionsOfCurrentQuery, }; } diff --git a/packages/redux-devtools-rtk-query-monitor/src/styles/createStylingFromTheme.ts b/packages/redux-devtools-rtk-query-monitor/src/styles/createStylingFromTheme.ts index d6eea6db..27b351df 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/styles/createStylingFromTheme.ts +++ b/packages/redux-devtools-rtk-query-monitor/src/styles/createStylingFromTheme.ts @@ -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', diff --git a/packages/redux-devtools-rtk-query-monitor/src/types.ts b/packages/redux-devtools-rtk-query-monitor/src/types.ts index 8dc56880..cad3bf1e 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/types.ts +++ b/packages/redux-devtools-rtk-query-monitor/src/types.ts @@ -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 { export interface SelectorsSource { userState: S | null; monitorState: RtkQueryMonitorState; + actionsById: LiftedState['actionsById']; } export interface StyleUtils { diff --git a/packages/redux-devtools-rtk-query-monitor/src/utils/rtk-query.ts b/packages/redux-devtools-rtk-query-monitor/src/utils/rtk-query.ts index f0761f8f..83fa3d14 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/utils/rtk-query.ts +++ b/packages/redux-devtools-rtk-query-monitor/src/utils/rtk-query.ts @@ -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['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; +}