fix(rtk-query): layering issue between queryPreview tabList and select

Other changes:

* clean up demo styles

* run prettier
This commit is contained in:
FaberVitale 2021-06-19 17:01:19 +02:00
parent e649318adb
commit 7663ba8191
9 changed files with 134 additions and 136 deletions

View File

@ -27,7 +27,6 @@ npm i @redux-devtools/rtk-query-inspector-monitor --save
yarn add @redux-devtools/rtk-query-inspector-monitor yarn add @redux-devtools/rtk-query-inspector-monitor
``` ```
## Usage ## Usage
You can use `RtkQueryInspectorMonitor` as the only monitor in your app: You can use `RtkQueryInspectorMonitor` as the only monitor in your app:
@ -54,7 +53,6 @@ See also
- [Redux Devtools walkthrough](https://github.com/reduxjs/redux-devtools/tree/master/docs/Walkthrough.md) - [Redux Devtools walkthrough](https://github.com/reduxjs/redux-devtools/tree/master/docs/Walkthrough.md)
## Features ## Features
- sorts queries in ascending or descending order by: - sorts queries in ascending or descending order by:
@ -85,7 +83,6 @@ See also
- [ ] refetch query button(?) - [ ] refetch query button(?)
- ...suggestions are welcome - ...suggestions are welcome
## Redux DevTools props ## Redux DevTools props
| Name | Description | | Name | Description |

View File

@ -17,6 +17,7 @@ yarn
```bash ```bash
yarn exec --cwd 'packages/redux-devtools-rtk-query-inspector-monitor/demo' yarn yarn exec --cwd 'packages/redux-devtools-rtk-query-inspector-monitor/demo' yarn
``` ```
### 3. Start demo ### 3. Start demo
```bash ```bash

View File

@ -7,116 +7,116 @@
/* eslint-disable */ /* eslint-disable */
/* tslint:disable */ /* tslint:disable */
const INTEGRITY_CHECKSUM = '82ef9b96d8393b6da34527d1d6e19187' const INTEGRITY_CHECKSUM = '82ef9b96d8393b6da34527d1d6e19187';
const bypassHeaderName = 'x-msw-bypass' const bypassHeaderName = 'x-msw-bypass';
const activeClientIds = new Set() const activeClientIds = new Set();
self.addEventListener('install', function () { self.addEventListener('install', function () {
return self.skipWaiting() return self.skipWaiting();
}) });
self.addEventListener('activate', async function (event) { self.addEventListener('activate', async function (event) {
return self.clients.claim() return self.clients.claim();
}) });
self.addEventListener('message', async function (event) { self.addEventListener('message', async function (event) {
const clientId = event.source.id const clientId = event.source.id;
if (!clientId || !self.clients) { if (!clientId || !self.clients) {
return return;
} }
const client = await self.clients.get(clientId) const client = await self.clients.get(clientId);
if (!client) { if (!client) {
return return;
} }
const allClients = await self.clients.matchAll() const allClients = await self.clients.matchAll();
switch (event.data) { switch (event.data) {
case 'KEEPALIVE_REQUEST': { case 'KEEPALIVE_REQUEST': {
sendToClient(client, { sendToClient(client, {
type: 'KEEPALIVE_RESPONSE', type: 'KEEPALIVE_RESPONSE',
}) });
break break;
} }
case 'INTEGRITY_CHECK_REQUEST': { case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, { sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE', type: 'INTEGRITY_CHECK_RESPONSE',
payload: INTEGRITY_CHECKSUM, payload: INTEGRITY_CHECKSUM,
}) });
break break;
} }
case 'MOCK_ACTIVATE': { case 'MOCK_ACTIVATE': {
activeClientIds.add(clientId) activeClientIds.add(clientId);
sendToClient(client, { sendToClient(client, {
type: 'MOCKING_ENABLED', type: 'MOCKING_ENABLED',
payload: true, payload: true,
}) });
break break;
} }
case 'MOCK_DEACTIVATE': { case 'MOCK_DEACTIVATE': {
activeClientIds.delete(clientId) activeClientIds.delete(clientId);
break break;
} }
case 'CLIENT_CLOSED': { case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId) activeClientIds.delete(clientId);
const remainingClients = allClients.filter((client) => { const remainingClients = allClients.filter((client) => {
return client.id !== clientId return client.id !== clientId;
}) });
// Unregister itself when there are no more clients // Unregister itself when there are no more clients
if (remainingClients.length === 0) { if (remainingClients.length === 0) {
self.registration.unregister() self.registration.unregister();
} }
break break;
} }
} }
}) });
// Resolve the "master" client for the given event. // Resolve the "master" client for the given event.
// Client that issues a request doesn't necessarily equal the client // Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should // that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase. // communicate with during the response resolving phase.
async function resolveMasterClient(event) { async function resolveMasterClient(event) {
const client = await self.clients.get(event.clientId) const client = await self.clients.get(event.clientId);
if (client.frameType === 'top-level') { if (client.frameType === 'top-level') {
return client return client;
} }
const allClients = await self.clients.matchAll() const allClients = await self.clients.matchAll();
return allClients return allClients
.filter((client) => { .filter((client) => {
// Get only those clients that are currently visible. // Get only those clients that are currently visible.
return client.visibilityState === 'visible' return client.visibilityState === 'visible';
}) })
.find((client) => { .find((client) => {
// Find the client ID that's recorded in the // Find the client ID that's recorded in the
// set of clients that have registered the worker. // set of clients that have registered the worker.
return activeClientIds.has(client.id) return activeClientIds.has(client.id);
}) });
} }
async function handleRequest(event, requestId) { async function handleRequest(event, requestId) {
const client = await resolveMasterClient(event) const client = await resolveMasterClient(event);
const response = await getResponse(event, client, requestId) const response = await getResponse(event, client, requestId);
// Send back the response clone for the "response:*" life-cycle events. // Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise // Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely. // this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) { if (client && activeClientIds.has(client.id)) {
;(async function () { (async function () {
const clonedResponse = response.clone() const clonedResponse = response.clone();
sendToClient(client, { sendToClient(client, {
type: 'RESPONSE', type: 'RESPONSE',
payload: { payload: {
@ -130,21 +130,21 @@ async function handleRequest(event, requestId) {
headers: serializeHeaders(clonedResponse.headers), headers: serializeHeaders(clonedResponse.headers),
redirected: clonedResponse.redirected, redirected: clonedResponse.redirected,
}, },
}) });
})() })();
} }
return response return response;
} }
async function getResponse(event, client, requestId) { async function getResponse(event, client, requestId) {
const { request } = event const { request } = event;
const requestClone = request.clone() const requestClone = request.clone();
const getOriginalResponse = () => fetch(requestClone) const getOriginalResponse = () => fetch(requestClone);
// Bypass mocking when the request client is not active. // Bypass mocking when the request client is not active.
if (!client) { if (!client) {
return getOriginalResponse() return getOriginalResponse();
} }
// Bypass initial page load requests (i.e. static assets). // Bypass initial page load requests (i.e. static assets).
@ -152,26 +152,26 @@ async function getResponse(event, client, requestId) {
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests. // and is not ready to handle requests.
if (!activeClientIds.has(client.id)) { if (!activeClientIds.has(client.id)) {
return await getOriginalResponse() return await getOriginalResponse();
} }
// Bypass requests with the explicit bypass header // Bypass requests with the explicit bypass header
if (requestClone.headers.get(bypassHeaderName) === 'true') { if (requestClone.headers.get(bypassHeaderName) === 'true') {
const cleanRequestHeaders = serializeHeaders(requestClone.headers) const cleanRequestHeaders = serializeHeaders(requestClone.headers);
// Remove the bypass header to comply with the CORS preflight check. // Remove the bypass header to comply with the CORS preflight check.
delete cleanRequestHeaders[bypassHeaderName] delete cleanRequestHeaders[bypassHeaderName];
const originalRequest = new Request(requestClone, { const originalRequest = new Request(requestClone, {
headers: new Headers(cleanRequestHeaders), headers: new Headers(cleanRequestHeaders),
}) });
return fetch(originalRequest) return fetch(originalRequest);
} }
// Send the request to the client-side MSW. // Send the request to the client-side MSW.
const reqHeaders = serializeHeaders(request.headers) const reqHeaders = serializeHeaders(request.headers);
const body = await request.text() const body = await request.text();
const clientMessage = await sendToClient(client, { const clientMessage = await sendToClient(client, {
type: 'REQUEST', type: 'REQUEST',
@ -192,31 +192,31 @@ async function getResponse(event, client, requestId) {
bodyUsed: request.bodyUsed, bodyUsed: request.bodyUsed,
keepalive: request.keepalive, keepalive: request.keepalive,
}, },
}) });
switch (clientMessage.type) { switch (clientMessage.type) {
case 'MOCK_SUCCESS': { case 'MOCK_SUCCESS': {
return delayPromise( return delayPromise(
() => respondWithMock(clientMessage), () => respondWithMock(clientMessage),
clientMessage.payload.delay, clientMessage.payload.delay
) );
} }
case 'MOCK_NOT_FOUND': { case 'MOCK_NOT_FOUND': {
return getOriginalResponse() return getOriginalResponse();
} }
case 'NETWORK_ERROR': { case 'NETWORK_ERROR': {
const { name, message } = clientMessage.payload const { name, message } = clientMessage.payload;
const networkError = new Error(message) const networkError = new Error(message);
networkError.name = name networkError.name = name;
// Rejecting a request Promise emulates a network error. // Rejecting a request Promise emulates a network error.
throw networkError throw networkError;
} }
case 'INTERNAL_ERROR': { case 'INTERNAL_ERROR': {
const parsedBody = JSON.parse(clientMessage.payload.body) const parsedBody = JSON.parse(clientMessage.payload.body);
console.error( console.error(
`\ `\
@ -229,38 +229,38 @@ This exception has been gracefully handled as a 500 response, however, it's stro
If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses\ If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses\
`, `,
request.method, request.method,
request.url, request.url
) );
return respondWithMock(clientMessage) return respondWithMock(clientMessage);
} }
} }
return getOriginalResponse() return getOriginalResponse();
} }
self.addEventListener('fetch', function (event) { self.addEventListener('fetch', function (event) {
const { request } = event const { request } = event;
// Bypass navigation requests. // Bypass navigation requests.
if (request.mode === 'navigate') { if (request.mode === 'navigate') {
return return;
} }
// Opening the DevTools triggers the "only-if-cached" request // Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests. // that cannot be handled by the worker. Bypass such requests.
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
return return;
} }
// Bypass all requests when there are no active clients. // Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests // Prevents the self-unregistered worked from handling requests
// after it's been deleted (still remains active until the next reload). // after it's been deleted (still remains active until the next reload).
if (activeClientIds.size === 0) { if (activeClientIds.size === 0) {
return return;
} }
const requestId = uuidv4() const requestId = uuidv4();
return event.respondWith( return event.respondWith(
handleRequest(event, requestId).catch((error) => { handleRequest(event, requestId).catch((error) => {
@ -268,55 +268,55 @@ self.addEventListener('fetch', function (event) {
'[MSW] Failed to mock a "%s" request to "%s": %s', '[MSW] Failed to mock a "%s" request to "%s": %s',
request.method, request.method,
request.url, request.url,
error, error
) );
}),
)
}) })
);
});
function serializeHeaders(headers) { function serializeHeaders(headers) {
const reqHeaders = {} const reqHeaders = {};
headers.forEach((value, name) => { headers.forEach((value, name) => {
reqHeaders[name] = reqHeaders[name] reqHeaders[name] = reqHeaders[name]
? [].concat(reqHeaders[name]).concat(value) ? [].concat(reqHeaders[name]).concat(value)
: value : value;
}) });
return reqHeaders return reqHeaders;
} }
function sendToClient(client, message) { function sendToClient(client, message) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const channel = new MessageChannel() const channel = new MessageChannel();
channel.port1.onmessage = (event) => { channel.port1.onmessage = (event) => {
if (event.data && event.data.error) { if (event.data && event.data.error) {
return reject(event.data.error) return reject(event.data.error);
} }
resolve(event.data) resolve(event.data);
} };
client.postMessage(JSON.stringify(message), [channel.port2]) client.postMessage(JSON.stringify(message), [channel.port2]);
}) });
} }
function delayPromise(cb, duration) { function delayPromise(cb, duration) {
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(() => resolve(cb()), duration) setTimeout(() => resolve(cb()), duration);
}) });
} }
function respondWithMock(clientMessage) { function respondWithMock(clientMessage) {
return new Response(clientMessage.payload.body, { return new Response(clientMessage.payload.body, {
...clientMessage.payload, ...clientMessage.payload,
headers: clientMessage.payload.headers, headers: clientMessage.payload.headers,
}) });
} }
function uuidv4() { function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = (Math.random() * 16) | 0 const r = (Math.random() * 16) | 0;
const v = c == 'x' ? r : (r & 0x3) | 0x8 const v = c == 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16) return v.toString(16);
}) });
} }

