From c3b6887e82aaab4a2d0407aeebfdcd93546cbce4 Mon Sep 17 00:00:00 2001 From: FaberVitale Date: Tue, 22 Jun 2021 17:17:57 +0200 Subject: [PATCH] feat(rtk-query): add timings to apiStats sections --- .../src/containers/RtkQueryInspector.tsx | 3 +- .../src/selectors.ts | 44 +++++++++-- .../src/types.ts | 15 +++- .../src/utils/rtk-query.ts | 78 ++++++++++++++++--- 4 files changed, 119 insertions(+), 21 deletions(-) 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 caa4d069..15d2b1a5 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/containers/RtkQueryInspector.tsx +++ b/packages/redux-devtools-rtk-query-monitor/src/containers/RtkQueryInspector.tsx @@ -122,7 +122,8 @@ class RtkQueryInspector> extends PureComponent< const currentQueryInfo = this.selectors.selectCurrentQueryInfo(selectorsSource); - const currentRtkApi = getApiStateOf(currentQueryInfo, apiStates); + const currentRtkApi = + this.selectors.selectApiOfCurrentQuery(selectorsSource); const currentQuerySubscriptions = getQuerySubscriptionsOf( currentQueryInfo, apiStates diff --git a/packages/redux-devtools-rtk-query-monitor/src/selectors.ts b/packages/redux-devtools-rtk-query-monitor/src/selectors.ts index a9791917..069055aa 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/selectors.ts +++ b/packages/redux-devtools-rtk-query-monitor/src/selectors.ts @@ -1,6 +1,13 @@ import { Action, createSelector, Selector } from '@reduxjs/toolkit'; 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 { FilterList, queryListFilters } from './utils/filters'; import { escapeRegExpSpecialCharacter } from './utils/regexp'; @@ -52,6 +59,10 @@ export interface InspectorSelectors { readonly selectSearchQueryRegex: InspectorSelector; readonly selectCurrentQueryTags: InspectorSelector; readonly selectApiStatsOfCurrentQuery: InspectorSelector; + readonly selectApiOfCurrentQuery: InspectorSelector< + S, + RtkQueryApiState | null + >; } export function createInspectorSelectors(): InspectorSelectors { @@ -131,17 +142,33 @@ export function createInspectorSelectors(): InspectorSelectors { } ); + const selectApiOfCurrentQuery: InspectorSelector = + (selectorsSource: SelectorsSource) => { + 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) => { + return selectApiOfCurrentQuery(selectorsSource)?.provided ?? null; + }; + const selectCurrentQueryTags = createSelector( - selectApiStates, - selectCurrentQueryInfo, - (apiState, currentQueryInfo) => getQueryTagsOf(currentQueryInfo, apiState) + [selectCurrentQueryInfo, selectProvidedOfCurrentQuery], + getQueryTagsOf ); const selectApiStatsOfCurrentQuery = createSelector( - selectApiStates, - selectCurrentQueryInfo, - (apiState, currentQueryInfo) => - generateApiStatsOfCurrentQuery(currentQueryInfo, apiState) + selectApiOfCurrentQuery, + generateApiStatsOfCurrentQuery ); return { @@ -153,5 +180,6 @@ export function createInspectorSelectors(): InspectorSelectors { selectCurrentQueryInfo, selectCurrentQueryTags, selectApiStatsOfCurrentQuery, + selectApiOfCurrentQuery, }; } diff --git a/packages/redux-devtools-rtk-query-monitor/src/types.ts b/packages/redux-devtools-rtk-query-monitor/src/types.ts index a7dfb0f4..44d46124 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/types.ts +++ b/packages/redux-devtools-rtk-query-monitor/src/types.ts @@ -109,13 +109,24 @@ export type QueryTally = { } & 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 { - readonly tally: { + readonly timings: ApiTimings; + readonly tally: Readonly<{ subscriptions: Tally; queries: QueryTally; tagTypes: Tally; mutations: QueryTally; - }; + }>; } export interface QueryPreviewTabProps extends StyleUtils { 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 04758cbe..fe703f63 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 @@ -11,6 +11,9 @@ import { MutationInfo, ApiStats, QueryTally, + RtkQueryProvided, + ApiTimings, + QueryTimings, } from '../types'; import { missingTagId } from '../monitor-config'; import { Comparator } from './comparators'; @@ -192,18 +195,75 @@ function tallySubscriptions( 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( - queryInfo: QueryInfo | null, - apiStates: ReturnType + api: RtkQueryApiState | null ): ApiStats | null { - if (!apiStates || !queryInfo) { + if (!api) { return null; } - const { reducerPath } = queryInfo; - const api = apiStates[reducerPath]; - return { + timings: computeApiTimings(api), tally: { subscriptions: tallySubscriptions(api.subscriptions), queries: computeQueryTallyOf(api.queries), @@ -268,14 +328,12 @@ export function getProvidedOf( export function getQueryTagsOf( queryInfo: QueryInfo | null, - apiStates: ReturnType + provided: RtkQueryProvided | null ): RtkQueryTag[] { - if (!apiStates || !queryInfo) { + if (!queryInfo || !provided) { return emptyArray; } - const provided = apiStates[queryInfo.reducerPath].provided; - const tagTypes = Object.keys(provided); if (tagTypes.length < 1) {