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 { 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,
@ -112,7 +129,7 @@ export class QueryPreview<S> extends React.PureComponent<QueryPreviewProps<S>> {
counterAsString = counterAsString.slice(0, 2) + '...';
}
return `${label} (${counterAsString})`;
return `${label}(${counterAsString})`;
};
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<
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}

View File

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

View File

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

View File

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

View File

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

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