From dc5045f2807b8cb7976044366b40a1cd36057dff Mon Sep 17 00:00:00 2001 From: FaberVitale Date: Sun, 27 Jun 2021 13:00:29 +0200 Subject: [PATCH] refactor(rtk-query): add custom logic for TreeView shouldExpandNode Other changes: * feat: added duration in QueryPreviewInfo tab * refactor: TreeView component --- .../src/components/QueryPreviewActions.tsx | 55 +++++++++++++++- .../src/components/QueryPreviewInfo.tsx | 48 ++++++++++++-- .../src/components/TreeView.tsx | 63 ++++++++++++++----- 3 files changed, 143 insertions(+), 23 deletions(-) diff --git a/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreviewActions.tsx b/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreviewActions.tsx index cea3e6b9..bb32c2f9 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreviewActions.tsx +++ b/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreviewActions.tsx @@ -1,5 +1,7 @@ +import { createSelector } from '@reduxjs/toolkit'; import React, { ReactNode, PureComponent } from 'react'; -import { AnyAction } from 'redux'; +import { Action, AnyAction } from 'redux'; +import { emptyRecord, identity } from '../utils/object'; import { TreeView } from './TreeView'; export interface QueryPreviewActionsProps { @@ -7,10 +9,59 @@ export interface QueryPreviewActionsProps { actionsOfQuery: AnyAction[]; } +const keySep = ' - '; + export class QueryPreviewActions extends PureComponent { + selectFormattedActions = createSelector< + AnyAction[], + AnyAction[], + Record + >(identity, (actions) => { + const output: Record = {}; + + if (actions.length === 0) { + return emptyRecord; + } + + for (let i = 0, len = actions.length; i < len; i++) { + const action = actions[i]; + const key = `${i}${keySep}${(action as Action)?.type ?? ''}`; + output[key] = action; + } + + return output; + }); + + shouldExpandNode = ( + keyPath: (string | number)[], + value: unknown, + layer: number + ): boolean => { + if (layer === 1) { + const len = this.props.actionsOfQuery.length; + const lastKey = keyPath[keyPath.length - 1]; + + if (typeof lastKey === 'string') { + const index = Number(lastKey.split(keySep)[0]); + + return len - index < 2; + } + + return false; + } + + return layer <= 1; + }; + render(): ReactNode { const { isWideLayout, actionsOfQuery } = this.props; - return ; + return ( + + ); } } 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 3588cf47..f9cee0f5 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreviewInfo.tsx +++ b/packages/redux-devtools-rtk-query-monitor/src/components/QueryPreviewInfo.tsx @@ -1,18 +1,22 @@ 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 { formatMs } from '../utils/formatters'; import { identity } from '../utils/object'; import { getQueryStatusFlags } from '../utils/rtk-query'; import { TreeView } from './TreeView'; -type ComputedQueryInfo = { +type QueryTimings = { startedAt: string; latestFetchAt: string; + duration: string; }; -interface FormattedQuery extends ComputedQueryInfo { +interface FormattedQuery { queryKey: string; reducerPath: string; + timings: QueryTimings; statusFlags: RTKStatusFlags; query: RtkQueryState; } @@ -22,6 +26,16 @@ export interface QueryPreviewInfoProps { isWideLayout: boolean; } export class QueryPreviewInfo extends PureComponent { + shouldExpandNode = ( + keyPath: (string | number)[], + value: unknown, + layer: number + ): boolean => { + const lastKey = keyPath[keyPath.length - 1]; + + return layer <= 1 && lastKey !== 'query'; + }; + selectFormattedQuery: Selector = createSelector( identity, (queryInfo: QueryInfo): FormattedQuery => { @@ -37,13 +51,29 @@ export class QueryPreviewInfo extends PureComponent { const statusFlags = getQueryStatusFlags(query); + const timings = { + startedAt, + latestFetchAt, + duration: '-', + }; + + if ( + query.fulfilledTimeStamp && + query.startedTimeStamp && + query.status !== QueryStatus.pending && + query.startedTimeStamp <= query.fulfilledTimeStamp + ) { + timings.duration = formatMs( + query.fulfilledTimeStamp - query.startedTimeStamp + ); + } + return { queryKey, reducerPath, - startedAt, - latestFetchAt, - statusFlags, query: queryInfo.query, + statusFlags, + timings, }; } ); @@ -52,6 +82,12 @@ export class QueryPreviewInfo extends PureComponent { const { queryInfo, isWideLayout } = this.props; const formattedQuery = this.selectFormattedQuery(queryInfo); - return ; + return ( + + ); } } diff --git a/packages/redux-devtools-rtk-query-monitor/src/components/TreeView.tsx b/packages/redux-devtools-rtk-query-monitor/src/components/TreeView.tsx index 8c8e62a4..8f97977c 100644 --- a/packages/redux-devtools-rtk-query-monitor/src/components/TreeView.tsx +++ b/packages/redux-devtools-rtk-query-monitor/src/components/TreeView.tsx @@ -1,7 +1,7 @@ import { createSelector } from '@reduxjs/toolkit'; import React, { ComponentProps, ReactNode } from 'react'; import JSONTree from 'react-json-tree'; -import { StylingFunction } from 'react-base16-styling'; +import { Base16Theme, StylingFunction } from 'react-base16-styling'; import { DATA_TYPE_KEY } from '../monitor-config'; import { getJsonTreeTheme, @@ -10,28 +10,68 @@ import { import { createTreeItemLabelRenderer, getItemString } from '../styles/tree'; import { identity } from '../utils/object'; -export interface TreeViewProps { +export interface TreeViewProps + extends Partial< + Pick< + ComponentProps, + 'keyPath' | 'shouldExpandNode' | 'hideRoot' + > + > { data: unknown; isWideLayout: boolean; before?: ReactNode; after?: ReactNode; children?: ReactNode; - keyPath?: ComponentProps['keyPath']; } export class TreeView extends React.PureComponent { + static defaultProps = { + hideRoot: true, + shouldExpandNode: ( + keyPath: (string | number)[], + value: unknown, + layer: number + ): boolean => { + return layer < 2; + }, + }; + readonly selectLabelRenderer = createSelector< StylingFunction, StylingFunction, ReturnType >(identity, createTreeItemLabelRenderer); + readonly selectGetItemString = createSelector( + [ + (styling: StylingFunction, _: boolean) => styling, + (_: StylingFunction, isWideLayout: boolean) => isWideLayout, + ], + (styling, isWideLayout) => (type: string, data: any) => + getItemString(styling, type, data, DATA_TYPE_KEY, isWideLayout) + ); + + readonly selectTheme = createSelector< + Base16Theme, + Base16Theme, + ReturnType + >(identity, getJsonTreeTheme); + constructor(props: TreeViewProps) { super(props); } render(): ReactNode { - const { isWideLayout, data, before, after, children, keyPath } = this.props; + const { + isWideLayout, + data, + before, + after, + children, + keyPath, + shouldExpandNode, + hideRoot, + } = this.props; return ( @@ -41,20 +81,13 @@ export class TreeView extends React.PureComponent { {before} - getItemString( - styling, - type, - data, - DATA_TYPE_KEY, - isWideLayout - ) - } - hideRoot + getItemString={this.selectGetItemString(styling, isWideLayout)} + hideRoot={hideRoot} /> {after} {children}