Move type-filtering logic to a hook

Signed-off-by: Tim Hansen <timbonicus@gmail.com>
This commit is contained in:
Tim Hansen
2021-05-12 14:18:48 -06:00
parent 5e0ae3428f
commit 5666efa9da
3 changed files with 105 additions and 55 deletions
+1
View File
@@ -21,6 +21,7 @@ export {
useEntityListProvider,
} from './useEntityListProvider';
export type { DefaultEntityFilters } from './useEntityListProvider';
export { useEntityTypeFilter } from './useEntityTypeFilter';
export { useOwnUser } from './useOwnUser';
export { useRelatedEntities } from './useRelatedEntities';
export { useStarredEntities } from './useStarredEntities';
@@ -0,0 +1,97 @@
/*
* Copyright 2021 Spotify AB
*
* 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 { useCallback, useEffect, useMemo, useState } from 'react';
import { useAsync } from 'react-use';
import { useApi } from '@backstage/core';
import { catalogApiRef } from '../api';
import {
DefaultEntityFilters,
useEntityListProvider,
} from './useEntityListProvider';
import { EntityTypeFilter } from '../types';
type EntityTypeReturn = {
loading: boolean;
error?: Error;
types: string[];
selectedType: string | undefined;
setType: (type: string | undefined) => void;
};
/**
* A hook built on top of `useEntityListProvider` for enabling selection of valid `spec.type` values
* based on the selected EntityKindFilter.
*/
export function useEntityTypeFilter(): EntityTypeReturn {
const catalogApi = useApi(catalogApiRef);
const {
filters: { kind: kindFilter, type: typeFilter },
updateFilters,
} = useEntityListProvider();
const [types, setTypes] = useState<string[]>([]);
const kind = useMemo(() => kindFilter?.value, [kindFilter]);
// Load all valid spec.type values straight from the catalogApi, paying attention to only the
// kind filter for a complete list.
const { error, loading, value: entities } = useAsync(async () => {
if (kind) {
const items = await catalogApi
.getEntities({
filter: { kind },
fields: ['spec.type'],
})
.then(response => response.items);
return items;
}
return [];
}, [kind, catalogApi]);
useEffect(() => {
// Resolve the unique set of types from returned entities; could be optimized by a new endpoint
// in the catalog-backend that does this, rather than loading entities with redundant types.
const newTypes = [
...new Set(
(entities ?? []).map(e => e.spec?.type).filter(Boolean) as string[],
),
].sort();
setTypes(newTypes);
// Reset type filter if no longer applicable
updateFilters((oldFilters: DefaultEntityFilters) =>
oldFilters.type && !newTypes.includes(oldFilters.type.value)
? { type: undefined }
: {},
);
}, [updateFilters, entities]);
const setType = useCallback(
(type: string | undefined) =>
updateFilters({
type: type === undefined ? undefined : new EntityTypeFilter(type),
}),
[updateFilters],
);
return {
loading,
error,
types,
selectedType: typeFilter?.value,
setType,
};
}
@@ -14,65 +14,21 @@
* limitations under the License.
*/
import React, { useEffect, useMemo, useState } from 'react';
import React from 'react';
import { capitalize } from 'lodash';
import { useAsync } from 'react-use';
import { Box } from '@material-ui/core';
import { alertApiRef, Select, useApi } from '@backstage/core';
import {
catalogApiRef,
DefaultEntityFilters,
EntityTypeFilter,
useEntityListProvider,
} from '@backstage/plugin-catalog-react';
import { useEntityTypeFilter } from '@backstage/plugin-catalog-react';
export const EntityTypePicker = () => {
const catalogApi = useApi(catalogApiRef);
const alertApi = useApi(alertApiRef);
const {
filters: { kind: kindFilter, type: typeFilter },
updateFilters,
} = useEntityListProvider();
const [types, setTypes] = useState<string[]>([]);
const kind = useMemo(() => kindFilter?.value, [kindFilter]);
// Load all valid spec.type values straight from the catalogApi - we want the full set for the
// selected kinds, not an otherwise filtered set.
const { error, value: entities } = useAsync(async () => {
if (kind) {
const items = await catalogApi
.getEntities({
filter: { kind },
fields: ['spec.type'],
})
.then(response => response.items);
return items;
}
return [];
}, [kind, catalogApi]);
useEffect(() => {
const newTypes = [
...new Set(
(entities ?? []).map(e => e.spec?.type).filter(Boolean) as string[],
),
].sort();
setTypes(newTypes);
updateFilters((oldFilters: DefaultEntityFilters) =>
oldFilters.type && !newTypes.includes(oldFilters.type.value)
? { type: undefined }
: {},
);
}, [updateFilters, entities]);
const { error, types, selectedType, setType } = useEntityTypeFilter();
if (!types) return null;
if (error) {
alertApi.post({
message: `Failed to load types for ${kind}`,
message: `Failed to load entity types`,
severity: 'error',
});
return null;
@@ -80,23 +36,19 @@ export const EntityTypePicker = () => {
const items = [
{ value: 'all', label: 'All' },
...types.map(type => ({
...types.map((type: string) => ({
value: type,
label: capitalize(type),
})),
];
const onChange = (value: any) => {
updateFilters({ type: new EntityTypeFilter(value) });
};
return (
<Box pb={1} pt={1}>
<Select
label="Type"
items={items}
selected={typeFilter?.value ?? 'all'}
onChange={onChange}
selected={selectedType ?? 'all'}
onChange={value => setType(value === 'all' ? undefined : String(value))}
/>
</Box>
);