support non 'filter'-keyed filters in paginated backend requests

Signed-off-by: Aramis <sennyeyaramis@gmail.com>
This commit is contained in:
Aramis
2024-01-26 11:46:38 -05:00
parent 267dc8e8db
commit 03604cffa2
9 changed files with 72 additions and 30 deletions
+10
View File
@@ -15,6 +15,8 @@ import { Entity } from '@backstage/catalog-model';
import { IconButton } from '@material-ui/core';
import { IconComponent } from '@backstage/core-plugin-api';
import { InfoCardVariants } from '@backstage/core-components';
import { JsonObject } from '@backstage/types';
import { JsonValue } from '@backstage/types';
import { LinkProps } from '@backstage/core-components';
import { Observable } from '@backstage/types';
import { Overrides } from '@material-ui/core/styles/overrides';
@@ -217,6 +219,7 @@ export type EntityFilter = {
string,
string | symbol | (string | symbol)[]
>;
getBackendRequestParameter?: () => Record<string, JsonObject | JsonValue>;
filterEntity?: (entity: Entity) => boolean;
toQueryValue?: () => string | string[];
};
@@ -541,6 +544,13 @@ export class EntityTextFilter implements EntityFilter {
// (undocumented)
filterEntity(entity: Entity): boolean;
// (undocumented)
getBackendRequestParameter(): {
fullTextFilter: {
term: string;
fields: string[];
};
};
// (undocumented)
readonly value: string;
}
@@ -30,13 +30,13 @@ export function useAllEntitiesCount() {
const request = useMemo(() => {
const { user, ...allFilters } = filters;
const compacted = compact(Object.values(allFilters));
const filter = reduceCatalogFilters(compacted);
const catalogFilters = reduceCatalogFilters(compacted);
const newRequest: QueryEntitiesInitialRequest = {
filter,
...catalogFilters,
limit: 0,
};
if (Object.keys(filter).length === 0) {
if (Object.keys(catalogFilters).length === 0) {
prevRequest.current = undefined;
return prevRequest.current;
}
@@ -21,7 +21,7 @@ import useAsync from 'react-use/lib/useAsync';
import { catalogApiRef } from '../../api';
import { EntityOwnerFilter, EntityUserFilter } from '../../filters';
import { useEntityList } from '../../hooks';
import { reduceCatalogFilters } from '../../utils';
import { CatalogFilters, reduceCatalogFilters } from '../../utils';
import useAsyncFn from 'react-use/lib/useAsyncFn';
import useDeepCompareEffect from 'react-use/lib/useDeepCompareEffect';
@@ -38,7 +38,7 @@ export function useOwnedEntitiesCount() {
);
const { user, owners, ...allFilters } = filters;
const { ['metadata.name']: metadata, ...filter } = reduceCatalogFilters(
const catalogFilters = reduceCatalogFilters(
compact(Object.values(allFilters)),
);
@@ -47,7 +47,7 @@ export function useOwnedEntitiesCount() {
async (req: {
ownershipEntityRefs: string[];
owners: EntityOwnerFilter | undefined;
filter: Record<string, string | symbol | (string | symbol)[]>;
filter: CatalogFilters;
}) => {
const ownedClaims = getOwnedCountClaims(
req.owners,
@@ -60,9 +60,12 @@ export function useOwnedEntitiesCount() {
return 0;
}
const { ['metadata.name']: metadata, ...filter } = req.filter.filter;
const { totalItems } = await catalogApi.queryEntities({
...req.filter,
filter: {
...req.filter,
...filter,
'relations.ownedBy': ownedClaims,
},
limit: 0,
@@ -75,15 +78,19 @@ export function useOwnedEntitiesCount() {
useDeepCompareEffect(() => {
// context contains no filter, wait
if (Object.keys(filter).length === 0) {
if (Object.keys(catalogFilters).length === 0) {
return;
}
// ownershipEntityRefs is loading, wait
if (ownershipEntityRefs === undefined) {
return;
}
fetchEntities({ ownershipEntityRefs, owners, filter });
}, [ownershipEntityRefs, owners, filter]);
fetchEntities({
ownershipEntityRefs,
owners,
filter: catalogFilters,
});
}, [ownershipEntityRefs, owners, catalogFilters]);
const loading = loadingEntityRefs || loadingEntityOwnership;
@@ -34,13 +34,14 @@ export function useStarredEntitiesCount() {
const request = useMemo(() => {
const { user, ...allFilters } = filters;
const compacted = compact(Object.values(allFilters));
const filter = reduceCatalogFilters(compacted);
const catalogFilters = reduceCatalogFilters(compacted);
const facet = 'metadata.name';
const newRequest: QueryEntitiesInitialRequest = {
...catalogFilters,
filter: {
...filter,
...catalogFilters.filter,
/**
* here we are filtering entities by `name`. Given this filter,
* the response might contain more entities than expected, in case multiple entities
+10
View File
@@ -108,6 +108,16 @@ export class EntityTextFilter implements EntityFilter {
return true;
}
getBackendRequestParameter() {
return {
fullTextFilter: {
term: this.value,
// Update this to be more dynamic based on table columns.
fields: ['metadata.name', 'metadata.title'],
},
};
}
private toUpperArray(
value: Array<string | string[] | undefined>,
): Array<string> {
@@ -338,6 +338,7 @@ describe('<EntityListProvider pagination />', () => {
orderFields,
fullTextFilter: {
term: '2',
fields: ['metadata.name', 'metadata.title'],
},
});
});
@@ -233,17 +233,9 @@ export const EntityListProvider = <EntityFilters extends DefaultEntityFilters>(
compact(Object.values(outputState.appliedFilters)),
);
if (
!isEqual(previousBackendFilter, backendFilter) ||
requestedFilters.text?.value !== outputState.textSearch
) {
if (!isEqual(previousBackendFilter, backendFilter)) {
const response = await catalogApi.queryEntities({
filter: backendFilter,
fullTextFilter: requestedFilters.text
? {
term: requestedFilters.text.value,
}
: undefined,
...backendFilter,
limit,
orderFields: [{ field: 'metadata.name', order: 'asc' }],
});
+8
View File
@@ -15,6 +15,7 @@
*/
import { Entity } from '@backstage/catalog-model';
import { JsonObject, JsonValue } from '@backstage/types';
/** @public */
export type EntityFilter = {
@@ -29,6 +30,13 @@ export type EntityFilter = {
string | symbol | (string | symbol)[]
>;
/**
* A superset of `getCatalogFilters` that supports filtering based on parameters outside of the
* catalog filter system. Examples include `fullTextFilter` or `orderFields`.
* @returns
*/
getBackendRequestParameter?: () => Record<string, JsonObject | JsonValue>;
/**
* Filter entities on the frontend after a catalog-backend request. This function will be called
* with each backend-resolved entity. This is used when frontend information is required for
+21 -8
View File
@@ -26,16 +26,29 @@ import {
EntityUserFilter,
UserListFilter,
} from '../filters';
import { merge } from 'lodash';
export function reduceCatalogFilters(
filters: EntityFilter[],
): Record<string, string | symbol | (string | symbol)[]> {
export interface CatalogFilters {
[x: string]:
| string
| Record<string, string | symbol | (string | symbol)[]>
| undefined;
filter: Record<string, string | symbol | (string | symbol)[]>;
fullTextFilter?: {
term: string;
};
}
export function reduceCatalogFilters(filters: EntityFilter[]): CatalogFilters {
return filters.reduce((compoundFilter, filter) => {
return {
...compoundFilter,
...(filter.getCatalogFilters ? filter.getCatalogFilters() : {}),
};
}, {} as Record<string, string | symbol | (string | symbol)[]>);
const addedFilters = {};
if (filter.getBackendRequestParameter) {
Object.assign(addedFilters, filter.getBackendRequestParameter());
} else if (filter.getCatalogFilters) {
Object.assign(addedFilters, { filter: filter.getCatalogFilters() });
}
return merge(compoundFilter, addedFilters);
}, {} as CatalogFilters);
}
/**