mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	chore: Add state filter to UI query; default to open (#10858)
Add an open/closed filter to the global change requests table. To accomodate this, the PR: - refactors the previous `ChangeRequestFilters` into its own directory with additional files for each of the filter groups. - updates the change request filters to work based on the table state instead of its own external state (this was fine with only one param, but would've gotten too complicated with two). <img width="1108" height="442" alt="image" src="https://github.com/user-attachments/assets/9cda0cbc-8524-45ce-b201-260e9581a346" /> <img width="1101" height="381" alt="image" src="https://github.com/user-attachments/assets/ab77b17f-5449-4987-9d08-341e856cb7ac" />
This commit is contained in:
		
							parent
							
								
									fd4fa815a9
								
							
						
					
					
						commit
						1932d21336
					
				| @ -1,105 +0,0 @@ | ||||
| import { Box, Chip, styled, type ChipProps } from '@mui/material'; | ||||
| import { useState, type FC } from 'react'; | ||||
| 
 | ||||
| const makeStyledChip = (ariaControlTarget: string) => | ||||
|     styled(({ ...props }: ChipProps) => ( | ||||
|         <Chip variant='outlined' aria-controls={ariaControlTarget} {...props} /> | ||||
|     ))<ChipProps>(({ theme, label }) => ({ | ||||
|         padding: theme.spacing(0.5), | ||||
|         fontSize: theme.typography.body2.fontSize, | ||||
|         height: 'auto', | ||||
|         '&[aria-current="true"]': { | ||||
|             backgroundColor: theme.palette.secondary.light, | ||||
|             fontWeight: 'bold', | ||||
|             borderColor: theme.palette.primary.main, | ||||
|             color: theme.palette.primary.main, | ||||
|         }, | ||||
|         ':focus-visible': { | ||||
|             outline: `1px solid ${theme.palette.primary.main}`, | ||||
|             borderColor: theme.palette.primary.main, | ||||
|         }, | ||||
| 
 | ||||
|         borderRadius: 0, | ||||
|         '&:first-of-type': { | ||||
|             borderTopLeftRadius: theme.shape.borderRadius, | ||||
|             borderBottomLeftRadius: theme.shape.borderRadius, | ||||
|         }, | ||||
|         '&:last-of-type': { | ||||
|             borderTopRightRadius: theme.shape.borderRadius, | ||||
|             borderBottomRightRadius: theme.shape.borderRadius, | ||||
|         }, | ||||
| 
 | ||||
|         '& .MuiChip-label': { | ||||
|             position: 'relative', | ||||
|             textAlign: 'center', | ||||
|             '&::before': { | ||||
|                 content: `'${label}'`, | ||||
|                 fontWeight: 'bold', | ||||
|                 visibility: 'hidden', | ||||
|                 height: 0, | ||||
|                 display: 'block', | ||||
|                 overflow: 'hidden', | ||||
|                 userSelect: 'none', | ||||
|             }, | ||||
|         }, | ||||
|     })); | ||||
| 
 | ||||
| export type ChangeRequestQuickFilter = 'Created' | 'Approval Requested'; | ||||
| 
 | ||||
| interface IChangeRequestFiltersProps { | ||||
|     ariaControlTarget: string; | ||||
|     initialSelection?: ChangeRequestQuickFilter; | ||||
|     onSelectionChange: (selection: ChangeRequestQuickFilter) => void; | ||||
| } | ||||
| 
 | ||||
| const Wrapper = styled(Box)(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
|     justifyContent: 'flex-start', | ||||
|     padding: theme.spacing(1.5, 3, 0, 3), | ||||
|     minHeight: theme.spacing(7), | ||||
|     gap: theme.spacing(2), | ||||
| })); | ||||
| 
 | ||||
| const StyledContainer = styled(Box)({ | ||||
|     display: 'flex', | ||||
|     alignItems: 'center', | ||||
|     flexWrap: 'wrap', | ||||
| }); | ||||
| 
 | ||||