View File

@ -1,6 +1,6 @@
import PokemonView from 'features/pokemon/PokemonView'; import PokemonView from 'features/pokemon/PokemonView';
import PostsView from 'features/posts/PostsView'; import PostsView from 'features/posts/PostsView';
import { Flex, Heading } from '@chakra-ui/react'; import { Box, Flex, Heading } from '@chakra-ui/react';
import { Link, UnorderedList, ListItem } from '@chakra-ui/react'; import { Link, UnorderedList, ListItem } from '@chakra-ui/react';
import { Code } from '@chakra-ui/react'; import { Code } from '@chakra-ui/react';
import * as React from 'react'; import * as React from 'react';
@ -14,14 +14,17 @@ export function App() {
<PostsView /> <PostsView />
<Flex p="2" as="section" flexWrap="nowrap" flexDirection="column"> <Flex p="2" as="section" flexWrap="nowrap" flexDirection="column">
<Heading as="h2">Dock controls</Heading> <Heading as="h2">Dock controls</Heading>
<pre> <Box as="pre" p="2" paddingX="4">
<Code> <Code>
{`toggleVisibilityKey="ctrl-h"\nchangePositionKey="ctrl-q"`} {`toggleVisibilityKey="ctrl-h"\nchangePositionKey="ctrl-q"`}
</Code> </Code>
</pre> </Box>
<Box as="p" p="2" paddingX="4">
Drag its border to resize
</Box>
</Flex> </Flex>
<Flex p="2" as="footer"> <Flex p="2" as="footer">
<UnorderedList> <UnorderedList p="2">
<ListItem> <ListItem>
<Link <Link
className="link" className="link"

View File

@ -12,7 +12,7 @@ export default function PokemonView() {
return ( return (
<Flex p="2" as="section" flexWrap="nowrap" flexDirection="column"> <Flex p="2" as="section" flexWrap="nowrap" flexDirection="column">
<Heading as="h2">Pokemon polling demo</Heading> <Heading as="h2">Pokemon polling demo</Heading>
<div className="demo-toolbar"> <Flex p="2" gridGap="0.5em" flexDirection="row" flexWrap="wrap">
<Button <Button
onClick={() => onClick={() =>
setPokemon((prev) => [...prev, getRandomPokemonName()]) setPokemon((prev) => [...prev, getRandomPokemonName()])
@ -23,7 +23,7 @@ export default function PokemonView() {
<Button onClick={() => setPokemon((prev) => [...prev, 'bulbasaur'])}> <Button onClick={() => setPokemon((prev) => [...prev, 'bulbasaur'])}>
Add bulbasaur Add bulbasaur
</Button> </Button>
</div> </Flex>
<div className="pokemon-list"> <div className="pokemon-list">
{pokemon.map((name, index) => ( {pokemon.map((name, index) => (

View File

@ -33,12 +33,6 @@ h6 {
margin: 0; margin: 0;
} }
.demo-toolbar {
display: flex;
padding: 1em;
justify-content: flex-start;
}
section { section {
display: block; display: block;
max-width: 67vw; max-width: 67vw;

View File

@ -1,4 +1,4 @@
import React, { PureComponent, ReactNode, MouseEvent } from 'react'; import React, { PureComponent, ReactNode } from 'react';
import { StyleUtilsContext } from '../styles/createStylingFromTheme'; import { StyleUtilsContext } from '../styles/createStylingFromTheme';
import { QueryInfo, RtkQueryInspectorMonitorState } from '../types'; import { QueryInfo, RtkQueryInspectorMonitorState } from '../types';
import { isQuerySelected } from '../utils/rtk-query'; import { isQuerySelected } from '../utils/rtk-query';

View File

@ -50,15 +50,18 @@ export class QueryPreview extends React.PureComponent<QueryPreviewProps> {
this.labelRenderer = createTreeItemLabelRenderer(this.props.styling); this.labelRenderer = createTreeItemLabelRenderer(this.props.styling);
} }
renderLabelWithCounter = (label: React.ReactText, counter: number): string => { renderLabelWithCounter = (
label: React.ReactText,
counter: number
): string => {
let counterAsString = counter.toFixed(0); let counterAsString = counter.toFixed(0);
if (counterAsString.length > 3) { if (counterAsString.length > 3) {
counterAsString = counterAsString.slice(0, 2) + '...'; counterAsString = counterAsString.slice(0, 2) + '...';
} }
return `${label} (${counterAsString})` return `${label} (${counterAsString})`;
} };
renderTabLabel = (tab: QueryPreviewTabOption): ReactNode => { renderTabLabel = (tab: QueryPreviewTabOption): ReactNode => {
const { queryInfo, tags, querySubscriptions } = this.props; const { queryInfo, tags, querySubscriptions } = this.props;
@ -67,7 +70,10 @@ export class QueryPreview extends React.PureComponent<QueryPreviewProps> {
return this.renderLabelWithCounter(tab.label, tags.length); return this.renderLabelWithCounter(tab.label, tags.length);
} }
if(tab.value === QueryPreviewTabs.querySubscriptions && querySubscriptions) { if (
tab.value === QueryPreviewTabs.querySubscriptions &&
querySubscriptions
) {
const subsCount = Object.keys(querySubscriptions).length; const subsCount = Object.keys(querySubscriptions).length;
if (subsCount > 0) { if (subsCount > 0) {
@ -77,8 +83,7 @@ export class QueryPreview extends React.PureComponent<QueryPreviewProps> {
} }
return tab.label; return tab.label;
};
}
render(): ReactNode { render(): ReactNode {
const { const {

View File

@ -161,8 +161,6 @@ const getSheetFromColorMap = (map: ColorMap) => {
}, },
tabSelector: { tabSelector: {
position: 'relative',
'z-index': 1,
display: 'inline-flex', display: 'inline-flex',
float: 'right', float: 'right',
}, },
@ -296,7 +294,7 @@ const getSheetFromColorMap = (map: ColorMap) => {
searchSelectLabel: { searchSelectLabel: {
display: 'inline-block', display: 'inline-block',
padding: 4, padding: 4,
borderLeft: `1px solid currentColor`, borderLeft: '1px solid currentColor',
}, },
queryPreview: { queryPreview: {