mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-07-25 15:40:06 +03:00
refactor(rtk-query): improve UI of api tab
This commit is contained in:
parent
0ae7deb8d6
commit
6a87019cb7
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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%;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}>;
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user