feat(rtk-query): add actions tab

This commit is contained in:
FaberVitale 2021-06-26 19:56:13 +02:00
parent 71b483bf02
commit 7a274d9025
8 changed files with 126 additions and 24 deletions

View File

@ -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 => {

View File

@ -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} />;
}
}

View File

@ -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}

View File

@ -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>
); );

View File

@ -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,
}; };
} }

View File

@ -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',

View File

@ -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 {

View File

@ -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;
}