feat(rtk-query): display mutations in queryList

This commit is contained in:
FaberVitale 2021-08-01 15:35:22 +02:00
parent 95da893616
commit e4c5720d87
12 changed files with 296 additions and 123 deletions

View File

@ -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<QueryListProps> {
static isItemSelected(
selectedQueryKey: QueryListProps['selectedQueryKey'],
queryInfo: QueryInfo
queryInfo: RtkResourceInfo
): boolean {
return (
!!selectedQueryKey &&
@ -21,27 +21,43 @@ export class QueryList extends PureComponent<QueryListProps> {
);
}
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 (
<StyleUtilsContext.Consumer>
{({ styling }) => (
<ul {...styling('queryList')}>
{queryInfos.map((queryInfo) => {
const isSelected = isQuerySelected(selectedQueryKey, queryInfo);
{resInfos.map((resInfo) => {
const isSelected = isQuerySelected(selectedQueryKey, resInfo);
return (
<li
key={queryInfo.queryKey}
onClick={() => onSelectQuery(queryInfo)}
key={resInfo.queryKey}
onClick={() => onSelectQuery(resInfo)}
{...styling(
['queryListItem', isSelected && 'queryListItemSelected'],
isSelected
)}
>
<p {...styling('queryListItemKey')}>{queryInfo.queryKey}</p>
<p {...styling('queryStatus')}>{queryInfo.query.status}</p>
<p {...styling('queryListItemKey')}>
{QueryList.formatQuery(resInfo)}
</p>
<div {...styling('queryStatusWrapper')}>
<strong {...styling(['queryStatus', 'queryType'])}>
{resInfo.type === 'query' ? 'Q' : 'M'}
</strong>
<p {...styling('queryStatus')}>{resInfo.state.status}</p>
</div>
</li>
);
})}

View File

@ -4,7 +4,9 @@ import { QueryPreviewTabs, TabOption } from '../types';
import { emptyArray } from '../utils/object';
export interface QueryPreviewHeaderProps {
tabs: ReadonlyArray<TabOption<QueryPreviewTabs, unknown>>;
tabs: ReadonlyArray<
TabOption<QueryPreviewTabs, unknown, 'query' | 'mutation'>
>;
onTabChange: (tab: QueryPreviewTabs) => void;
selectedTab: QueryPreviewTabs;
renderTabLabel?: (tab: QueryPreviewHeaderProps['tabs'][number]) => ReactNode;

View File

@ -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<QueryPreviewInfoProps> {
@ -33,23 +35,22 @@ export class QueryPreviewInfo extends PureComponent<QueryPreviewInfoProps> {
): boolean => {
const lastKey = keyPath[keyPath.length - 1];
return layer <= 1 && lastKey !== 'query';
return layer <= 1 && lastKey !== 'query' && lastKey !== 'mutation';
};
selectFormattedQuery: Selector<QueryInfo, FormattedQuery> = createSelector(
identity,
(queryInfo: QueryInfo): FormattedQuery => {
const { query, queryKey, reducerPath } = queryInfo;
selectFormattedQuery: Selector<RtkResourceInfo, FormattedQuery> =
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<QueryPreviewInfoProps> {
};
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 {
queryKey,
key: queryKey,
reducerPath,
query: queryInfo.query,
query: resInfo.state,
statusFlags,
timings,
};
}
);
return {
key: queryKey,
reducerPath,
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 (
<TreeView

View File

@ -3,7 +3,7 @@ import { StyleUtilsContext } from '../styles/createStylingFromTheme';
import { createTreeItemLabelRenderer } from '../styles/tree';
import {
QueryPreviewTabs,
QueryInfo,
RtkResourceInfo,
SelectorsSource,
TabOption,
} from '../types';
@ -32,12 +32,13 @@ import {
QueryPreviewActions,
QueryPreviewActionsProps,
} from '../components/QueryPreviewActions';
import { isTabVisible } from '../utils/tabs';
export interface QueryPreviewProps<S = unknown> {
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<S>;
@ -47,15 +48,15 @@ export interface QueryPreviewProps<S = unknown> {
/**
* Tab content is not rendered if there's no selected query.
*/
type QueryPreviewTabProps = Omit<QueryPreviewProps<unknown>, 'queryInfo'> & {
queryInfo: QueryInfo;
type QueryPreviewTabProps = Omit<QueryPreviewProps<unknown>, '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<TabOption<QueryPreviewTabs, QueryPreviewTabProps>> = [
const tabs: ReadonlyArray<
TabOption<QueryPreviewTabs, QueryPreviewTabProps, RtkResourceInfo['type']>
> = [
{
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<S> extends React.PureComponent<QueryPreviewProps<S>> {
return `${label}(${counterAsString})`;
};
renderTabLabel = (tab: TabOption<QueryPreviewTabs, unknown>): ReactNode => {
const { selectors, selectorsSource } = this.props;
renderTabLabel = (
tab: TabOption<QueryPreviewTabs, unknown, 'query' | 'mutation'>
): 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 (
<StyleUtilsContext.Consumer>
{({ styling }) => (
@ -167,7 +196,15 @@ export class QueryPreview<S> extends React.PureComponent<QueryPreviewProps<S>> {
selectedTab={selectedTab}
onTabChange={onTabChange}
tabs={
tabs as ReadonlyArray<TabOption<QueryPreviewTabs, unknown>>
tabs.filter((tab) =>
isTabVisible(tab, 'default')
) as ReadonlyArray<
TabOption<
QueryPreviewTabs,
unknown,
RtkResourceInfo['type']
>
>
}
renderTabLabel={this.renderTabLabel}
/>
@ -187,7 +224,15 @@ export class QueryPreview<S> extends React.PureComponent<QueryPreviewProps<S>> {
selectedTab={selectedTab}
onTabChange={onTabChange}
tabs={
tabs as ReadonlyArray<TabOption<QueryPreviewTabs, unknown>>
tabs.filter((tab) =>
isTabVisible(tab, resInfo.type)
) as ReadonlyArray<
TabOption<
QueryPreviewTabs,
unknown,
RtkResourceInfo['type']
>
>
}
renderTabLabel={this.renderTabLabel}
/>

View File

@ -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<S, A extends Action<unknown>> 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<S, A extends Action<unknown>> 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<S, A extends Action<unknown>> extends PureComponent<
/>
<QueryList
onSelectQuery={this.handleSelectQuery}
queryInfos={allVisibleQueries}
resInfos={allVisibleRtkResourceInfos}
selectedQueryKey={selectorsSource.monitorState.selectedQueryKey}
/>
</div>
<QueryPreview<S>
selectorsSource={this.state.selectorsSource}
selectors={this.selectors}
queryInfo={currentQueryInfo}
resInfo={currentResInfo}
selectedTab={selectorsSource.monitorState.selectedPreviewTab}
onTabChange={this.handleTabChange}
styling={styling}

View File

@ -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<S, Output> = Selector<SelectorsSource<S>, Output>;
@ -60,8 +62,8 @@ export interface InspectorSelectors<S> {
S,
ReturnType<typeof extractAllApiQueries>
>;
readonly selectAllVisbileQueries: InspectorSelector<S, QueryInfo[]>;
readonly selectCurrentQueryInfo: InspectorSelector<S, QueryInfo | null>;
readonly selectAllVisbileQueries: InspectorSelector<S, RtkResourceInfo[]>;
readonly selectCurrentQueryInfo: InspectorSelector<S, RtkResourceInfo | null>;
readonly selectSearchQueryRegex: InspectorSelector<S, RegExp | null>;
readonly selectCurrentQueryTags: InspectorSelector<S, RtkQueryTag[]>;
readonly selectApiStatsOfCurrentQuery: InspectorSelector<S, ApiStats | null>;
@ -86,13 +88,13 @@ export interface InspectorSelectors<S> {
export function createInspectorSelectors<S>(): InspectorSelectors<S> {
const selectQueryComparator = ({
monitorState,
}: SelectorsSource<S>): Comparator<QueryInfo> => {
}: SelectorsSource<S>): Comparator<RtkResourceInfo> => {
return queryComparators[monitorState.queryForm.values.queryComparator];
};
const selectQueryListFilter = ({
monitorState,
}: SelectorsSource<S>): FilterList<QueryInfo> => {
}: SelectorsSource<S>): FilterList<RtkResourceInfo> => {
return queryListFilters[monitorState.queryForm.values.queryFilter];
};
@ -108,6 +110,11 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
extractAllApiQueries
);
const selectAllMutations = createSelector(
selectApiStates,
extractAllApiMutations
);
const selectSearchQueryRegex = createSelector(
({ monitorState }: SelectorsSource<S>) =>
monitorState.queryForm.values.searchValue,
@ -138,13 +145,21 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
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<S>(): InspectorSelectors<S> {
const selectCurrentQueryInfo = createSelector(
selectAllQueries,
selectAllMutations,
({ monitorState }: SelectorsSource<S>) => 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;
}
);

View File

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

View File

@ -61,25 +61,33 @@ export interface ExternalProps<S, A extends Action<unknown>> {
}
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<T = string> {
export interface SelectOption<
T = string,
VisConfig extends string = 'default'
> {
label: string;
value: T;
visible?: Record<VisConfig | 'default', boolean> | boolean;
}
export interface SelectorsSource<S> {
@ -136,7 +144,8 @@ export interface ApiStats {
}>;
}
export interface TabOption<S, P> extends SelectOption<S> {
export interface TabOption<S, P, V extends string = 'default'>
extends SelectOption<S, V> {
component: ComponentType<P>;
}

View File

@ -1,5 +1,5 @@
import { QueryStatus } from '@reduxjs/toolkit/query';
import { QueryInfo, SelectOption } from '../types';
import { RtkResourceInfo, SelectOption } from '../types';
export interface Comparator<T> {
(a: T, b: T): number;
@ -22,11 +22,11 @@ export const sortQueryOptions: SelectOption<QueryComparators>[] = [
];
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<T extends string | number | boolean | null>(
}
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<QueryComparators, Comparator<QueryInfo>>
Record<QueryComparators, Comparator<RtkResourceInfo>>
> = {
[QueryComparators.fulfilledTimeStamp]: sortQueryByFulfilled,
[QueryComparators.status]: sortQueryByStatus,

View File

@ -1,4 +1,4 @@
import { QueryInfo, SelectOption } from '../types';
import { RtkResourceInfo, SelectOption } from '../types';
export interface FilterList<T> {
(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<QueryFilters>[] = [
@ -62,7 +69,7 @@ export const filterQueryOptions: SelectOption<QueryFilters>[] = [
];
export const queryListFilters: Readonly<
Record<QueryFilters, FilterList<QueryInfo>>
Record<QueryFilters, FilterList<RtkResourceInfo>>
> = {
[QueryFilters.queryKey]: filterByQueryKey,
[QueryFilters.endpointName]: filterByEndpointName,

View File

@ -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<T>(comparator: Comparator<T>): Comparator<T> {
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<typeof getApiStatesOf>
): 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<unknown>['actionsById']
): AnyAction[] {
if (!currentQuery) {
return emptyArray;
}
const matcher = isAnyOf(matchesQueryKey(currentQuery.queryKey));
let matcher: ReturnType<typeof macthesRequestId>;
if (currentQuery.type === 'mutation') {
matcher = isAllOf(
matchesReducerPath(currentQuery.reducerPath),
macthesRequestId(currentQuery.queryKey)
);
} else {
matcher = isAllOf(
matchesReducerPath(currentQuery.reducerPath),
matchesQueryKey(currentQuery.queryKey)
);
}
const output: AnyAction[] = [];

View File

@ -0,0 +1,16 @@
import { TabOption } from '../types';
export function isTabVisible<St, Props, Vis extends string>(
tab: TabOption<St, Props, Vis>,
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;
}