| export const ChangeRequestFilters: FC<IChangeRequestFiltersProps> = ({ | ||||
|     onSelectionChange, | ||||
|     initialSelection, | ||||
|     ariaControlTarget, | ||||
| }) => { | ||||
|     const [selected, setSelected] = useState<ChangeRequestQuickFilter>( | ||||
|         initialSelection || 'Created', | ||||
|     ); | ||||
|     const handleSelectionChange = (value: ChangeRequestQuickFilter) => () => { | ||||
|         if (value === selected) { | ||||
|             return; | ||||
|         } | ||||
|         setSelected(value); | ||||
|         onSelectionChange(value); | ||||
|     }; | ||||
| 
 | ||||
|     const StyledChip = makeStyledChip(ariaControlTarget); | ||||
| 
 | ||||
|     return ( | ||||
|         <Wrapper> | ||||
|             <StyledContainer> | ||||
|                 <StyledChip | ||||
|                     label={'Created'} | ||||
|                     aria-current={selected === 'Created'} | ||||
|                     onClick={handleSelectionChange('Created')} | ||||
|                     title={'Show change requests created by you'} | ||||
|                 /> | ||||
|                 <StyledChip | ||||
|                     label={'Approval Requested'} | ||||
|                     aria-current={selected === 'Approval Requested'} | ||||
|                     onClick={handleSelectionChange('Approval Requested')} | ||||
|                     title={'Show change requests requesting your approval'} | ||||
|                 /> | ||||
|             </StyledContainer> | ||||
|         </Wrapper> | ||||
|     ); | ||||
| }; | ||||
| @ -0,0 +1,67 @@ | ||||
| import { Box, Chip, styled, type ChipProps } from '@mui/material'; | ||||
| 
 | ||||
| export const makeStyledChip = (ariaControlTarget: string) => | ||||
|     styled(({ ...props }: ChipProps) => ( | ||||
|         <Chip variant='outlined' aria-controls={ariaControlTarget} {...props} /> | ||||
|     ))<ChipProps>(({ theme, label }) => ({ | ||||
|         padding: theme.spacing(0.5), | ||||
|         fontSize: theme.typography.body2.fontSize, | ||||
|         height: 'auto', | ||||
|         '&[aria-current="true"]': { | ||||
|             backgroundColor: theme.palette.secondary.light, | ||||
|             fontWeight: 'bold', | ||||
|             borderColor: theme.palette.primary.main, | ||||
|             color: theme.palette.primary.main, | ||||
|         }, | ||||
|         ':focus-visible': { | ||||
|             outline: `1px solid ${theme.palette.primary.main}`, | ||||
|             borderColor: theme.palette.primary.main, | ||||
|         }, | ||||
| 
 | ||||
|         borderRadius: 0, | ||||
|         '&:first-of-type': { | ||||
|             borderTopLeftRadius: theme.shape.borderRadius, | ||||
|             borderBottomLeftRadius: theme.shape.borderRadius, | ||||
|         }, | ||||
|         '&:last-of-type': { | ||||
|             borderTopRightRadius: theme.shape.borderRadius, | ||||
|             borderBottomRightRadius: theme.shape.borderRadius, | ||||
|         }, | ||||
| 
 | ||||
|         '&:not(&[aria-current="true"])': { | ||||
|             '&:not(&:first-of-type)': { | ||||
|                 borderLeftWidth: 0, | ||||
|             }, | ||||
|             '&:not(&:last-of-type)': { | ||||
|                 borderRightWidth: 0, | ||||
|             }, | ||||
|         }, | ||||
| 
 | ||||
|         '& .MuiChip-label': { | ||||
|             position: 'relative', | ||||
|             textAlign: 'center', | ||||
|             '&::before': { | ||||
|                 content: `'${label}'`, | ||||
|                 fontWeight: 'bold', | ||||
|                 visibility: 'hidden', | ||||
|                 height: 0, | ||||
|                 display: 'block', | ||||
|                 overflow: 'hidden', | ||||
|                 userSelect: 'none', | ||||
|             }, | ||||
|         }, | ||||
|     })); | ||||
| 
 | ||||
| export const Wrapper = styled(Box)(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
|     justifyContent: 'flex-start', | ||||
|     flexFlow: 'row wrap', | ||||
|     padding: theme.spacing(1.5, 3, 0, 3), | ||||
|     minHeight: theme.spacing(7), | ||||
|     gap: theme.spacing(2), | ||||
| })); | ||||
| 
 | ||||
