refactor(rtk-query): improve UI of api tab

This commit is contained in:
FaberVitale 2021-07-31 22:42:43 +02:00
parent 0ae7deb8d6
commit 6a87019cb7
7 changed files with 135 additions and 68 deletions

View File

@ -8,8 +8,8 @@ import { DevToolsSelector } from 'features/DevTools/DevToolsSelector';
export function App() { export function App() {
return ( return (
<article> <main className="rtk-query-demo-app">
<Heading as="h1" p="0"> <Heading as="h1" p="2">
RTK Query inspector monitor demo RTK Query inspector monitor demo
</Heading> </Heading>
<PokemonView /> <PokemonView />
@ -66,6 +66,6 @@ export function App() {
</ListItem> </ListItem>
</UnorderedList> </UnorderedList>
</Flex> </Flex>
</article> </main>
); );
} }

View File

@ -12,27 +12,6 @@ code {
monospace; monospace;
} }
h1 {
font-weight: 700;
font-size: 1.4em;
}
h2 {
font-size: 1.2em;
}
h1,
h2,
h3,
h4,
h5,
h6 {
text-align: left;
font-family: inherit;
padding: 0.5em;
margin: 0;
}
section { section {
display: block; display: block;
max-width: 65%; max-width: 65%;

View File

@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit';
import React, { ReactNode, PureComponent } from 'react'; import React, { ReactNode, PureComponent } from 'react';
import { ApiStats, RtkQueryApiState } from '../types'; import { ApiStats, RtkQueryApiState } from '../types';
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
import { TreeView } from './TreeView'; import { TreeView } from './TreeView';
export interface QueryPreviewApiProps { export interface QueryPreviewApiProps {
@ -10,24 +10,63 @@ export interface QueryPreviewApiProps {
} }
export class QueryPreviewApi extends PureComponent<QueryPreviewApiProps> { export class QueryPreviewApi extends PureComponent<QueryPreviewApiProps> {
selectData = createSelector( shouldExpandApiStateNode = (
[ keyPath: (string | number)[],
({ apiState }: QueryPreviewApiProps) => apiState, value: unknown,
({ apiStats }: QueryPreviewApiProps) => apiStats, layer: number
], ): boolean => {
(apiState, apiStats) => ({ const lastKey = keyPath[keyPath.length - 1];
reducerPath: apiState?.config?.reducerPath ?? null,
apiState: apiState, return layer <= 1 && lastKey !== 'config';
stats: apiStats, };
})
);
render(): ReactNode { render(): ReactNode {
const { apiStats, isWideLayout, apiState } = this.props;
if (!apiState) {
return null;
}
const hasMutations = Object.keys(apiState.mutations).length > 0;
const hasQueries = Object.keys(apiState.queries).length > 0;
return ( return (
<TreeView <StyleUtilsContext.Consumer>
data={this.selectData(this.props)} {({ styling }) => (
isWideLayout={this.props.isWideLayout} <article {...styling('tabContent')}>
/> <h2>{apiState.config.reducerPath}</h2>
<TreeView
before={<h3>State</h3>}
data={apiState}
shouldExpandNode={this.shouldExpandApiStateNode}
isWideLayout={isWideLayout}
/>
{apiStats && (
<>
<TreeView
before={<h3>Tally</h3>}
data={apiStats.tally}
isWideLayout={isWideLayout}
/>
{hasQueries && (
<TreeView
before={<h3>Queries Timings</h3>}
data={apiStats.timings.queries}
isWideLayout={isWideLayout}
/>
)}
{hasMutations && (
<TreeView
before={<h3>Mutations Timings</h3>}
data={apiStats.timings.mutations}
isWideLayout={isWideLayout}
/>
)}
</>
)}
</article>
)}
</StyleUtilsContext.Consumer>
); );
} }
} }

View File

@ -0,0 +1,12 @@
import * as React from 'react';
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
export type UListProps = React.HTMLAttributes<HTMLUListElement>;
export function UList(props: UListProps): JSX.Element {
return (
<StyleUtilsContext.Consumer>
{({ styling }) => <ul {...props} {...styling('uList')} />}
</StyleUtilsContext.Consumer>
);
}

View File

@ -39,6 +39,10 @@ export const colorMap = (theme: reduxThemes.Base16Theme) =>
LINK_COLOR: rgba(theme.base0E, 90), LINK_COLOR: rgba(theme.base0E, 90),
LINK_HOVER_COLOR: theme.base0E, LINK_HOVER_COLOR: theme.base0E,
ERROR_COLOR: theme.base08, ERROR_COLOR: theme.base08,
ULIST_DISC_COLOR: theme.base0D,
ULIST_COLOR: rgba(theme.base06, 60),
ULIST_STRONG_COLOR: theme.base0B,
TAB_CONTENT_COLOR: rgba(theme.base06, 60),
} as const); } as const);
type Color = keyof ReturnType<typeof colorMap>; type Color = keyof ReturnType<typeof colorMap>;
@ -400,7 +404,42 @@ const getSheetFromColorMap = (map: ColorMap) => {
treeWrapper: { treeWrapper: {
overflowX: 'auto', overflowX: 'auto',
overflowY: 'auto', overflowY: 'auto',
padding: '1em', padding: '0.5em 1em',
},
tabContent: {
display: 'block',
overflowY: 'auto',
padding: '0.5em 0',
color: map.TAB_CONTENT_COLOR,
'& h2': {
color: map.ULIST_STRONG_COLOR,
padding: '0.5em 1em',
fontWeight: 700,
},
'& h3': {
color: map.ULIST_STRONG_COLOR,
},
},
uList: {
listStyle: 'none',
padding: '0 0 0 1em',
color: map.ULIST_COLOR,
'& > li': {
listStyle: 'none',
},
'& > li::before': {
content: '"\\2022"',
display: 'inline-block',
paddingRight: '0.5em',
color: map.ULIST_DISC_COLOR,
fontSize: '0.8em',
},
'& strong': {
color: map.ULIST_STRONG_COLOR,
},
}, },
}; };
}; };

