diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx
index 8b223c50b1..f894368a4a 100644
--- a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx
+++ b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx
@@ -14,7 +14,11 @@
* limitations under the License.
*/
-import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
+import {
+ Entity,
+ parseEntityRef,
+ stringifyEntityRef,
+} from '@backstage/catalog-model';
import {
Box,
Checkbox,
@@ -27,17 +31,14 @@ import CheckBoxIcon from '@material-ui/icons/CheckBox';
import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { Autocomplete } from '@material-ui/lab';
-import React, { useEffect, useMemo, useRef, useState } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
import { useEntityList } from '../../hooks/useEntityListProvider';
import { EntityOwnerFilter } from '../../filters';
-import { useApi } from '@backstage/core-plugin-api';
-import { catalogApiRef } from '../../api';
-import useAsync from 'react-use/lib/useAsync';
-import useAsyncFn from 'react-use/lib/useAsyncFn';
import { useDebouncedEffect } from '@react-hookz/web';
import PersonIcon from '@material-ui/icons/Person';
import GroupIcon from '@material-ui/icons/Group';
-import { humanizeEntity } from '../EntityRefLink/humanize';
+import { humanizeEntity, humanizeEntityRef } from '../EntityRefLink/humanize';
+import { useFetchEntities } from './useFetchEntities';
/** @public */
export type CatalogReactEntityOwnerPickerClassKey = 'input';
@@ -54,57 +55,22 @@ const useStyles = makeStyles(
const icon = ;
const checkedIcon = ;
+export type EntityOwnerPickerProps = {
+ mode?: 'owners-only' | 'all';
+};
+
/** @public */
-export const EntityOwnerPicker = () => {
+export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => {
const classes = useStyles();
+ const { mode = 'owners-only' } = props || {};
const {
updateFilters,
filters,
queryParameters: { owners: ownersParameter },
} = useEntityList();
- const catalogApi = useApi(catalogApiRef);
const [text, setText] = useState('');
- const [{ value, loading }, handleFetch] = useAsyncFn(
- async (request: { text: string } | { cursor: string; prev: Entity[] }) => {
- const initialRequest = request as { text: string };
- const cursorRequest = request as { cursor: string; prev: Entity[] };
- const limit = 20;
-
- if (cursorRequest.cursor) {
- const response = await catalogApi.queryEntities({
- cursor: cursorRequest.cursor,
- limit,
- });
- return {
- ...response,
- items: [...cursorRequest.prev, ...response.items],
- };
- }
-
- return catalogApi.queryEntities({
- fullTextFilter: {
- term: initialRequest.text || '',
- fields: [
- 'metadata.name',
- 'kind',
- 'spec.profile.displayname',
- 'metadata.title',
- ],
- },
- filter: { kind: ['User', 'Group'] },
- orderFields: [{ field: 'metadata.name', order: 'asc' }],
- limit,
- });
- },
- [text],
- );
-
- useDebouncedEffect(() => handleFetch({ text }), [text], 250);
-
- const availableOwners = value?.items || [];
-
const queryParamOwners = useMemo(
() => [ownersParameter].flat().filter(Boolean) as string[],
[ownersParameter],
@@ -114,7 +80,14 @@ export const EntityOwnerPicker = () => {
queryParamOwners.length ? queryParamOwners : filters.owners?.values ?? [],
);
- const { getEntity, setEntity } = useSelectedOwners(selectedOwners);
+ const [{ value, loading }, handleFetch, { getEntity, setEntity }] =
+ useFetchEntities({
+ mode,
+ initialSelectedOwnersRefs: selectedOwners,
+ });
+ useDebouncedEffect(() => handleFetch({ text }), [text, handleFetch], 250);
+
+ const availableOwners = value?.items || [];
// Set selected owners on query parameter updates; this happens at initial page load and from
// external updates to the page location.
@@ -158,11 +131,15 @@ export const EntityOwnerPicker = () => {
return o === v;
}}
getOptionLabel={o => {
- const entity = typeof o === 'string' ? getEntity(o) || o : o;
-
- return typeof entity === 'string'
- ? entity
- : humanizeEntity(entity, entity.metadata.name);
+ const entity =
+ typeof o === 'string'
+ ? getEntity(o) ||
+ parseEntityRef(o, {
+ defaultKind: 'group',
+ defaultNamespace: 'default',
+ })
+ : o;
+ return humanizeEntity(entity, humanizeEntityRef(entity));
}}
onChange={(_: object, owners) => {
setText('');
@@ -181,7 +158,7 @@ export const EntityOwnerPicker = () => {
}}
filterOptions={x => x}
renderOption={(entity, { selected }) => {
- const isGroup = entity.kind === 'Group';
+ const isGroup = entity.kind.toLocaleLowerCase('en-US') === 'group';
return (
{
)}
- {humanizeEntity(entity, entity.metadata.name)}
+ {humanizeEntity(
+ entity,
+ humanizeEntityRef(entity, { defaultKind: entity.kind }),
+ )}
}
/>
@@ -229,11 +209,8 @@ export const EntityOwnerPicker = () => {
element.scrollTop,
) < 1;
- if (hasReachedEnd && value?.pageInfo.nextCursor) {
- handleFetch({
- cursor: value.pageInfo.nextCursor,
- prev: value.items,
- });
+ if (hasReachedEnd && value?.cursor) {
+ handleFetch({ items: value.items, cursor: value.cursor });
}
},
'data-testid': 'owner-picker-listbox',
@@ -243,36 +220,3 @@ export const EntityOwnerPicker = () => {
);
};
-
-/**
- * Hook used for storing the full entity of the specified owners
- * in order to display users and group using the information contained on each entity.
- * When a component is rendered for the first time, it loads the content of the entities
- * specified by `initialSelectedOwnersRefs` and export the `getEntity` and `setEntity`
- * utilities, used to retrieve and modify the owners.
- */
-function useSelectedOwners(initialSelectedOwnersRefs: string[]) {
- const allEntities = useRef>({});
- const catalogApi = useApi(catalogApiRef);
-
- useAsync(async () => {
- if (initialSelectedOwnersRefs.length === 0) {
- return;
- }
- const initialSelectedEntities = await catalogApi.getEntitiesByRefs({
- entityRefs: initialSelectedOwnersRefs,
- });
- initialSelectedEntities.items.forEach(e => {
- if (e) {
- allEntities.current[stringifyEntityRef(e)] = e;
- }
- });
- }, []);
-
- return {
- getEntity: (entityRef: string) => allEntities.current[entityRef],
- setEntity: (entity: Entity) => {
- allEntities.current[stringifyEntityRef(entity)] = entity;
- },
- };
-}
diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/useFetchEntities.ts b/plugins/catalog-react/src/components/EntityOwnerPicker/useFetchEntities.ts
new file mode 100644
index 0000000000..01cef24fd6
--- /dev/null
+++ b/plugins/catalog-react/src/components/EntityOwnerPicker/useFetchEntities.ts
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2023 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 { useRef } from 'react';
+import { useFacetsEntities } from './useFacetsEntities';
+import { useQueryEntities } from './useQueryEntities';
+import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
+import { useApi } from '@backstage/core-plugin-api';
+import { catalogApiRef } from '../../api';
+import useAsyncFn from 'react-use/lib/useAsyncFn';
+import { useMountEffect } from '@react-hookz/web';
+
+export function useFetchEntities({
+ mode,
+ initialSelectedOwnersRefs,
+}: {
+ mode: 'owners-only' | 'all';
+ initialSelectedOwnersRefs: string[];
+}) {
+ const isOwnersOnlyMode = mode === 'owners-only';
+ const queryEntitiesResponse = useQueryEntities();
+ const facetsEntitiesResponse = useFacetsEntities({
+ enabled: isOwnersOnlyMode,
+ });
+
+ const [state, handleFetch] = isOwnersOnlyMode
+ ? facetsEntitiesResponse
+ : queryEntitiesResponse;
+
+ return [
+ state,
+ handleFetch,
+ useSelectedOwners({
+ enabled: !isOwnersOnlyMode,
+ initialSelectedOwnersRefs,
+ }),
+ ] as const;
+}
+
+/**
+ * Hook used for storing the full entity of the specified owners
+ * in order to display users and group using the information contained on each entity.
+ * When a component is rendered for the first time, it loads the content of the entities
+ * specified by `initialSelectedOwnersRefs` and export the `getEntity` and `setEntity`
+ * utilities, used to retrieve and modify the owners.
+ */
+function useSelectedOwners({
+ enabled,
+ initialSelectedOwnersRefs,
+}: {
+ enabled: boolean;
+ initialSelectedOwnersRefs: string[];
+}) {
+ const allEntities = useRef>({});
+ const catalogApi = useApi(catalogApiRef);
+
+ const [, handleFetch] = useAsyncFn(async () => {
+ const initialSelectedEntities = await catalogApi.getEntitiesByRefs({
+ entityRefs: initialSelectedOwnersRefs,
+ });
+ initialSelectedEntities.items.forEach(e => {
+ if (e) {
+ allEntities.current[stringifyEntityRef(e)] = e;
+ }
+ });
+ }, []);
+
+ useMountEffect(() => {
+ if (enabled && initialSelectedOwnersRefs.length > 0) {
+ handleFetch();
+ }
+ });
+
+ return {
+ getEntity: (entityRef: string) => allEntities.current[entityRef],
+ setEntity: (entity: Entity) => {
+ allEntities.current[stringifyEntityRef(entity)] = entity;
+ },
+ };
+}