mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-07-27 08:30:02 +03:00
feat(rtk-query): display mutations in queryList
This commit is contained in:
parent
95da893616
commit
e4c5720d87
|
@ -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>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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[] = [];
|
||||
|
||||
|
|
16
packages/redux-devtools-rtk-query-monitor/src/utils/tabs.ts
Normal file
16
packages/redux-devtools-rtk-query-monitor/src/utils/tabs.ts
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user