Move type-filtering logic to a hook
Signed-off-by: Tim Hansen <timbonicus@gmail.com>
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user