feat(rtk-query): add timings to apiStats sections

This commit is contained in:
FaberVitale 2021-06-22 17:17:57 +02:00
parent 136fd02ff8
commit c3b6887e82
4 changed files with 119 additions and 21 deletions

View File

@ -122,7 +122,8 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends PureComponent<
const currentQueryInfo = const currentQueryInfo =
this.selectors.selectCurrentQueryInfo(selectorsSource); this.selectors.selectCurrentQueryInfo(selectorsSource);
const currentRtkApi = getApiStateOf(currentQueryInfo, apiStates); const currentRtkApi =
this.selectors.selectApiOfCurrentQuery(selectorsSource);
const currentQuerySubscriptions = getQuerySubscriptionsOf( const currentQuerySubscriptions = getQuerySubscriptionsOf(
currentQueryInfo, currentQueryInfo,
apiStates apiStates

View File

@ -1,6 +1,13 @@
import { Action, createSelector, Selector } from '@reduxjs/toolkit'; import { Action, createSelector, Selector } from '@reduxjs/toolkit';
import { RtkQueryInspectorProps } from './containers/RtkQueryInspector'; import { RtkQueryInspectorProps } from './containers/RtkQueryInspector';
import { ApiStats, QueryInfo, RtkQueryTag, SelectorsSource } from './types'; import {
ApiStats,
QueryInfo,
RtkQueryApiState,
RtkQueryTag,
SelectorsSource,
RtkQueryProvided,
} from './types';
import { Comparator, queryComparators } from './utils/comparators'; import { Comparator, queryComparators } from './utils/comparators';
import { FilterList, queryListFilters } from './utils/filters'; import { FilterList, queryListFilters } from './utils/filters';
import { escapeRegExpSpecialCharacter } from './utils/regexp'; import { escapeRegExpSpecialCharacter } from './utils/regexp';
@ -52,6 +59,10 @@ export interface InspectorSelectors<S> {
readonly selectSearchQueryRegex: InspectorSelector<S, RegExp | null>; readonly selectSearchQueryRegex: InspectorSelector<S, RegExp | null>;
readonly selectCurrentQueryTags: InspectorSelector<S, RtkQueryTag[]>; readonly selectCurrentQueryTags: InspectorSelector<S, RtkQueryTag[]>;
readonly selectApiStatsOfCurrentQuery: InspectorSelector<S, ApiStats | null>; readonly selectApiStatsOfCurrentQuery: InspectorSelector<S, ApiStats | null>;
readonly selectApiOfCurrentQuery: InspectorSelector<
S,
RtkQueryApiState | null
>;
} }
export function createInspectorSelectors<S>(): InspectorSelectors<S> { export function createInspectorSelectors<S>(): InspectorSelectors<S> {
@ -131,17 +142,33 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
} }
); );
const selectApiOfCurrentQuery: InspectorSelector<S, null | RtkQueryApiState> =
(selectorsSource: SelectorsSource<S>) => {
const apiStates = selectApiStates(selectorsSource);
const currentQueryInfo = selectCurrentQueryInfo(selectorsSource);
if (!apiStates || !currentQueryInfo) {
return null;
}
return apiStates[currentQueryInfo.reducerPath] ?? null;
};
const selectProvidedOfCurrentQuery: InspectorSelector<
S,
null | RtkQueryProvided
> = (selectorsSource: SelectorsSource<S>) => {
return selectApiOfCurrentQuery(selectorsSource)?.provided ?? null;
};
const selectCurrentQueryTags = createSelector( const selectCurrentQueryTags = createSelector(
selectApiStates, [selectCurrentQueryInfo, selectProvidedOfCurrentQuery],
selectCurrentQueryInfo, getQueryTagsOf
(apiState, currentQueryInfo) => getQueryTagsOf(currentQueryInfo, apiState)
); );
const selectApiStatsOfCurrentQuery = createSelector( const selectApiStatsOfCurrentQuery = createSelector(
selectApiStates, selectApiOfCurrentQuery,
selectCurrentQueryInfo, generateApiStatsOfCurrentQuery
(apiState, currentQueryInfo) =>
generateApiStatsOfCurrentQuery(currentQueryInfo, apiState)
); );
return { return {
@ -153,5 +180,6 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
selectCurrentQueryInfo, selectCurrentQueryInfo,
selectCurrentQueryTags, selectCurrentQueryTags,
selectApiStatsOfCurrentQuery, selectApiStatsOfCurrentQuery,
selectApiOfCurrentQuery,
}; };
} }

View File