| export const StyledContainer = styled(Box)({ | ||||
|     display: 'flex', | ||||
|     alignItems: 'center', | ||||
| }); | ||||
| @ -0,0 +1,35 @@ | ||||
| import type { FC } from 'react'; | ||||
| import type { TableState } from '../ChangeRequests.types'; | ||||
| import { makeStyledChip, Wrapper } from './ChangeRequestFilters.styles'; | ||||
| import { UserFilterChips } from './UserFilterChips.tsx'; | ||||
| import { StateFilterChips } from './StateFilterChips.tsx'; | ||||
| import type { ChangeRequestFiltersProps } from './ChangeRequestFilters.types.ts'; | ||||
| 
 | ||||
| export const ChangeRequestFilters: FC<ChangeRequestFiltersProps> = ({ | ||||
|     tableState, | ||||
|     setTableState, | ||||
|     userId, | ||||
|     ariaControlTarget, | ||||
| }) => { | ||||
|     const updateTableState = (update: Partial<TableState>) => { | ||||
|         setTableState({ ...tableState, ...update, offset: 0 }); | ||||
|     }; | ||||
| 
 | ||||
|     const StyledChip = makeStyledChip(ariaControlTarget); | ||||
| 
 | ||||
|     return ( | ||||
|         <Wrapper> | ||||
|             <UserFilterChips | ||||
|                 tableState={tableState} | ||||
|                 setTableState={updateTableState} | ||||
|                 userId={userId} | ||||
|                 StyledChip={StyledChip} | ||||
|             /> | ||||
|             <StateFilterChips | ||||
|                 tableState={tableState} | ||||
|                 setTableState={updateTableState} | ||||
|                 StyledChip={StyledChip} | ||||
|             /> | ||||
|         </Wrapper> | ||||
|     ); | ||||
| }; | ||||
| @ -0,0 +1,15 @@ | ||||
| import type { TableState } from '../ChangeRequests.types'; | ||||
| import type { makeStyledChip } from './ChangeRequestFilters.styles'; | ||||
| 
 | ||||
| export type ChangeRequestFiltersProps = { | ||||
|     tableState: Readonly<TableState>; | ||||
|     setTableState: (update: Partial<TableState>) => void; | ||||
|     userId: number; | ||||
|     ariaControlTarget: string; | ||||
| }; | ||||
| 
 | ||||
| export type FilterChipsProps = { | ||||
|     tableState: ChangeRequestFiltersProps['tableState']; | ||||
|     setTableState: ChangeRequestFiltersProps['setTableState']; | ||||
|     StyledChip: ReturnType<typeof makeStyledChip>; | ||||
| }; | ||||
| @ -0,0 +1,48 @@ | ||||
| import type { FC } from 'react'; | ||||
| import { StyledContainer } from './ChangeRequestFilters.styles'; | ||||
| import type { FilterChipsProps } from './ChangeRequestFilters.types'; | ||||
| 
 | ||||
| type StateFilterType = 'open' | 'closed'; | ||||
| 
 | ||||
