refactor(rtk-query): add custom logic for TreeView shouldExpandNode

Other changes:

* feat: added duration in QueryPreviewInfo tab

* refactor: TreeView component
This commit is contained in:
FaberVitale 2021-06-27 13:00:29 +02:00
parent 7a274d9025
commit dc5045f280
3 changed files with 143 additions and 23 deletions

View File

@ -1,5 +1,7 @@
import { createSelector } from '@reduxjs/toolkit';
import React, { ReactNode, PureComponent } from 'react'; 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'; import { TreeView } from './TreeView';
export interface QueryPreviewActionsProps { export interface QueryPreviewActionsProps {
@ -7,10 +9,59 @@ export interface QueryPreviewActionsProps {
actionsOfQuery: AnyAction[]; actionsOfQuery: AnyAction[];
} }
const keySep = ' - ';
export class QueryPreviewActions extends PureComponent<QueryPreviewActionsProps> { export class QueryPreviewActions extends PureComponent<QueryPreviewActionsProps> {
selectFormattedActions = createSelector<
AnyAction[],
AnyAction[],
Record<string, AnyAction>
>(identity, (actions) => {
const output: Record<string, AnyAction> = {};
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<string>)?.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 { render(): ReactNode {
const { isWideLayout, actionsOfQuery } = this.props; const { isWideLayout, actionsOfQuery } = this.props;
return <TreeView data={actionsOfQuery} isWideLayout={isWideLayout} />; return (
<TreeView
data={this.selectFormattedActions(actionsOfQuery)}
isWideLayout={isWideLayout}
shouldExpandNode={this.shouldExpandNode}
/>
);
} }
} }

View File

@ -1,18 +1,22 @@
import { createSelector, Selector } from '@reduxjs/toolkit'; import { createSelector, Selector } from '@reduxjs/toolkit';
import { QueryStatus } from '@reduxjs/toolkit/dist/query';
import React, { ReactNode, PureComponent } from 'react'; import React, { ReactNode, PureComponent } from 'react';
import { QueryInfo, RtkQueryState, RTKStatusFlags } from '../types'; import { QueryInfo, RtkQueryState, RTKStatusFlags } from '../types';
import { formatMs } from '../utils/formatters';
import { identity } from '../utils/object'; import { identity } from '../utils/object';
import { getQueryStatusFlags } from '../utils/rtk-query'; import { getQueryStatusFlags } from '../utils/rtk-query';
import { TreeView } from './TreeView'; import { TreeView } from './TreeView';
type ComputedQueryInfo = { type QueryTimings = {
startedAt: string; startedAt: string;
latestFetchAt: string; latestFetchAt: string;
duration: string;
}; };
interface FormattedQuery extends ComputedQueryInfo { interface FormattedQuery {
queryKey: string; queryKey: string;
reducerPath: string; reducerPath: string;
timings: QueryTimings;
statusFlags: RTKStatusFlags; statusFlags: RTKStatusFlags;
query: RtkQueryState; query: RtkQueryState;
} }
@ -22,6 +26,16 @@ export interface QueryPreviewInfoProps {
isWideLayout: boolean; isWideLayout: boolean;
} }
export class QueryPreviewInfo extends PureComponent<QueryPreviewInfoProps> { export class QueryPreviewInfo extends PureComponent<QueryPreviewInfoProps> {
shouldExpandNode = (
keyPath: (string | number)[],
value: unknown,
layer: number
): boolean => {
const lastKey = keyPath[keyPath.length - 1];
return layer <= 1 && lastKey !== 'query';
};
selectFormattedQuery: Selector<QueryInfo, FormattedQuery> = createSelector( selectFormattedQuery: Selector<QueryInfo, FormattedQuery> = createSelector(
identity, identity,
(queryInfo: QueryInfo): FormattedQuery => { (queryInfo: QueryInfo): FormattedQuery => {
@ -37,13 +51,29 @@ export class QueryPreviewInfo extends PureComponent<QueryPreviewInfoProps> {
const statusFlags = getQueryStatusFlags(query); 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 { return {
queryKey, queryKey,
reducerPath, reducerPath,
startedAt,
latestFetchAt,
statusFlags,
query: queryInfo.query, query: queryInfo.query,
statusFlags,
timings,
}; };
} }
); );
@ -52,6 +82,12 @@ export class QueryPreviewInfo extends PureComponent<QueryPreviewInfoProps> {
const { queryInfo, isWideLayout } = this.props; const { queryInfo, isWideLayout } = this.props;
const formattedQuery = this.selectFormattedQuery(queryInfo); const formattedQuery = this.selectFormattedQuery(queryInfo);
return <TreeView data={formattedQuery} isWideLayout={isWideLayout} />; return (
<TreeView
data={formattedQuery}
isWideLayout={isWideLayout}
shouldExpandNode={this.shouldExpandNode}
/>
);
} }
} }

View File

@ -1,7 +1,7 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import React, { ComponentProps, ReactNode } from 'react'; import React, { ComponentProps, ReactNode } from 'react';
import JSONTree from 'react-json-tree'; 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 { DATA_TYPE_KEY } from '../monitor-config';
import { import {
getJsonTreeTheme, getJsonTreeTheme,
@ -10,28 +10,68 @@ import {
import { createTreeItemLabelRenderer, getItemString } from '../styles/tree'; import { createTreeItemLabelRenderer, getItemString } from '../styles/tree';
import { identity } from '../utils/object'; import { identity } from '../utils/object';
export interface TreeViewProps { export interface TreeViewProps
extends Partial<
Pick<
ComponentProps<typeof JSONTree>,
'keyPath' | 'shouldExpandNode' | 'hideRoot'
>
> {
data: unknown; data: unknown;
isWideLayout: boolean; isWideLayout: boolean;
before?: ReactNode; before?: ReactNode;
after?: ReactNode; after?: ReactNode;
children?: ReactNode; children?: ReactNode;
keyPath?: ComponentProps<typeof JSONTree>['keyPath'];
} }
export class TreeView extends React.PureComponent<TreeViewProps> { export class TreeView extends React.PureComponent<TreeViewProps> {
static defaultProps = {
hideRoot: true,
shouldExpandNode: (
keyPath: (string | number)[],
value: unknown,
layer: number
): boolean => {
return layer < 2;
},
};
readonly selectLabelRenderer = createSelector< readonly selectLabelRenderer = createSelector<
StylingFunction, StylingFunction,
StylingFunction, StylingFunction,
ReturnType<typeof createTreeItemLabelRenderer> ReturnType<typeof createTreeItemLabelRenderer>
>(identity, createTreeItemLabelRenderer); >(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<typeof getJsonTreeTheme>
>(identity, getJsonTreeTheme);
constructor(props: TreeViewProps) { constructor(props: TreeViewProps) {
super(props); super(props);
} }
render(): ReactNode { render(): ReactNode {
const { isWideLayout, data, before, after, children, keyPath } = this.props; const {
isWideLayout,
data,
before,
after,
children,
keyPath,
shouldExpandNode,
hideRoot,
} = this.props;
return ( return (
<StyleUtilsContext.Consumer> <StyleUtilsContext.Consumer>
@ -41,20 +81,13 @@ export class TreeView extends React.PureComponent<TreeViewProps> {
{before} {before}
<JSONTree <JSONTree
keyPath={keyPath} keyPath={keyPath}
shouldExpandNode={shouldExpandNode}
data={data} data={data}
labelRenderer={this.selectLabelRenderer(styling)} labelRenderer={this.selectLabelRenderer(styling)}
theme={getJsonTreeTheme(base16Theme)} theme={this.selectTheme(base16Theme)}
invertTheme={invertTheme} invertTheme={invertTheme}
getItemString={(type, data) => getItemString={this.selectGetItemString(styling, isWideLayout)}
getItemString( hideRoot={hideRoot}
styling,
type,
data,
DATA_TYPE_KEY,
isWideLayout
)
}
hideRoot
/> />
{after} {after}
{children} {children}