Merge pull request #23472 from backstage/vinzscam/catalog-pagination-text-search-debouncing

Catalog: pagination search save text in query params
This commit is contained in:
Vincenzo Scamporlino
2024-03-26 10:14:15 +01:00
committed by GitHub
9 changed files with 100 additions and 21 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog': patch
---
`CatalogIndexPage` now uses `EntitySearchBar` for text-based filtering of entities, saving the search text in the query parameters and debouncing the server requests.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-react': patch
---
`EntitySearchBar` and `EntityTextFilter` have been updated accordingly to persist the status as query params, following the same pattern as the other server side
+2
View File
@@ -573,6 +573,8 @@ export class EntityTextFilter implements EntityFilter {
fields: string[];
};
// (undocumented)
toQueryValue(): string;
// (undocumented)
readonly value: string;
}
@@ -17,7 +17,6 @@
import React from 'react';
import { fireEvent, render, waitFor, screen } from '@testing-library/react';
import { EntitySearchBar } from './EntitySearchBar';
import { DefaultEntityFilters } from '../../hooks/useEntityListProvider';
import { EntityTextFilter } from '../../filters';
import { MockEntityListContextProvider } from '../../testUtils/providers';
@@ -25,12 +24,15 @@ describe('EntitySearchBar', () => {
it('should display search value and execute set callback', async () => {
const updateFilters = jest.fn();
const filters: DefaultEntityFilters = {
text: new EntityTextFilter('hello'),
};
render(
<MockEntityListContextProvider value={{ updateFilters, filters }}>
<MockEntityListContextProvider
value={{
updateFilters,
queryParameters: {
text: 'hello',
},
}}
>
<EntitySearchBar />
</MockEntityListContextProvider>,
);
@@ -24,8 +24,8 @@ import {
} from '@material-ui/core';
import Clear from '@material-ui/icons/Clear';
import Search from '@material-ui/icons/Search';
import React, { useState } from 'react';
import useDebounce from 'react-use/esm/useDebounce';
import React, { useEffect, useMemo, useState } from 'react';
import useDebounce from 'react-use/lib/useDebounce';
import { useEntityList } from '../../hooks/useEntityListProvider';
import { EntityTextFilter } from '../../filters';
@@ -52,8 +52,17 @@ const useStyles = makeStyles(
export const EntitySearchBar = () => {
const classes = useStyles();
const { filters, updateFilters } = useEntityList();
const [search, setSearch] = useState(filters.text?.value ?? '');
const {
updateFilters,
queryParameters: { text: textParameter },
} = useEntityList();
const queryParamTextFilter = useMemo(
() => [textParameter].flat()[0],
[textParameter],
);
const [search, setSearch] = useState(queryParamTextFilter ?? '');
useDebounce(
() => {
@@ -65,6 +74,12 @@ export const EntitySearchBar = () => {
[search, updateFilters],
);
useEffect(() => {
if (queryParamTextFilter) {
setSearch(queryParamTextFilter);
}
}, [queryParamTextFilter]);
return (
<Toolbar className={classes.searchToolbar}>
<FormControl>
+4
View File
@@ -116,6 +116,10 @@ export class EntityTextFilter implements EntityFilter {
};
}
toQueryValue() {
return this.value;
}
private toUpperArray(
value: Array<string | string[] | undefined>,
): Array<string> {
@@ -192,7 +192,7 @@ export const CatalogTable = (props: CatalogTableProps) => {
columns={tableColumns}
emptyContent={emptyContent}
isLoading={loading}
title={title}
title={titleDisplay}
actions={actions}
subtitle={subtitle}
options={options}
@@ -0,0 +1,52 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { EntitySearchBar } from '@backstage/plugin-catalog-react';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
const useToolbarStyles = makeStyles(
theme => ({
root: {
paddingTop: theme.spacing(1.25),
paddingLeft: theme.spacing(2.5),
paddingBottom: theme.spacing(0.75),
display: 'flex',
justifyContent: 'space-between',
},
text: {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
},
}),
{ name: 'BackstageTableToolbar' },
);
export function CatalogTableToolbar(props: {
title?: string | React.ReactElement<any>;
}) {
const styles = useToolbarStyles();
return (
<Toolbar className={styles.root}>
<Typography variant="h5" className={styles.text}>
{props.title}
</Typography>
<EntitySearchBar />
</Toolbar>
);
}
@@ -18,10 +18,7 @@ import React from 'react';
import { Table, TableProps } from '@backstage/core-components';
import { CatalogTableRow } from './types';
import {
EntityTextFilter,
useEntityList,
} from '@backstage/plugin-catalog-react';
import { CatalogTableToolbar } from './CatalogTableToolbar';
type PaginatedCatalogTableProps = {
prev?(): void;
@@ -33,7 +30,6 @@ type PaginatedCatalogTableProps = {
*/
export function PaginatedCatalogTable(props: PaginatedCatalogTableProps) {
const { columns, data, next, prev, title, isLoading, options } = props;
const { updateFilters } = useEntityList();
return (
<Table
@@ -49,11 +45,6 @@ export function PaginatedCatalogTable(props: PaginatedCatalogTableProps) {
pageSize: Number.MAX_SAFE_INTEGER,
emptyRowsWhenPaging: false,
}}
onSearchChange={(searchText: string) =>
updateFilters({
text: searchText ? new EntityTextFilter(searchText) : undefined,
})
}
onPageChange={page => {
if (page > 0) {
next?.();
@@ -61,6 +52,9 @@ export function PaginatedCatalogTable(props: PaginatedCatalogTableProps) {
prev?.();
}
}}
components={{
Toolbar: CatalogTableToolbar,
}}
/* this will enable the prev button accordingly */
page={prev ? 1 : 0}
/* this will enable the next button accordingly */