| const getStateFilter = ( | ||||
|     stateValue: string | undefined, | ||||
| ): StateFilterType | undefined => { | ||||
|     if (stateValue === 'open') { | ||||
|         return 'open'; | ||||
|     } | ||||
|     if (stateValue === 'closed') { | ||||
|         return 'closed'; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| export const StateFilterChips: FC<FilterChipsProps> = ({ | ||||
|     tableState, | ||||
|     setTableState, | ||||
|     StyledChip, | ||||
| }) => { | ||||
|     const activeStateFilter = getStateFilter(tableState.state?.values?.[0]); | ||||
| 
 | ||||
|     const handleStateFilterChange = (filter: StateFilterType) => () => { | ||||
|         if (filter === activeStateFilter) { | ||||
|             return; | ||||
|         } | ||||
|         setTableState({ state: { operator: 'IS' as const, values: [filter] } }); | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <StyledContainer> | ||||
|             <StyledChip | ||||
|                 label={'Open'} | ||||
|                 aria-current={activeStateFilter === 'open'} | ||||
|                 onClick={handleStateFilterChange('open')} | ||||
|                 title={'Show open change requests'} | ||||
|             /> | ||||
|             <StyledChip | ||||
|                 label={'Closed'} | ||||
|                 aria-current={activeStateFilter === 'closed'} | ||||
|                 onClick={handleStateFilterChange('closed')} | ||||
|                 title={'Show closed change requests'} | ||||
|             /> | ||||
|         </StyledContainer> | ||||
|     ); | ||||
| }; | ||||
| @ -0,0 +1,82 @@ | ||||
| import type { FC } from 'react'; | ||||
| import type { TableState } from '../ChangeRequests.types'; | ||||
| import { StyledContainer } from './ChangeRequestFilters.styles'; | ||||
| import type { FilterChipsProps } from './ChangeRequestFilters.types'; | ||||
| 
 | ||||
| type UserFilterType = 'created' | 'approval requested'; | ||||
| 
 | ||||
| type UserFilterChipsProps = FilterChipsProps & { userId: number }; | ||||
| 
 | ||||
| const getUserFilter = ( | ||||
|     tableState: TableState, | ||||
|     userId: string, | ||||
| ): UserFilterType | undefined => { | ||||
|     if ( | ||||
|         !tableState.requestedApproverId && | ||||
|         tableState.createdBy?.values.length === 1 && | ||||
|         tableState.createdBy.values[0] === userId | ||||
|     ) { | ||||
|         return 'created'; | ||||
|     } | ||||
|     if ( | ||||
|         !tableState.createdBy && | ||||
|         tableState.requestedApproverId?.values.length === 1 && | ||||
|         tableState.requestedApproverId.values[0] === userId | ||||
|     ) { | ||||
|         return 'approval requested'; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| export const UserFilterChips: FC<UserFilterChipsProps> = ({ | ||||
|     tableState, | ||||
|     setTableState, | ||||
|     userId, | ||||
|     StyledChip, | ||||
| }) => { | ||||
|     const userIdString = userId.toString(); | ||||
| 
 | ||||
|     const activeUserFilter: UserFilterType | undefined = getUserFilter( | ||||
|         tableState, | ||||
|         userIdString, | ||||
|     ); | ||||
| 
 | ||||
|     const handleUserFilterChange = (filter: UserFilterType) => () => { | ||||
|         if (filter === activeUserFilter) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const [targetProperty, otherProperty] = | ||||
|             filter === 'created' | ||||
|                 ? (['createdBy', 'requestedApproverId'] as const) | ||||
|                 : (['requestedApproverId', 'createdBy'] as const); | ||||
| 
 | ||||
|         setTableState({ | ||||
|             [targetProperty]: { | ||||
|                 operator: 'IS' as const, | ||||
|                 values: [userIdString], | ||||
|             }, | ||||
|             [otherProperty]: | ||||
|                 tableState[otherProperty]?.values.length === 1 && | ||||
|                 tableState[otherProperty]?.values[0] === userIdString | ||||
|                     ? null | ||||
|                     : tableState[otherProperty], | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <StyledContainer> | ||||
|             <StyledChip | ||||
|                 label={'Created'} | ||||
|                 aria-current={activeUserFilter === 'created'} | ||||
|                 onClick={handleUserFilterChange('created')} | ||||
|                 title={'Show change requests created by you'} | ||||
|             /> | ||||
|             <StyledChip | ||||
|                 label={'Approval Requested'} | ||||
|                 aria-current={activeUserFilter === 'approval requested'} | ||||
|                 onClick={handleUserFilterChange('approval requested')} | ||||
|                 title={'Show change requests requesting your approval'} | ||||
|             /> | ||||
|         </StyledContainer> | ||||
|     ); | ||||
| }; | ||||
| @ -14,25 +14,16 @@ import { useUiFlag } from 'hooks/useUiFlag.js'; | ||||
| import { withTableState } from 'utils/withTableState'; | ||||
| import { | ||||
|     useChangeRequestSearch, | ||||
|     DEFAULT_PAGE_LIMIT, | ||||
|     type SearchChangeRequestsInput, | ||||
| } from 'hooks/api/getters/useChangeRequestSearch/useChangeRequestSearch'; | ||||
| import type { ChangeRequestSearchItemSchema } from 'openapi'; | ||||
| import { | ||||
|     NumberParam, | ||||
|     StringParam, | ||||
|     withDefault, | ||||
|     useQueryParams, | ||||
|     encodeQueryParams, | ||||
| } from 'use-query-params'; | ||||
| import { useQueryParams, encodeQueryParams } from 'use-query-params'; | ||||
| import useLoading from 'hooks/useLoading'; | ||||
| import { styles as themeStyles } from 'component/common'; | ||||
| import { FilterItemParam } from 'utils/serializeQueryParams'; | ||||
| import { | ||||
|     ChangeRequestFilters, | ||||
|     type ChangeRequestQuickFilter, | ||||
| } from './ChangeRequestFilters.js'; | ||||
| import { ChangeRequestFilters } from './ChangeRequestFilters/ChangeRequestFilters.js'; | ||||
| import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser.js'; | ||||
| import type { IUser } from 'interfaces/user.js'; | ||||
| import { stateConfig } from './ChangeRequests.types.js'; | ||||
| 
 | ||||
| const columnHelper = createColumnHelper<ChangeRequestSearchItemSchema>(); | ||||
| 
 | ||||
| @ -49,46 +40,33 @@ const StyledPaginatedTable = styled( | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| const ChangeRequestsInner = () => { | ||||
|     const { user } = useAuthUser(); | ||||
| const defaultTableState = (user: IUser) => ({ | ||||
|     createdBy: { | ||||
|         operator: 'IS' as const, | ||||
|         values: [user.id.toString()], | ||||
|     }, | ||||
|     state: { | ||||
|         operator: 'IS' as const, | ||||
|         values: ['open'], | ||||
|     }, | ||||
| }); | ||||
| 
 | ||||
| const ChangeRequestsInner = ({ user }: { user: IUser }) => { | ||||
|     const urlParams = new URLSearchParams(window.location.search); | ||||
|     const shouldApplyDefaults = | ||||
|         user && | ||||
|         !urlParams.has('createdBy') && | ||||
|         !urlParams.has('requestedApproverId'); | ||||
|     const initialFilter = urlParams.has('requestedApproverId') | ||||
|         ? 'Approval Requested' | ||||
|         : 'Created'; | ||||
|     const shouldApplyDefaults = !urlParams.toString(); | ||||
| 
 | ||||
|     const stateConfig = { | ||||
|         offset: withDefault(NumberParam, 0), | ||||
|         limit: withDefault(NumberParam, DEFAULT_PAGE_LIMIT), | ||||
|         sortBy: withDefault(StringParam, 'createdAt'), | ||||
|         sortOrder: withDefault(StringParam, 'desc'), | ||||
|         createdBy: FilterItemParam, | ||||
|         requestedApproverId: FilterItemParam, | ||||
|     }; | ||||
| 
 | ||||
|     const initialState = shouldApplyDefaults | ||||
|         ? { | ||||
|               createdBy: { | ||||
|                   operator: 'IS' as const, | ||||
|                   values: [user.id.toString()], | ||||
|               }, | ||||
|           } | ||||
|         : {}; | ||||
|     const initialState = shouldApplyDefaults ? defaultTableState(user) : {}; | ||||
| 
 | ||||
|     const [tableState, setTableState] = useQueryParams(stateConfig, { | ||||
|         updateType: 'replaceIn', | ||||
|     }); | ||||
| 
 | ||||
|     const effectiveTableState = useMemo( | ||||
|         () => ({ | ||||
|             ...tableState, | ||||
|             ...initialState, | ||||
|         }), | ||||
|         [initialState, tableState], | ||||
|     ); | ||||
|     const effectiveTableState = shouldApplyDefaults | ||||
|         ? { | ||||
|               ...tableState, | ||||
|               ...initialState, | ||||
|           } | ||||
|         : tableState; | ||||
| 
 | ||||
|     const { | ||||
|         changeRequests: data, | ||||
| @ -172,30 +150,6 @@ const ChangeRequestsInner = () => { | ||||
|         }), | ||||
|     ); | ||||
|     const tableId = useId(); | ||||
|     const handleQuickFilterChange = (filter: ChangeRequestQuickFilter) => { | ||||
|         if (!user) { | ||||
|             // todo (globalChangeRequestList): handle this somehow? Or just ignore.
 | ||||
|             return; | ||||
|         } | ||||
|         const [targetProperty, otherProperty] = | ||||
|             filter === 'Created' | ||||
|                 ? ['createdBy', 'requestedApproverId'] | ||||
|                 : ['requestedApproverId', 'createdBy']; | ||||
| 
 | ||||
|         // todo (globalChangeRequestList): extract and test the logic for wiping out createdby/requestedapproverid
 | ||||
|         setTableState((state) => ({ | ||||
|             [targetProperty]: { | ||||
|                 operator: 'IS', | ||||
|                 values: [user.id.toString()], | ||||
|             }, | ||||
|             [otherProperty]: | ||||
|                 state[otherProperty]?.values.length === 1 && | ||||
|                 state[otherProperty].values[0] === user.id.toString() | ||||
|                     ? null | ||||
|                     : state[otherProperty], | ||||
|         })); | ||||
|     }; | ||||
| 
 | ||||
|     const bodyLoadingRef = useLoading(loading); | ||||
| 
 | ||||
|     return ( | ||||
| @ -205,8 +159,9 @@ const ChangeRequestsInner = () => { | ||||
|         > | ||||
|             <ChangeRequestFilters | ||||
|                 ariaControlTarget={tableId} | ||||
|                 initialSelection={initialFilter} | ||||
|                 onSelectionChange={handleQuickFilterChange} | ||||
|                 tableState={effectiveTableState} | ||||
|                 setTableState={setTableState} | ||||
|                 userId={user.id} | ||||
|             /> | ||||
| 
 | ||||
|             <div | ||||
| @ -231,6 +186,7 @@ const ChangeRequestsInner = () => { | ||||
| }; | ||||
| 
 | ||||
| export const ChangeRequests = () => { | ||||
|     const { user } = useAuthUser(); | ||||
|     if (!useUiFlag('globalChangeRequestList')) { | ||||
|         return ( | ||||
|             <PageContent header={<PageHeader title='Change requests' />}> | ||||
| @ -239,5 +195,16 @@ export const ChangeRequests = () => { | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     return <ChangeRequestsInner />; | ||||
|     if (!user) { | ||||
|         return ( | ||||
|             <PageContent header={<PageHeader title='Change requests' />}> | ||||
|                 <p> | ||||
|                     Failed to get your user information. Please refresh. If the | ||||
|                     problem persists, get in touch. | ||||
|                 </p> | ||||
|             </PageContent> | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     return <ChangeRequestsInner user={user} />; | ||||
| }; | ||||
|  | ||||
| @ -0,0 +1,20 @@ | ||||
| import { DEFAULT_PAGE_LIMIT } from 'hooks/api/getters/useChangeRequestSearch/useChangeRequestSearch'; | ||||
| import { | ||||
|     NumberParam, | ||||
|     StringParam, | ||||
|     withDefault, | ||||
|     type DecodedValueMap, | ||||
| } from 'use-query-params'; | ||||
| import { FilterItemParam } from 'utils/serializeQueryParams'; | ||||
| 
 | ||||
| export const stateConfig = { | ||||
|     offset: withDefault(NumberParam, 0), | ||||
|     limit: withDefault(NumberParam, DEFAULT_PAGE_LIMIT), | ||||
|     sortBy: withDefault(StringParam, 'createdAt'), | ||||
|     sortOrder: withDefault(StringParam, 'desc'), | ||||
|     createdBy: FilterItemParam, | ||||
|     requestedApproverId: FilterItemParam, | ||||
|     state: FilterItemParam, | ||||
| }; | ||||
| 
 | ||||
| export type TableState = DecodedValueMap<typeof stateConfig>; | ||||
| @ -1303,6 +1303,7 @@ export * from './searchChangeRequests401.js'; | ||||
| export * from './searchChangeRequests403.js'; | ||||
| export * from './searchChangeRequests404.js'; | ||||
| export * from './searchChangeRequestsParams.js'; | ||||
| export * from './searchChangeRequestsState.js'; | ||||
| export * from './searchEventsParams.js'; | ||||
| export * from './searchFeatures401.js'; | ||||
| export * from './searchFeatures403.js'; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user