View File

@ -112,8 +112,8 @@ export type QueryTally = {
Tally; Tally;
export interface QueryTimings { export interface QueryTimings {
readonly oldestFetch: { key: string; at: string } | null; readonly oldest: { key: string; at: string } | null;
readonly latestFetch: { key: string; at: string } | null; readonly latest: { key: string; at: string } | null;
readonly slowest: { key: string; duration: string } | null; readonly slowest: { key: string; duration: string } | null;
readonly fastest: { key: string; duration: string } | null; readonly fastest: { key: string; duration: string } | null;
readonly average: string; readonly average: string;
@ -128,9 +128,9 @@ export interface ApiTimings {
export interface ApiStats { export interface ApiStats {
readonly timings: ApiTimings; readonly timings: ApiTimings;
readonly tally: Readonly<{ readonly tally: Readonly<{
subscriptions: Tally; subscriptions: number;
queries: QueryTally; queries: QueryTally;
tagTypes: Tally; tagTypes: number;
mutations: QueryTally; mutations: QueryTally;
}>; }>;
} }

View File

@ -179,18 +179,16 @@ function computeQueryTallyOf(
function tallySubscriptions( function tallySubscriptions(
subsState: RtkQueryApiState['subscriptions'] subsState: RtkQueryApiState['subscriptions']
): ApiStats['tally']['subscriptions'] { ): number {
const subsOfQueries = Object.values(subsState); const subsOfQueries = Object.values(subsState);
const output: ApiStats['tally']['subscriptions'] = { let output = 0;
count: 0,
};
for (let i = 0, len = subsOfQueries.length; i < len; i++) { for (let i = 0, len = subsOfQueries.length; i < len; i++) {
const subsOfQuery = subsOfQueries[i]; const subsOfQuery = subsOfQueries[i];
if (subsOfQuery) { if (subsOfQuery) {
output.count += Object.keys(subsOfQuery).length; output += Object.keys(subsOfQuery).length;
} }
} }
@ -204,8 +202,8 @@ function computeQueryApiTimings(
): QueryTimings { ): QueryTimings {
type SpeedReport = { key: string | null; at: string | number }; type SpeedReport = { key: string | null; at: string | number };
type DurationReport = { key: string | null; duration: string | number }; type DurationReport = { key: string | null; duration: string | number };
let latestFetch: null | SpeedReport = { key: null, at: -1 }; let latest: null | SpeedReport = { key: null, at: -1 };
let oldestFetch: null | SpeedReport = { let oldest: null | SpeedReport = {
key: null, key: null,
at: Number.MAX_SAFE_INTEGER, at: Number.MAX_SAFE_INTEGER,
}; };
@ -227,14 +225,14 @@ function computeQueryApiTimings(
const startedTimeStamp = query?.startedTimeStamp; const startedTimeStamp = query?.startedTimeStamp;
if (typeof fulfilledTimeStamp === 'number') { if (typeof fulfilledTimeStamp === 'number') {
if (fulfilledTimeStamp > latestFetch.at) { if (fulfilledTimeStamp > latest.at) {
latestFetch.key = queryKey; latest.key = queryKey;
latestFetch.at = fulfilledTimeStamp; latest.at = fulfilledTimeStamp;
} }
if (fulfilledTimeStamp < oldestFetch.at) { if (fulfilledTimeStamp < oldest.at) {
oldestFetch.key = queryKey; oldest.key = queryKey;
oldestFetch.at = fulfilledTimeStamp; oldest.at = fulfilledTimeStamp;
} }
if ( if (
@ -258,16 +256,16 @@ function computeQueryApiTimings(
} }
} }
if (latestFetch.key !== null) { if (latest.key !== null) {
latestFetch.at = new Date(latestFetch.at).toISOString(); latest.at = new Date(latest.at).toISOString();
} else { } else {
latestFetch = null; latest = null;
} }
if (oldestFetch.key !== null) { if (oldest.key !== null) {
oldestFetch.at = new Date(oldestFetch.at).toISOString(); oldest.at = new Date(oldest.at).toISOString();
} else { } else {
oldestFetch = null; oldest = null;
} }
if (slowest.key !== null) { if (slowest.key !== null) {
@ -293,8 +291,8 @@ function computeQueryApiTimings(
: '-'; : '-';
return { return {
latestFetch, latest,
oldestFetch, oldest,
slowest, slowest,
fastest, fastest,
average, average,
@ -319,10 +317,10 @@ export function generateApiStatsOfCurrentQuery(
return { return {
timings: computeApiTimings(api), timings: computeApiTimings(api),
tally: { tally: {
subscriptions: tallySubscriptions(api.subscriptions),
queries: computeQueryTallyOf(api.queries), queries: computeQueryTallyOf(api.queries),
tagTypes: { count: Object.keys(api.provided).length },
mutations: computeQueryTallyOf(api.mutations), mutations: computeQueryTallyOf(api.mutations),
tagTypes: Object.keys(api.provided).length,
subscriptions: tallySubscriptions(api.subscriptions),
}, },
}; };
} }