From e4c5720d872b22ef527e11fe359c26f4330d63e0 Mon Sep 17 00:00:00 2001 From: FaberVitale Date: Sun, 1 Aug 2021 15:35:22 +0200 Subject: [PATCH] feat(rtk-query): display mutations in queryList --- .../src/components/QueryList.tsx | 38 ++++++--- .../src/components/QueryPreviewHeader.tsx | 4 +- .../src/components/QueryPreviewInfo.tsx | 64 ++++++++------ .../src/containers/QueryPreview.tsx | 83 ++++++++++++++----- .../src/containers/RtkQueryInspector.tsx | 12 +-- .../src/selectors.ts | 41 +++++++-- .../src/styles/createStylingFromTheme.ts | 17 ++++ .../src/types.ts | 17 +++- .../src/utils/comparators.ts | 36 ++++---- .../src/utils/filters.ts | 35 ++++---- .../src/utils/rtk-query.ts | 56 +++++++++---- .../src/utils/tabs.ts | 16 ++++ 12 files changed, 296 insertions(+), 123 deletions(-) create mode 100644 packages/redux-devtools-rtk-query-monitor/src/utils/tabs.ts diff --git a/packages/redux-devtools-rtk-query-monitor/src/components/QueryList.tsx b/packages/redux-devtools-rtk-query-monitor/src/components/QueryList.tsx index cef856c6..b953e56c 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/components/QueryList.tsx +++ b/packages/redux-devtools-rtk-query-monitor/src/components/QueryList.tsx @@ -1,18 +1,18 @@ import React, { PureComponent, ReactNode } from 'react'; import { StyleUtilsContext } from '../styles/createStylingFromTheme'; -import { QueryInfo, RtkQueryMonitorState } from '../types'; +import { RtkResourceInfo, RtkQueryMonitorState } from '../types'; import { isQuerySelected } from '../utils/rtk-query'; export interface QueryListProps { - queryInfos: QueryInfo[]; + resInfos: RtkResourceInfo[]; selectedQueryKey: RtkQueryMonitorState['selectedQueryKey']; - onSelectQuery: (query: QueryInfo) => void; + onSelectQuery: (query: RtkResourceInfo) => void; } export class QueryList extends PureComponent { static isItemSelected( selectedQueryKey: QueryListProps['selectedQueryKey'], - queryInfo: QueryInfo + queryInfo: RtkResourceInfo ): boolean { return ( !!selectedQueryKey && @@ -21,27 +21,43 @@ export class QueryList extends PureComponent { ); } + static formatQuery(resInfo: RtkResourceInfo): string { + const key = + resInfo.type === 'query' + ? resInfo.queryKey + : `${resInfo.state.endpointName ?? ''} ${resInfo.queryKey}`; + + return key; + } + render(): ReactNode { - const { queryInfos, selectedQueryKey, onSelectQuery } = this.props; + const { resInfos, selectedQueryKey, onSelectQuery } = this.props; return ( {({ styling }) => (
    - {queryInfos.map((queryInfo) => { - const isSelected = isQuerySelected(selectedQueryKey, queryInfo); + {resInfos.map((resInfo) => { + const isSelected = isQuerySelected(selectedQueryKey, resInfo); return (
  • onSelectQuery(queryInfo)} + key={resInfo.queryKey} + onClick={() => onSelectQuery(resInfo)} {...styling( ['queryListItem', isSelected && 'queryListItemSelected'], isSelected )} > -

    {queryInfo.queryKey}

    -

    {queryInfo.query.status}

    +

    + {QueryList.formatQuery(resInfo)} +

    +
    + + {resInfo.type === 'query' ? 'Q' : 'M'} + +

    {resInfo.state.status}

    +
  • ); })} diff --git a/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreviewHeader.tsx b/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreviewHeader.tsx index d1659e74..ef9ddc1d 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreviewHeader.tsx +++ b/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreviewHeader.tsx @@ -4,7 +4,9 @@ import { QueryPreviewTabs, TabOption } from '../types'; import { emptyArray } from '../utils/object'; export interface QueryPreviewHeaderProps { - tabs: ReadonlyArray>; + tabs: ReadonlyArray< + TabOption + >; onTabChange: (tab: QueryPreviewTabs) => void; selectedTab: QueryPreviewTabs; renderTabLabel?: (tab: QueryPreviewHeaderProps['tabs'][number]) => ReactNode; diff --git a/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreviewInfo.tsx b/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreviewInfo.tsx index f4e82937..091f6add 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreviewInfo.tsx +++ b/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreviewInfo.tsx @@ -1,7 +1,7 @@ import { createSelector, Selector } from '@reduxjs/toolkit'; import { QueryStatus } from '@reduxjs/toolkit/dist/query'; import React, { ReactNode, PureComponent } from 'react'; -import { QueryInfo, RtkQueryState, RTKStatusFlags } from '../types'; +import { RtkResourceInfo, RTKStatusFlags } from '../types'; import { formatMs } from '../utils/formatters'; import { identity } from '../utils/object'; import { getQueryStatusFlags } from '../utils/rtk-query'; @@ -13,16 +13,18 @@ type QueryTimings = { duration: string; }; -interface FormattedQuery { - queryKey: string; +type FormattedQuery = { + key: string; reducerPath: string; timings: QueryTimings; statusFlags: RTKStatusFlags; - query: RtkQueryState; -} +} & ( + | { mutation: RtkResourceInfo['state'] } + | { query: RtkResourceInfo['state'] } +); export interface QueryPreviewInfoProps { - queryInfo: QueryInfo; + resInfo: RtkResourceInfo; isWideLayout: boolean; } export class QueryPreviewInfo extends PureComponent { @@ -33,23 +35,22 @@ export class QueryPreviewInfo extends PureComponent { ): boolean => { const lastKey = keyPath[keyPath.length - 1]; - return layer <= 1 && lastKey !== 'query'; + return layer <= 1 && lastKey !== 'query' && lastKey !== 'mutation'; }; - selectFormattedQuery: Selector = createSelector( - identity, - (queryInfo: QueryInfo): FormattedQuery => { - const { query, queryKey, reducerPath } = queryInfo; + selectFormattedQuery: Selector = + createSelector(identity, (resInfo: RtkResourceInfo): FormattedQuery => { + const { state, queryKey, reducerPath } = resInfo; - const startedAt = query.startedTimeStamp - ? new Date(query.startedTimeStamp).toISOString() + const startedAt = state.startedTimeStamp + ? new Date(state.startedTimeStamp).toISOString() : '-'; - const loadedAt = query.fulfilledTimeStamp - ? new Date(query.fulfilledTimeStamp).toISOString() + const loadedAt = state.fulfilledTimeStamp + ? new Date(state.fulfilledTimeStamp).toISOString() : '-'; - const statusFlags = getQueryStatusFlags(query); + const statusFlags = getQueryStatusFlags(state); const timings = { startedAt, @@ -58,29 +59,38 @@ export class QueryPreviewInfo extends PureComponent { }; if ( - query.fulfilledTimeStamp && - query.startedTimeStamp && - query.status !== QueryStatus.pending && - query.startedTimeStamp <= query.fulfilledTimeStamp + state.fulfilledTimeStamp && + state.startedTimeStamp && + state.status !== QueryStatus.pending && + state.startedTimeStamp <= state.fulfilledTimeStamp ) { timings.duration = formatMs( - query.fulfilledTimeStamp - query.startedTimeStamp + state.fulfilledTimeStamp - state.startedTimeStamp ); } + if (resInfo.type === 'query') { + return { + key: queryKey, + reducerPath, + query: resInfo.state, + statusFlags, + timings, + }; + } + return { - queryKey, + key: queryKey, reducerPath, - query: queryInfo.query, + mutation: resInfo.state, statusFlags, timings, }; - } - ); + }); render(): ReactNode { - const { queryInfo, isWideLayout } = this.props; - const formattedQuery = this.selectFormattedQuery(queryInfo); + const { resInfo, isWideLayout } = this.props; + const formattedQuery = this.selectFormattedQuery(resInfo); return ( { readonly selectedTab: QueryPreviewTabs; readonly hasNoApis: boolean; readonly onTabChange: (tab: QueryPreviewTabs) => void; - readonly queryInfo: QueryInfo | null; + readonly resInfo: RtkResourceInfo | null; readonly styling: StylingFunction; readonly isWideLayout: boolean; readonly selectorsSource: SelectorsSource; @@ -47,15 +48,15 @@ export interface QueryPreviewProps { /** * Tab content is not rendered if there's no selected query. */ -type QueryPreviewTabProps = Omit, 'queryInfo'> & { - queryInfo: QueryInfo; +type QueryPreviewTabProps = Omit, 'resInfo'> & { + resInfo: RtkResourceInfo; }; const MappedQueryPreviewTags = mapProps< QueryPreviewTabProps, QueryPreviewTagsProps ->(({ selectors, selectorsSource, isWideLayout, queryInfo }) => ({ - queryInfo, +>(({ selectors, selectorsSource, isWideLayout, resInfo }) => ({ + resInfo, tags: selectors.selectCurrentQueryTags(selectorsSource), isWideLayout, }))(QueryPreviewTags); @@ -63,9 +64,7 @@ const MappedQueryPreviewTags = mapProps< const MappedQueryPreviewInfo = mapProps< QueryPreviewTabProps, QueryPreviewInfoProps ->(({ queryInfo, isWideLayout }) => ({ queryInfo, isWideLayout }))( - QueryPreviewInfo -); +>(({ resInfo, isWideLayout }) => ({ resInfo, isWideLayout }))(QueryPreviewInfo); const MappedQuerySubscriptipns = mapProps< QueryPreviewTabProps, @@ -91,26 +90,48 @@ const MappedQueryPreviewActions = mapProps< actionsOfQuery: selectors.selectActionsOfCurrentQuery(selectorsSource), }))(QueryPreviewActions); -const tabs: ReadonlyArray> = [ +const tabs: ReadonlyArray< + TabOption +> = [ { label: 'query', value: QueryPreviewTabs.queryinfo, component: MappedQueryPreviewInfo, + visible: { + query: true, + mutation: true, + default: true, + }, }, { label: 'actions', value: QueryPreviewTabs.actions, component: MappedQueryPreviewActions, + visible: { + query: true, + mutation: true, + default: true, + }, }, { label: 'tags', value: QueryPreviewTabs.queryTags, component: MappedQueryPreviewTags, + visible: { + query: true, + mutation: false, + default: true, + }, }, { label: 'subs', value: QueryPreviewTabs.querySubscriptions, component: MappedQuerySubscriptipns, + visible: { + query: true, + mutation: false, + default: true, + }, }, { label: 'api', @@ -141,24 +162,32 @@ export class QueryPreview extends React.PureComponent> { return `${label}(${counterAsString})`; }; - renderTabLabel = (tab: TabOption): ReactNode => { - const { selectors, selectorsSource } = this.props; + renderTabLabel = ( + tab: TabOption + ): ReactNode => { + const { selectors, selectorsSource, resInfo } = this.props; const tabCount = selectors.selectTabCounters(selectorsSource)[tab.value]; - if (tabCount > 0) { - return this.renderLabelWithCounter(tab.label, tabCount); + let tabLabel = tab.label; + + if (tabLabel === 'query' && resInfo?.type === 'mutation') { + tabLabel = resInfo.type; } - return tab.label; + if (tabCount > 0) { + return this.renderLabelWithCounter(tabLabel, tabCount); + } + + return tabLabel; }; render(): ReactNode { - const { queryInfo, selectedTab, onTabChange, hasNoApis } = this.props; + const { resInfo, selectedTab, onTabChange, hasNoApis } = this.props; const { component: TabComponent } = tabs.find((tab) => tab.value === selectedTab) || tabs[0]; - if (!queryInfo) { + if (!resInfo) { return ( {({ styling }) => ( @@ -167,7 +196,15 @@ export class QueryPreview extends React.PureComponent> { selectedTab={selectedTab} onTabChange={onTabChange} tabs={ - tabs as ReadonlyArray> + tabs.filter((tab) => + isTabVisible(tab, 'default') + ) as ReadonlyArray< + TabOption< + QueryPreviewTabs, + unknown, + RtkResourceInfo['type'] + > + > } renderTabLabel={this.renderTabLabel} /> @@ -187,7 +224,15 @@ export class QueryPreview extends React.PureComponent> { selectedTab={selectedTab} onTabChange={onTabChange} tabs={ - tabs as ReadonlyArray> + tabs.filter((tab) => + isTabVisible(tab, resInfo.type) + ) as ReadonlyArray< + TabOption< + QueryPreviewTabs, + unknown, + RtkResourceInfo['type'] + > + > } renderTabLabel={this.renderTabLabel} /> 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 0ad97df3..e1af9d64 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/containers/RtkQueryInspector.tsx +++ b/packages/redux-devtools-rtk-query-monitor/src/containers/RtkQueryInspector.tsx @@ -3,11 +3,11 @@ import type { AnyAction, Dispatch, Action } from '@reduxjs/toolkit'; import type { LiftedAction, LiftedState } from '@redux-devtools/core'; import { QueryFormValues, - QueryInfo, QueryPreviewTabs, RtkQueryMonitorState, StyleUtils, SelectorsSource, + RtkResourceInfo, } from '../types'; import { createInspectorSelectors, computeSelectorSource } from '../selectors'; import { @@ -101,7 +101,7 @@ class RtkQueryInspector> extends PureComponent< this.props.dispatch(changeQueryFormValues(values) as AnyAction); }; - handleSelectQuery = (queryInfo: QueryInfo): void => { + handleSelectQuery = (queryInfo: RtkResourceInfo): void => { this.props.dispatch(selectQueryKey(queryInfo) as AnyAction); }; @@ -114,10 +114,10 @@ class RtkQueryInspector> extends PureComponent< const { styleUtils: { styling }, } = this.props; - const allVisibleQueries = + const allVisibleRtkResourceInfos = this.selectors.selectAllVisbileQueries(selectorsSource); - const currentQueryInfo = + const currentResInfo = this.selectors.selectCurrentQueryInfo(selectorsSource); const apiStates = this.selectors.selectApiStates(selectorsSource); @@ -144,14 +144,14 @@ class RtkQueryInspector> extends PureComponent< /> selectorsSource={this.state.selectorsSource} selectors={this.selectors} - queryInfo={currentQueryInfo} + resInfo={currentResInfo} selectedTab={selectorsSource.monitorState.selectedPreviewTab} onTabChange={this.handleTabChange} styling={styling} diff --git a/packages/redux-devtools-rtk-query-monitor/src/selectors.ts b/packages/redux-devtools-rtk-query-monitor/src/selectors.ts index ffb2f9a9..a38b65fb 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/selectors.ts +++ b/packages/redux-devtools-rtk-query-monitor/src/selectors.ts @@ -8,6 +8,7 @@ import { SelectorsSource, RtkQueryProvided, QueryPreviewTabs, + RtkResourceInfo, } from './types'; import { Comparator, queryComparators } from './utils/comparators'; import { FilterList, queryListFilters } from './utils/filters'; @@ -20,6 +21,7 @@ import { getQueryTagsOf, generateApiStatsOfCurrentQuery, getActionsOfCurrentQuery, + extractAllApiMutations, } from './utils/rtk-query'; type InspectorSelector = Selector, Output>; @@ -60,8 +62,8 @@ export interface InspectorSelectors { S, ReturnType >; - readonly selectAllVisbileQueries: InspectorSelector; - readonly selectCurrentQueryInfo: InspectorSelector; + readonly selectAllVisbileQueries: InspectorSelector; + readonly selectCurrentQueryInfo: InspectorSelector; readonly selectSearchQueryRegex: InspectorSelector; readonly selectCurrentQueryTags: InspectorSelector; readonly selectApiStatsOfCurrentQuery: InspectorSelector; @@ -86,13 +88,13 @@ export interface InspectorSelectors { export function createInspectorSelectors(): InspectorSelectors { const selectQueryComparator = ({ monitorState, - }: SelectorsSource): Comparator => { + }: SelectorsSource): Comparator => { return queryComparators[monitorState.queryForm.values.queryComparator]; }; const selectQueryListFilter = ({ monitorState, - }: SelectorsSource): FilterList => { + }: SelectorsSource): FilterList => { return queryListFilters[monitorState.queryForm.values.queryFilter]; }; @@ -108,6 +110,11 @@ export function createInspectorSelectors(): InspectorSelectors { extractAllApiQueries ); + const selectAllMutations = createSelector( + selectApiStates, + extractAllApiMutations + ); + const selectSearchQueryRegex = createSelector( ({ monitorState }: SelectorsSource) => monitorState.queryForm.values.searchValue, @@ -138,13 +145,21 @@ export function createInspectorSelectors(): InspectorSelectors { selectQueryComparator, selectQueryListFilter, selectAllQueries, + selectAllMutations, selectComparatorOrder, selectSearchQueryRegex, ], - (comparator, queryListFilter, queryList, isAscending, searchRegex) => { + ( + comparator, + queryListFilter, + queryList, + mutationsList, + isAscending, + searchRegex + ) => { const filteredList = queryListFilter( searchRegex, - queryList as QueryInfo[] + (queryList as RtkResourceInfo[]).concat(mutationsList) ); const computedComparator = isAscending @@ -157,19 +172,29 @@ export function createInspectorSelectors(): InspectorSelectors { const selectCurrentQueryInfo = createSelector( selectAllQueries, + selectAllMutations, ({ monitorState }: SelectorsSource) => monitorState.selectedQueryKey, - (allQueries, selectedQueryKey) => { + (allQueries, allMutations, selectedQueryKey) => { if (!selectedQueryKey) { return null; } - const currentQueryInfo = + let currentQueryInfo: null | RtkResourceInfo = allQueries.find( (query) => query.queryKey === selectedQueryKey.queryKey && selectedQueryKey.reducerPath === query.reducerPath ) || null; + if (!currentQueryInfo) { + currentQueryInfo = + allMutations.find( + (mutation) => + mutation.queryKey === selectedQueryKey.queryKey && + selectedQueryKey.reducerPath === mutation.reducerPath + ) || null; + } + return currentQueryInfo; } ); 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 7c40a7a5..2b7e9f79 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/styles/createStylingFromTheme.ts +++ b/packages/redux-devtools-rtk-query-monitor/src/styles/createStylingFromTheme.ts @@ -139,8 +139,10 @@ const getSheetFromColorMap = (map: ColorMap) => { '-webkit-line-clamp': 2, whiteSpace: 'normal', overflow: 'hidden', + width: '100%', maxWidth: 'calc(100% - 70px)', wordBreak: 'break-all', + margin: 0, }, queryListHeader: { @@ -154,6 +156,20 @@ const getSheetFromColorMap = (map: ColorMap) => { 'border-color': map.LIST_BORDER_COLOR, }, + queryStatusWrapper: { + display: 'flex', + width: 'auto', + justifyContent: 'center', + alignItems: 'center', + margin: 0, + flex: '0 0 auto', + overflow: 'hidden', + }, + + queryType: { + marginRight: 4, + }, + queryStatus: { display: 'inline-flex', alignItems: 'center', @@ -164,6 +180,7 @@ const getSheetFromColorMap = (map: ColorMap) => { 'font-size': '0.7em', 'line-height': '1em', 'flex-shrink': 0, + fontWeight: 700, 'background-color': map.ACTION_TIME_BACK_COLOR, color: map.ACTION_TIME_COLOR, }, diff --git a/packages/redux-devtools-rtk-query-monitor/src/types.ts b/packages/redux-devtools-rtk-query-monitor/src/types.ts index 8d66c0bc..647585ba 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/types.ts +++ b/packages/redux-devtools-rtk-query-monitor/src/types.ts @@ -61,25 +61,33 @@ export interface ExternalProps> { } export interface QueryInfo { - query: RtkQueryState; + type: 'query'; + state: RtkQueryState; queryKey: string; reducerPath: string; } export interface MutationInfo { - mutation: RtkMutationState; + type: 'mutation'; + state: RtkMutationState; queryKey: string; reducerPath: string; } +export type RtkResourceInfo = QueryInfo | MutationInfo; + export interface ApiInfo { reducerPath: string; apiState: RtkQueryApiState; } -export interface SelectOption { +export interface SelectOption< + T = string, + VisConfig extends string = 'default' +> { label: string; value: T; + visible?: Record | boolean; } export interface SelectorsSource { @@ -136,7 +144,8 @@ export interface ApiStats { }>; } -export interface TabOption extends SelectOption { +export interface TabOption + extends SelectOption { component: ComponentType

    ; } diff --git a/packages/redux-devtools-rtk-query-monitor/src/utils/comparators.ts b/packages/redux-devtools-rtk-query-monitor/src/utils/comparators.ts index c16c4a2c..cd12d1ec 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/utils/comparators.ts +++ b/packages/redux-devtools-rtk-query-monitor/src/utils/comparators.ts @@ -1,5 +1,5 @@ import { QueryStatus } from '@reduxjs/toolkit/query'; -import { QueryInfo, SelectOption } from '../types'; +import { RtkResourceInfo, SelectOption } from '../types'; export interface Comparator { (a: T, b: T): number; @@ -22,11 +22,11 @@ export const sortQueryOptions: SelectOption[] = [ ]; function sortQueryByFulfilled( - thisQueryInfo: QueryInfo, - thatQueryInfo: QueryInfo + thisQueryInfo: RtkResourceInfo, + thatQueryInfo: RtkResourceInfo ): number { - const thisFulfilled = thisQueryInfo.query.fulfilledTimeStamp ?? -1; - const thatFulfilled = thatQueryInfo.query.fulfilledTimeStamp ?? -1; + const thisFulfilled = thisQueryInfo.state.fulfilledTimeStamp ?? -1; + const thatFulfilled = thatQueryInfo.state.fulfilledTimeStamp ?? -1; return thisFulfilled - thatFulfilled; } @@ -39,11 +39,11 @@ const mapStatusToFactor = { }; function sortQueryByStatus( - thisQueryInfo: QueryInfo, - thatQueryInfo: QueryInfo + thisQueryInfo: RtkResourceInfo, + thatQueryInfo: RtkResourceInfo ): number { - const thisTerm = mapStatusToFactor[thisQueryInfo.query.status] || -1; - const thatTerm = mapStatusToFactor[thatQueryInfo.query.status] || -1; + const thisTerm = mapStatusToFactor[thisQueryInfo.state.status] || -1; + const thatTerm = mapStatusToFactor[thatQueryInfo.state.status] || -1; return thisTerm - thatTerm; } @@ -60,25 +60,25 @@ function compareJSONPrimitive( } function sortByQueryKey( - thisQueryInfo: QueryInfo, - thatQueryInfo: QueryInfo + thisQueryInfo: RtkResourceInfo, + thatQueryInfo: RtkResourceInfo ): number { return compareJSONPrimitive(thisQueryInfo.queryKey, thatQueryInfo.queryKey); } function sortQueryByEndpointName( - thisQueryInfo: QueryInfo, - thatQueryInfo: QueryInfo + thisQueryInfo: RtkResourceInfo, + thatQueryInfo: RtkResourceInfo ): number { - const thisEndpointName = thisQueryInfo.query.endpointName ?? ''; - const thatEndpointName = thatQueryInfo.query.endpointName ?? ''; + const thisEndpointName = thisQueryInfo.state.endpointName ?? ''; + const thatEndpointName = thatQueryInfo.state.endpointName ?? ''; return compareJSONPrimitive(thisEndpointName, thatEndpointName); } function sortByApiReducerPath( - thisQueryInfo: QueryInfo, - thatQueryInfo: QueryInfo + thisQueryInfo: RtkResourceInfo, + thatQueryInfo: RtkResourceInfo ): number { return compareJSONPrimitive( thisQueryInfo.reducerPath, @@ -87,7 +87,7 @@ function sortByApiReducerPath( } export const queryComparators: Readonly< - Record> + Record> > = { [QueryComparators.fulfilledTimeStamp]: sortQueryByFulfilled, [QueryComparators.status]: sortQueryByStatus, diff --git a/packages/redux-devtools-rtk-query-monitor/src/utils/filters.ts b/packages/redux-devtools-rtk-query-monitor/src/utils/filters.ts index eae8c51f..0f179cd1 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/utils/filters.ts +++ b/packages/redux-devtools-rtk-query-monitor/src/utils/filters.ts @@ -1,4 +1,4 @@ -import { QueryInfo, SelectOption } from '../types'; +import { RtkResourceInfo, SelectOption } from '../types'; export interface FilterList { (regex: RegExp | null, list: T[]): T[]; @@ -13,45 +13,52 @@ export enum QueryFilters { function filterByQueryKey( regex: RegExp | null, - list: QueryInfo[] -): QueryInfo[] { + list: RtkResourceInfo[] +): RtkResourceInfo[] { if (!regex) { return list; } - return list.filter((queryInfo) => regex.test(queryInfo.queryKey)); + return list.filter((RtkResourceInfo) => regex.test(RtkResourceInfo.queryKey)); } function filterByReducerPath( regex: RegExp | null, - list: QueryInfo[] -): QueryInfo[] { + list: RtkResourceInfo[] +): RtkResourceInfo[] { if (!regex) { return list; } - return list.filter((queryInfo) => regex.test(queryInfo.reducerPath)); + return list.filter((RtkResourceInfo) => + regex.test(RtkResourceInfo.reducerPath) + ); } function filterByEndpointName( regex: RegExp | null, - list: QueryInfo[] -): QueryInfo[] { + list: RtkResourceInfo[] +): RtkResourceInfo[] { if (!regex) { return list; } - return list.filter((queryInfo) => - regex.test(queryInfo.query.endpointName ?? 'undefined') + return list.filter((RtkResourceInfo) => + regex.test(RtkResourceInfo.state.endpointName ?? 'undefined') ); } -function filterByStatus(regex: RegExp | null, list: QueryInfo[]): QueryInfo[] { +function filterByStatus( + regex: RegExp | null, + list: RtkResourceInfo[] +): RtkResourceInfo[] { if (!regex) { return list; } - return list.filter((queryInfo) => regex.test(queryInfo.query.status)); + return list.filter((RtkResourceInfo) => + regex.test(RtkResourceInfo.state.status) + ); } export const filterQueryOptions: SelectOption[] = [ @@ -62,7 +69,7 @@ export const filterQueryOptions: SelectOption[] = [ ]; export const queryListFilters: Readonly< - Record> + Record> > = { [QueryFilters.queryKey]: filterByQueryKey, [QueryFilters.endpointName]: filterByEndpointName, 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 a83f07e8..91399400 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, isAnyOf, isPlainObject } from '@reduxjs/toolkit'; +import { AnyAction, isAllOf, isAnyOf, isPlainObject } from '@reduxjs/toolkit'; import { QueryStatus } from '@reduxjs/toolkit/query'; import { QueryInfo, @@ -15,6 +15,8 @@ import { ApiTimings, QueryTimings, SelectorsSource, + RtkMutationState, + RtkResourceInfo, } from '../types'; import { missingTagId } from '../monitor-config'; import { Comparator } from './comparators'; @@ -104,13 +106,14 @@ export function extractAllApiQueries( for (let j = 0, qKeysLen = queryKeys.length; j < qKeysLen; j++) { const queryKey = queryKeys[j]; - const query = api.queries[queryKey]; + const state = api.queries[queryKey]; - if (query) { + if (state) { output.push({ + type: 'query', reducerPath, queryKey, - query, + state, }); } } @@ -136,13 +139,14 @@ export function extractAllApiMutations( for (let j = 0, mKeysLen = mutationKeys.length; j < mKeysLen; j++) { const queryKey = mutationKeys[j]; - const mutation = api.queries[queryKey]; + const state = api.mutations[queryKey]; - if (mutation) { + if (state) { output.push({ + type: 'mutation', reducerPath, queryKey, - mutation, + state, }); } } @@ -333,7 +337,7 @@ export function flipComparator(comparator: Comparator): Comparator { export function isQuerySelected( selectedQueryKey: RtkQueryMonitorState['selectedQueryKey'], - queryInfo: QueryInfo + queryInfo: RtkResourceInfo ): boolean { return ( !!selectedQueryKey && @@ -343,7 +347,7 @@ export function isQuerySelected( } export function getApiStateOf( - queryInfo: QueryInfo | null, + queryInfo: RtkResourceInfo | null, apiStates: ReturnType ): RtkQueryApiState | null { if (!apiStates || !queryInfo) { @@ -379,10 +383,10 @@ export function getProvidedOf( } export function getQueryTagsOf( - queryInfo: QueryInfo | null, + resInfo: RtkResourceInfo | null, provided: RtkQueryProvided | null ): RtkQueryTag[] { - if (!queryInfo || !provided) { + if (!resInfo || resInfo.type === 'mutation' || !provided) { return emptyArray; } @@ -397,7 +401,7 @@ export function getQueryTagsOf( for (const [type, tagIds] of Object.entries(provided)) { if (tagIds) { for (const [id, queryKeys] of Object.entries(tagIds)) { - if ((queryKeys as unknown[]).includes(queryInfo.queryKey)) { + if ((queryKeys as unknown[]).includes(resInfo.queryKey)) { const tag: RtkQueryTag = { type }; if (id !== missingTagId) { @@ -422,7 +426,7 @@ export function getQueryTagsOf( export function getQueryStatusFlags({ status, data, -}: RtkQueryState): RTKStatusFlags { +}: RtkQueryState | RtkMutationState): RTKStatusFlags { return { isUninitialized: status === QueryStatus.uninitialized, isFetching: status === QueryStatus.pending, @@ -446,15 +450,37 @@ function matchesQueryKey(queryKey: string) { action?.meta?.arg?.queryCacheKey === queryKey; } +function macthesRequestId(requestId: string) { + return (action: any): action is AnyAction => + action?.meta?.requestId === requestId; +} + +function matchesReducerPath(reducerPath: string) { + return (action: any): action is AnyAction => + typeof action?.type === 'string' && action.type.startsWith(reducerPath); +} + export function getActionsOfCurrentQuery( - currentQuery: QueryInfo | null, + currentQuery: RtkResourceInfo | null, actionById: SelectorsSource['actionsById'] ): AnyAction[] { if (!currentQuery) { return emptyArray; } - const matcher = isAnyOf(matchesQueryKey(currentQuery.queryKey)); + let matcher: ReturnType; + + if (currentQuery.type === 'mutation') { + matcher = isAllOf( + matchesReducerPath(currentQuery.reducerPath), + macthesRequestId(currentQuery.queryKey) + ); + } else { + matcher = isAllOf( + matchesReducerPath(currentQuery.reducerPath), + matchesQueryKey(currentQuery.queryKey) + ); + } const output: AnyAction[] = []; diff --git a/packages/redux-devtools-rtk-query-monitor/src/utils/tabs.ts b/packages/redux-devtools-rtk-query-monitor/src/utils/tabs.ts new file mode 100644 index 00000000..436d4a9f --- /dev/null +++ b/packages/redux-devtools-rtk-query-monitor/src/utils/tabs.ts @@ -0,0 +1,16 @@ +import { TabOption } from '../types'; + +export function isTabVisible( + tab: TabOption, + visKey: Vis | 'default' +): boolean { + if (typeof tab.visible === 'boolean') { + return tab.visible; + } + + if (typeof tab.visible === 'object' && tab.visible) { + return !!tab.visible[visKey]; + } + + return true; +}