mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-07-27 00:19:55 +03:00
feat(rtk-query): add multiple filter options
This commit is contained in:
parent
825a468ff1
commit
2ce2dc6e5e
|
@ -156,7 +156,6 @@ class RtkQueryInspector<S, A extends Action<unknown>> extends Component<
|
||||||
data-wide-layout={+this.state.isWideLayout}
|
data-wide-layout={+this.state.isWideLayout}
|
||||||
>
|
>
|
||||||
<QueryForm
|
<QueryForm
|
||||||
dispatch={this.props.dispatch}
|
|
||||||
values={selectorsSource.monitorState.queryForm.values}
|
values={selectorsSource.monitorState.queryForm.values}
|
||||||
onFormValuesChange={this.handleQueryFormValuesChange}
|
onFormValuesChange={this.handleQueryFormValuesChange}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -3,12 +3,11 @@ import { QueryFormValues } from '../types';
|
||||||
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
|
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
|
||||||
import { Select } from 'devui';
|
import { Select } from 'devui';
|
||||||
import { SelectOption } from '../types';
|
import { SelectOption } from '../types';
|
||||||
import { AnyAction } from 'redux';
|
|
||||||
import debounce from 'lodash.debounce';
|
import debounce from 'lodash.debounce';
|
||||||
import { sortQueryOptions, QueryComparators } from '../utils/comparators';
|
import { sortQueryOptions, QueryComparators } from '../utils/comparators';
|
||||||
|
import { QueryFilters, filterQueryOptions } from '../utils/filters';
|
||||||
|
|
||||||
export interface QueryFormProps {
|
export interface QueryFormProps {
|
||||||
dispatch: (action: AnyAction) => void;
|
|
||||||
values: QueryFormValues;
|
values: QueryFormValues;
|
||||||
onFormValuesChange: (values: Partial<QueryFormValues>) => void;
|
onFormValuesChange: (values: Partial<QueryFormValues>) => void;
|
||||||
}
|
}
|
||||||
|
@ -21,8 +20,8 @@ const ascId = 'rtk-query-rb-asc';
|
||||||
const descId = 'rtk-query-rb-desc';
|
const descId = 'rtk-query-rb-desc';
|
||||||
const selectId = 'rtk-query-comp-select';
|
const selectId = 'rtk-query-comp-select';
|
||||||
const searchId = 'rtk-query-search-query';
|
const searchId = 'rtk-query-search-query';
|
||||||
|
const filterSelectId = 'rtk-query-search-query-select';
|
||||||
const searchPlaceholder = 'filter query...';
|
const searchPlaceholder = 'filter query by...';
|
||||||
|
|
||||||
export class QueryForm extends React.PureComponent<
|
export class QueryForm extends React.PureComponent<
|
||||||
QueryFormProps,
|
QueryFormProps,
|
||||||
|
@ -57,7 +56,7 @@ export class QueryForm extends React.PureComponent<
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSelectComparator = (
|
handleSelectComparatorChange = (
|
||||||
option: SelectOption<QueryComparators> | undefined | null
|
option: SelectOption<QueryComparators> | undefined | null
|
||||||
): void => {
|
): void => {
|
||||||
if (typeof option?.value === 'string') {
|
if (typeof option?.value === 'string') {
|
||||||
|
@ -65,6 +64,14 @@ export class QueryForm extends React.PureComponent<
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleSelectFilterChange = (
|
||||||
|
option: SelectOption<QueryFilters> | undefined | null
|
||||||
|
): void => {
|
||||||
|
if (typeof option?.value === 'string') {
|
||||||
|
this.props.onFormValuesChange({ queryFilter: option.value });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
restoreCaretPosition = (start: number | null, end: number | null): void => {
|
restoreCaretPosition = (start: number | null, end: number | null): void => {
|
||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
if (this.inputSearchRef.current) {
|
if (this.inputSearchRef.current) {
|
||||||
|
@ -86,9 +93,21 @@ export class QueryForm extends React.PureComponent<
|
||||||
this.invalidateSearchValueFromProps();
|
this.invalidateSearchValueFromProps();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleClearSearchClick = (evt: MouseEvent<HTMLButtonElement>): void => {
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
if (this.state.searchValue) {
|
||||||
|
this.setState({ searchValue: '' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
const {
|
const {
|
||||||
values: { isAscendingQueryComparatorOrder: isAsc, queryComparator },
|
values: {
|
||||||
|
isAscendingQueryComparatorOrder: isAsc,
|
||||||
|
queryComparator,
|
||||||
|
queryFilter,
|
||||||
|
},
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const isDesc = !isAsc;
|
const isDesc = !isAsc;
|
||||||
|
@ -106,13 +125,36 @@ export class QueryForm extends React.PureComponent<
|
||||||
<label htmlFor={searchId} {...styling('srOnly')}>
|
<label htmlFor={searchId} {...styling('srOnly')}>
|
||||||
filter query
|
filter query
|
||||||
</label>
|
</label>
|
||||||
|
<div {...styling('querySearch')}>
|
||||||
<input
|
<input
|
||||||
ref={this.inputSearchRef}
|
ref={this.inputSearchRef}
|
||||||
type="search"
|
type="search"
|
||||||
value={this.state.searchValue}
|
value={this.state.searchValue}
|
||||||
onChange={this.handleSearchChange}
|
onChange={this.handleSearchChange}
|
||||||
placeholder={searchPlaceholder}
|
placeholder={searchPlaceholder}
|
||||||
{...styling('querySearch')}
|
/>
|
||||||
|
<button
|
||||||
|
type="reset"
|
||||||
|
aria-label="clear search"
|
||||||
|
data-invisible={
|
||||||
|
+(this.state.searchValue.length === 0) || undefined
|
||||||
|
}
|
||||||
|
onClick={this.handleClearSearchClick}
|
||||||
|
{...styling('closeButton')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<label htmlFor={selectId} {...styling('srOnly')}>
|
||||||
|
filter by
|
||||||
|
</label>
|
||||||
|
<Select<SelectOption<QueryFilters>>
|
||||||
|
id={filterSelectId}
|
||||||
|
isSearchable={false}
|
||||||
|
options={filterQueryOptions}
|
||||||
|
theme={base16Theme as any}
|
||||||
|
value={filterQueryOptions.find(
|
||||||
|
(opt) => opt?.value === queryFilter
|
||||||
|
)}
|
||||||
|
onChange={this.handleSelectFilterChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div {...styling('sortBySection')}>
|
<div {...styling('sortBySection')}>
|
||||||
|
@ -125,7 +167,7 @@ export class QueryForm extends React.PureComponent<
|
||||||
(opt) => opt?.value === queryComparator
|
(opt) => opt?.value === queryComparator
|
||||||
)}
|
)}
|
||||||
options={sortQueryOptions}
|
options={sortQueryOptions}
|
||||||
onChange={this.handleSelectComparator}
|
onChange={this.handleSelectComparatorChange}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
QueryPreviewTabs,
|
QueryPreviewTabs,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { QueryComparators } from './utils/comparators';
|
import { QueryComparators } from './utils/comparators';
|
||||||
|
import { QueryFilters } from './utils/filters';
|
||||||
|
|
||||||
const initialState: RtkQueryInspectorMonitorState = {
|
const initialState: RtkQueryInspectorMonitorState = {
|
||||||
queryForm: {
|
queryForm: {
|
||||||
|
@ -15,6 +16,7 @@ const initialState: RtkQueryInspectorMonitorState = {
|
||||||
queryComparator: QueryComparators.fulfilledTimeStamp,
|
queryComparator: QueryComparators.fulfilledTimeStamp,
|
||||||
isAscendingQueryComparatorOrder: false,
|
isAscendingQueryComparatorOrder: false,
|
||||||
searchValue: '',
|
searchValue: '',
|
||||||
|
queryFilter: QueryFilters.queryKey,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
selectedPreviewTab: QueryPreviewTabs.queryinfo,
|
selectedPreviewTab: QueryPreviewTabs.queryinfo,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Action, createSelector, Selector } from '@reduxjs/toolkit';
|
||||||
import { RtkQueryInspectorProps } from './RtkQueryInspector';
|
import { RtkQueryInspectorProps } from './RtkQueryInspector';
|
||||||
import { QueryInfo, RtkQueryTag, SelectorsSource } from './types';
|
import { QueryInfo, RtkQueryTag, SelectorsSource } from './types';
|
||||||
import { Comparator, queryComparators } from './utils/comparators';
|
import { Comparator, queryComparators } from './utils/comparators';
|
||||||
|
import { FilterList, queryListFilters } from './utils/filters';
|
||||||
import { escapeRegExpSpecialCharacter } from './utils/regexp';
|
import { escapeRegExpSpecialCharacter } from './utils/regexp';
|
||||||
import {
|
import {
|
||||||
getApiStatesOf,
|
getApiStatesOf,
|
||||||
|
@ -58,6 +59,12 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
||||||
return queryComparators[monitorState.queryForm.values.queryComparator];
|
return queryComparators[monitorState.queryForm.values.queryComparator];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectQueryListFilter = ({
|
||||||
|
monitorState,
|
||||||
|
}: SelectorsSource<S>): FilterList<QueryInfo> => {
|
||||||
|
return queryListFilters[monitorState.queryForm.values.queryFilter];
|
||||||
|
};
|
||||||
|
|
||||||
const selectApiStates = createSelector(
|
const selectApiStates = createSelector(
|
||||||
({ userState }: SelectorsSource<S>) => userState,
|
({ userState }: SelectorsSource<S>) => userState,
|
||||||
getApiStatesOf
|
getApiStatesOf
|
||||||
|
@ -81,21 +88,23 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
||||||
const selectAllVisbileQueries = createSelector(
|
const selectAllVisbileQueries = createSelector(
|
||||||
[
|
[
|
||||||
selectQueryComparator,
|
selectQueryComparator,
|
||||||
|
selectQueryListFilter,
|
||||||
selectAllQueries,
|
selectAllQueries,
|
||||||
({ monitorState }: SelectorsSource<S>) =>
|
({ monitorState }: SelectorsSource<S>) =>
|
||||||
monitorState.queryForm.values.isAscendingQueryComparatorOrder,
|
monitorState.queryForm.values.isAscendingQueryComparatorOrder,
|
||||||
selectSearchQueryRegex,
|
selectSearchQueryRegex,
|
||||||
],
|
],
|
||||||
(comparator, queryList, isAscending, searchRegex) => {
|
(comparator, queryListFilter, queryList, isAscending, searchRegex) => {
|
||||||
const filteredList = searchRegex
|
const filteredList = queryListFilter(
|
||||||
? queryList.filter((queryInfo) => searchRegex.test(queryInfo.queryKey))
|
searchRegex,
|
||||||
: queryList.slice();
|
queryList as QueryInfo[]
|
||||||
|
);
|
||||||
|
|
||||||
const computedComparator = isAscending
|
const computedComparator = isAscending
|
||||||
? comparator
|
? comparator
|
||||||
: flipComparator(comparator);
|
: flipComparator(comparator);
|
||||||
|
|
||||||
return filteredList.sort(computedComparator);
|
return filteredList.slice().sort(computedComparator);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,12 @@ type ColorMap = {
|
||||||
[color in Color]: string;
|
[color in Color]: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSheetFromColorMap = (map: ColorMap) => ({
|
const getSheetFromColorMap = (map: ColorMap) => {
|
||||||
|
const appearanceNone = {
|
||||||
|
'-webkit-appearance': 'none',
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
inspector: {
|
inspector: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexFlow: 'column nowrap',
|
flexFlow: 'column nowrap',
|
||||||
|
@ -128,6 +133,7 @@ const getSheetFromColorMap = (map: ColorMap) => ({
|
||||||
|
|
||||||
queryListHeader: {
|
queryListHeader: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
padding: 4,
|
||||||
flex: '0 0 auto',
|
flex: '0 0 auto',
|
||||||
'align-items': 'center',
|
'align-items': 'center',
|
||||||
'border-bottom-width': '1px',
|
'border-bottom-width': '1px',
|
||||||
|
@ -229,13 +235,24 @@ const getSheetFromColorMap = (map: ColorMap) => ({
|
||||||
paddingRight: '0.4em',
|
paddingRight: '0.4em',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
querySearch: {
|
querySearch: {
|
||||||
|
maxWidth: '65%',
|
||||||
|
'background-color': map.BACKGROUND_COLOR,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexFlow: 'row nowrap',
|
||||||
|
flex: '1 1 auto',
|
||||||
|
|
||||||
|
'& input': {
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
flex: '1 1 auto',
|
||||||
padding: '5px 10px',
|
padding: '5px 10px',
|
||||||
'font-size': '1em',
|
'font-size': '1em',
|
||||||
'font-family': 'monaco, Consolas, "Lucida Console", monospace',
|
position: 'relative',
|
||||||
|
fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
|
||||||
|
|
||||||
'background-color': map.BACKGROUND_COLOR,
|
'background-color': map.BACKGROUND_COLOR,
|
||||||
color: map.TEXT_COLOR,
|
color: map.TEXT_COLOR,
|
||||||
|
@ -247,6 +264,39 @@ const getSheetFromColorMap = (map: ColorMap) => ({
|
||||||
'&::-moz-placeholder': {
|
'&::-moz-placeholder': {
|
||||||
color: map.TEXT_PLACEHOLDER_COLOR,
|
color: map.TEXT_PLACEHOLDER_COLOR,
|
||||||
},
|
},
|
||||||
|
'&::-webkit-search-cancel-button': appearanceNone,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
closeButton: {
|
||||||
|
...appearanceNone,
|
||||||
|
border: 'none',
|
||||||
|
outline: 'none',
|
||||||
|
boxShadow: 'none',
|
||||||
|
display: 'block',
|
||||||
|
cursor: 'pointer',
|
||||||
|
background: 'transparent',
|
||||||
|
position: 'relative',
|
||||||
|
fontSize: 'inherit',
|
||||||
|
'&[data-invisible="1"]': {
|
||||||
|
visibility: 'hidden !important',
|
||||||
|
},
|
||||||
|
'&::after': {
|
||||||
|
content: '"\u00d7"',
|
||||||
|
display: 'block',
|
||||||
|
padding: 4,
|
||||||
|
width: '16px',
|
||||||
|
height: '16px',
|
||||||
|
fontSize: '16px',
|
||||||
|
color: map.TEXT_COLOR,
|
||||||
|
background: 'transparent',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
searchSelectLabel: {
|
||||||
|
display: 'inline-block',
|
||||||
|
padding: 4,
|
||||||
|
borderLeft: `1px solid currentColor`,
|
||||||
},
|
},
|
||||||
|
|
||||||
queryPreview: {
|
queryPreview: {
|
||||||
|
@ -297,7 +347,8 @@ const getSheetFromColorMap = (map: ColorMap) => ({
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
padding: '1em',
|
padding: '1em',
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
};
|
||||||
|
|
||||||
let themeSheet: StyleSheet;
|
let themeSheet: StyleSheet;
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { Base16Theme, StylingFunction } from 'react-base16-styling';
|
||||||
import { Action } from 'redux';
|
import { Action } from 'redux';
|
||||||
import * as themes from 'redux-devtools-themes';
|
import * as themes from 'redux-devtools-themes';
|
||||||
import { QueryComparators } from './utils/comparators';
|
import { QueryComparators } from './utils/comparators';
|
||||||
|
import { QueryFilters } from './utils/filters';
|
||||||
|
|
||||||
export enum QueryPreviewTabs {
|
export enum QueryPreviewTabs {
|
||||||
queryinfo,
|
queryinfo,
|
||||||
|
@ -17,6 +18,7 @@ export interface QueryFormValues {
|
||||||
queryComparator: QueryComparators;
|
queryComparator: QueryComparators;
|
||||||
isAscendingQueryComparatorOrder: boolean;
|
isAscendingQueryComparatorOrder: boolean;
|
||||||
searchValue: string;
|
searchValue: string;
|
||||||
|
queryFilter: QueryFilters;
|
||||||
}
|
}
|
||||||
export interface RtkQueryInspectorMonitorState {
|
export interface RtkQueryInspectorMonitorState {
|
||||||
readonly queryForm: {
|
readonly queryForm: {
|
||||||
|
@ -69,8 +71,6 @@ export interface ExternalProps<S, A extends Action<unknown>> {
|
||||||
invertTheme: boolean;
|
invertTheme: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AnyExternalProps = ExternalProps<unknown, any>;
|
|
||||||
|
|
||||||
export interface QueryInfo {
|
export interface QueryInfo {
|
||||||
query: RtkQueryState;
|
query: RtkQueryState;
|
||||||
queryKey: string;
|
queryKey: string;
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { QueryInfo, SelectOption } from '../types';
|
||||||
|
|
||||||
|
export interface FilterList<T> {
|
||||||
|
(regex: RegExp | null, list: T[]): T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum QueryFilters {
|
||||||
|
queryKey = 'query key',
|
||||||
|
reducerPath = 'reducerPath',
|
||||||
|
endpointName = 'endpoint',
|
||||||
|
status = 'status',
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterByQueryKey(
|
||||||
|
regex: RegExp | null,
|
||||||
|
list: QueryInfo[]
|
||||||
|
): QueryInfo[] {
|
||||||
|
if (!regex) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
return list.filter((queryInfo) => regex.test(queryInfo.queryKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterByReducerPath(
|
||||||
|
regex: RegExp | null,
|
||||||
|
list: QueryInfo[]
|
||||||
|
): QueryInfo[] {
|
||||||
|
if (!regex) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
return list.filter((queryInfo) => regex.test(queryInfo.reducerPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterByEndpointName(
|
||||||
|
regex: RegExp | null,
|
||||||
|
list: QueryInfo[]
|
||||||
|
): QueryInfo[] {
|
||||||
|
if (!regex) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
return list.filter((queryInfo) =>
|
||||||
|
regex.test(queryInfo.query.endpointName ?? 'undefined')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterByStatus(regex: RegExp | null, list: QueryInfo[]): QueryInfo[] {
|
||||||
|
if (!regex) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
return list.filter((queryInfo) => regex.test(queryInfo.query.status));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const filterQueryOptions: SelectOption<QueryFilters>[] = [
|
||||||
|
{ label: 'query key', value: QueryFilters.queryKey },
|
||||||
|
{ label: 'reducerPath', value: QueryFilters.reducerPath },
|
||||||
|
{ label: 'status', value: QueryFilters.status },
|
||||||
|
{ label: 'endpoint', value: QueryFilters.endpointName },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const queryListFilters: Readonly<Record<
|
||||||
|
QueryFilters,
|
||||||
|
FilterList<QueryInfo>
|
||||||
|
>> = {
|
||||||
|
[QueryFilters.queryKey]: filterByQueryKey,
|
||||||
|
[QueryFilters.endpointName]: filterByEndpointName,
|
||||||
|
[QueryFilters.reducerPath]: filterByReducerPath,
|
||||||
|
[QueryFilters.status]: filterByStatus,
|
||||||
|
};
|
|
@ -157,7 +157,7 @@ export function getQueryTagsOf(
|
||||||
const provided = apiStates[queryInfo.reducerPath].provided;
|
const provided = apiStates[queryInfo.reducerPath].provided;
|
||||||
|
|
||||||
const tagTypes = Object.keys(provided);
|
const tagTypes = Object.keys(provided);
|
||||||
console.log({ tagTypes, provided });
|
|
||||||
if (tagTypes.length < 1) {
|
if (tagTypes.length < 1) {
|
||||||
return emptyArray;
|
return emptyArray;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user