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() {
return (
<article>
<Heading as="h1" p="0">
<main className="rtk-query-demo-app">
<Heading as="h1" p="2">
RTK Query inspector monitor demo
</Heading>
<PokemonView />
@ -66,6 +66,6 @@ export function App() {
</ListItem>
</UnorderedList>
</Flex>
</article>
</main>
);
}

View File

@ -12,27 +12,6 @@ code {
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 {
display: block;
max-width: 65%;

View File

@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit';
import React, { ReactNode, PureComponent } from 'react';
import { ApiStats, RtkQueryApiState } from '../types';
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
import { TreeView } from './TreeView';
export interface QueryPreviewApiProps {
@ -10,24 +10,63 @@ export interface QueryPreviewApiProps {
}
export class QueryPreviewApi extends PureComponent<QueryPreviewApiProps> {
selectData = createSelector(
[
({ apiState }: QueryPreviewApiProps) => apiState,
({ apiStats }: QueryPreviewApiProps) => apiStats,
],
(apiState, apiStats) => ({
reducerPath: apiState?.config?.reducerPath ?? null,
apiState: apiState,
stats: apiStats,
})
);
shouldExpandApiStateNode = (
keyPath: (string | number)[],
value: unknown,
layer: number
): boolean => {
const lastKey = keyPath[keyPath.length - 1];
return layer <= 1 && lastKey !== 'config';
};
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 (
<TreeView
data={this.selectData(this.props)}
isWideLayout={this.props.isWideLayout}
/>
<StyleUtilsContext.Consumer>
{({ styling }) => (
<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_HOVER_COLOR: theme.base0E,
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);
type Color = keyof ReturnType<typeof colorMap>;
@ -400,7 +404,42 @@ const getSheetFromColorMap = (map: ColorMap) => {
treeWrapper: {
overflowX: '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;
export interface QueryTimings {
readonly oldestFetch: { key: string; at: string } | null;
readonly latestFetch: { key: string; at: string } | null;
readonly oldest: { key: string; at: string } | null;
readonly latest: { key: string; at: string } | null;
readonly slowest: { key: string; duration: string } | null;
readonly fastest: { key: string; duration: string } | null;
readonly average: string;
@ -128,9 +128,9 @@ export interface ApiTimings {
export interface ApiStats {
readonly timings: ApiTimings;
readonly tally: Readonly<{
subscriptions: Tally;
subscriptions: number;
queries: QueryTally;
tagTypes: Tally;
tagTypes: number;
mutations: QueryTally;
}>;
}

View File

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