@ -109,13 +109,24 @@ export type QueryTally = {
} & } &
Tally; Tally;
export interface QueryTimings {
readonly oldestFetch: { key: string; at: string } | null;
readonly latestFetch: { key: string; at: string } | null;
}
export interface ApiTimings {
readonly queries: QueryTimings;
readonly mutations: QueryTimings;
}
export interface ApiStats { export interface ApiStats {
readonly tally: { readonly timings: ApiTimings;
readonly tally: Readonly<{
subscriptions: Tally; subscriptions: Tally;
queries: QueryTally; queries: QueryTally;
tagTypes: Tally; tagTypes: Tally;
mutations: QueryTally; mutations: QueryTally;
}; }>;
} }
export interface QueryPreviewTabProps extends StyleUtils { export interface QueryPreviewTabProps extends StyleUtils {

View File

@ -11,6 +11,9 @@ import {
MutationInfo, MutationInfo,
ApiStats, ApiStats,
QueryTally, QueryTally,
RtkQueryProvided,
ApiTimings,
QueryTimings,
} from '../types'; } from '../types';
import { missingTagId } from '../monitor-config'; import { missingTagId } from '../monitor-config';
import { Comparator } from './comparators'; import { Comparator } from './comparators';
@ -192,18 +195,75 @@ function tallySubscriptions(
return output; return output;
} }
function computeQueryApiTimings(
queriesOrMutations:
| RtkQueryApiState['queries']
| RtkQueryApiState['mutations']
): QueryTimings {
let latestFetch = null;
let latestFetchedQueryKey: string | null = null;
let latestFetchedQueryTiming = -1;
let oldestFetch = null;
let oldestFetchedQueryKey: string | null = null;
let oldestFetchedQueryTiming = Number.MAX_SAFE_INTEGER;
const queryKeys = Object.keys(queriesOrMutations);
for (let i = 0, len = queryKeys.length; i < len; i++) {
const queryKey = queryKeys[i];
const query = queriesOrMutations[queryKey];
const fulfilledTimeStamp = query?.fulfilledTimeStamp;
if (typeof fulfilledTimeStamp === 'number') {
if (fulfilledTimeStamp > latestFetchedQueryTiming) {
latestFetchedQueryKey = queryKey;
latestFetchedQueryTiming = fulfilledTimeStamp;
}
if (fulfilledTimeStamp < oldestFetchedQueryTiming) {
oldestFetchedQueryKey = queryKey;
oldestFetchedQueryTiming = fulfilledTimeStamp;
}
}
}
if (latestFetchedQueryKey) {
latestFetch = {
key: latestFetchedQueryKey,
at: new Date(latestFetchedQueryTiming).toISOString(),
};
}
if (oldestFetchedQueryKey) {
oldestFetch = {
key: oldestFetchedQueryKey,
at: new Date(oldestFetchedQueryTiming).toISOString(),
};
}
return {
latestFetch,
oldestFetch,
};
}
function computeApiTimings(api: RtkQueryApiState): ApiTimings {
return {
queries: computeQueryApiTimings(api.queries),
mutations: computeQueryApiTimings(api.mutations),
};
}
export function generateApiStatsOfCurrentQuery( export function generateApiStatsOfCurrentQuery(
queryInfo: QueryInfo | null, api: RtkQueryApiState | null
apiStates: ReturnType<typeof getApiStatesOf>
): ApiStats | null { ): ApiStats | null {
if (!apiStates || !queryInfo) { if (!api) {
return null; return null;
} }
const { reducerPath } = queryInfo;
const api = apiStates[reducerPath];
return { return {
timings: computeApiTimings(api),
tally: { tally: {
subscriptions: tallySubscriptions(api.subscriptions), subscriptions: tallySubscriptions(api.subscriptions),
queries: computeQueryTallyOf(api.queries), queries: computeQueryTallyOf(api.queries),
@ -268,14 +328,12 @@ export function getProvidedOf(
export function getQueryTagsOf( export function getQueryTagsOf(
queryInfo: QueryInfo | null, queryInfo: QueryInfo | null,
apiStates: ReturnType<typeof getApiStatesOf> provided: RtkQueryProvided | null
): RtkQueryTag[] { ): RtkQueryTag[] {
if (!apiStates || !queryInfo) { if (!queryInfo || !provided) {
return emptyArray; return emptyArray;
} }
const provided = apiStates[queryInfo.reducerPath].provided;
const tagTypes = Object.keys(provided); const tagTypes = Object.keys(provided);
if (tagTypes.length < 1) { if (tagTypes.length < 1) {