diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json
index 3b411518fd..53e6a45134 100644
--- a/plugins/catalog-react/package.json
+++ b/plugins/catalog-react/package.json
@@ -43,7 +43,7 @@
},
"devDependencies": {
"@backstage/cli": "^0.6.11",
- "@backstage/core-api": "^0.2.18",
+ "@backstage/core": "^0.7.10",
"@backstage/dev-utils": "^0.1.14",
"@backstage/test-utils": "^0.1.11",
"@testing-library/jest-dom": "^5.10.1",
diff --git a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx
index 9b40af0dbd..f33feb3a57 100644
--- a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx
+++ b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx
@@ -93,7 +93,7 @@ describe('', () => {
);
expect(rendered.getByText('Type')).toBeInTheDocument();
- const input = await rendered.getByTestId('select');
+ const input = rendered.getByTestId('select');
fireEvent.click(input);
await waitFor(() => rendered.getByText('Service'));
@@ -119,7 +119,7 @@ describe('', () => {
,
);
- const input = await rendered.getByTestId('select');
+ const input = rendered.getByTestId('select');
fireEvent.click(input);
await waitFor(() => rendered.getByText('Service'));
diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx
index a85ddd232b..55ff0c199d 100644
--- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx
+++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx
@@ -16,7 +16,11 @@
import React from 'react';
import { fireEvent, render } from '@testing-library/react';
-import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model';
+import {
+ Entity,
+ RELATION_OWNED_BY,
+ UserEntity,
+} from '@backstage/catalog-model';
import { UserListPicker } from './UserListPicker';
import { MockEntityListContextProvider } from '../../testUtils/providers';
import {
@@ -24,90 +28,110 @@ import {
ApiRegistry,
ConfigApi,
configApiRef,
-} from '@backstage/core-api';
-import { EntityTagFilter, FilterEnvironment } from '../../types';
+ IdentityApi,
+ identityApiRef,
+ storageApiRef,
+} from '@backstage/core';
+import { EntityTagFilter } from '../../types';
+import { CatalogApi } from '@backstage/catalog-client';
+import { catalogApiRef } from '../../api';
+import { MockStorageApi } from '@backstage/test-utils';
-const apis = ApiRegistry.from([
- [
- configApiRef,
- ({
- getOptionalString: jest.fn(
- (key: string) =>
- ({
- 'organization.name': 'Test Company',
- }[key]),
- ),
- } as unknown) as ConfigApi,
- ],
-]);
-
-const filterEnv: FilterEnvironment = {
- user: {
- apiVersion: 'backstage.io/v1alpha1',
- kind: 'User',
- metadata: {
- namespace: 'default',
- name: 'testUser',
- },
- spec: {
- memberOf: [],
- },
+const mockUser: UserEntity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'User',
+ metadata: {
+ namespace: 'default',
+ name: 'testUser',
+ },
+ spec: {
+ memberOf: [],
},
- isStarredEntity: (entity: Entity) => entity.metadata.name === 'component-3',
};
-describe('', () => {
- const backendEntities: Entity[] = [
- {
- apiVersion: '1',
- kind: 'Component',
- metadata: {
- namespace: 'namespace-1',
- name: 'component-1',
- tags: ['tag1'],
- },
- relations: [
- {
- type: RELATION_OWNED_BY,
- target: { kind: 'User', namespace: 'default', name: 'testUser' },
- },
- ],
- },
- {
- apiVersion: '1',
- kind: 'Component',
- metadata: {
- namespace: 'namespace-2',
- name: 'component-2',
- tags: ['tag1'],
- },
- },
- {
- apiVersion: '1',
- kind: 'Component',
- metadata: {
- namespace: 'namespace-2',
- name: 'component-3',
- tags: [],
- },
- },
- {
- apiVersion: '1',
- kind: 'Component',
- metadata: {
- namespace: 'namespace-2',
- name: 'component-4',
- tags: [],
- },
- relations: [
- {
- type: RELATION_OWNED_BY,
- target: { kind: 'User', namespace: 'default', name: 'testUser' },
- },
- ],
- },
- ];
+const mockConfigApi = {
+ getOptionalString: () => 'Test Company',
+} as Partial;
+const mockCatalogApi = {
+ getEntityByName: () => Promise.resolve(mockUser),
+} as Partial;
+
+const mockIdentityApi = {
+ getUserId: () => '',
+} as Partial;
+
+const apis = ApiRegistry.from([
+ [configApiRef, mockConfigApi],
+ [catalogApiRef, mockCatalogApi],
+ [identityApiRef, mockIdentityApi],
+ [storageApiRef, MockStorageApi.create()],
+]);
+
+jest.mock('../../hooks', () => {
+ const actual = jest.requireActual('../../hooks');
+ return {
+ ...actual,
+ useOwnUser: () => ({ value: mockUser }),
+ useStarredEntities: () => ({
+ isStarredEntity: (entity: Entity) =>
+ entity.metadata.name === 'component-3',
+ }),
+ };
+});
+
+const backendEntities: Entity[] = [
+ {
+ apiVersion: '1',
+ kind: 'Component',
+ metadata: {
+ namespace: 'namespace-1',
+ name: 'component-1',
+ tags: ['tag1'],
+ },
+ relations: [
+ {
+ type: RELATION_OWNED_BY,
+ target: { kind: 'User', namespace: 'default', name: 'testUser' },
+ },
+ ],
+ },
+ {
+ apiVersion: '1',
+ kind: 'Component',
+ metadata: {
+ namespace: 'namespace-2',
+ name: 'component-2',
+ tags: ['tag1'],
+ },
+ },
+ {
+ apiVersion: '1',
+ kind: 'Component',
+ metadata: {
+ namespace: 'namespace-2',
+ name: 'component-3',
+ tags: [],
+ },
+ },
+ {
+ apiVersion: '1',
+ kind: 'Component',
+ metadata: {
+ namespace: 'namespace-2',
+ name: 'component-4',
+ tags: [],
+ },
+ relations: [
+ {
+ type: RELATION_OWNED_BY,
+ target: { kind: 'User', namespace: 'default', name: 'testUser' },
+ },
+ ],
+ },
+];
+
+describe('', () => {
it('renders filter groups', () => {
const { queryByText } = render(
@@ -138,7 +162,7 @@ describe('', () => {
it('includes counts alongside each filter', () => {
const { getAllByRole } = render(
-
+
,
@@ -160,7 +184,6 @@ describe('', () => {
value={{
backendEntities,
filters: { tags: new EntityTagFilter(['tag1']) },
- filterEnv,
}}
>
diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx
index 4ef57b7a48..129e5e6b1a 100644
--- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx
+++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx
@@ -14,11 +14,15 @@
* limitations under the License.
*/
-import React, { Fragment, useEffect, useState } from 'react';
+import React, { Fragment, useEffect, useMemo, useState } from 'react';
import { compact } from 'lodash';
import { configApiRef, IconComponent, useApi } from '@backstage/core';
import { UserListFilter, UserListFilterKind } from '../../types';
-import { useEntityListProvider } from '../../hooks';
+import {
+ useEntityListProvider,
+ useOwnUser,
+ useStarredEntities,
+} from '../../hooks';
import {
Card,
List,
@@ -96,22 +100,26 @@ function getFilterGroups(orgName: string | undefined): ButtonGroup[] {
];
}
-// Static filters; only used for generating counts of potentially unselected kinds
-const ownedFilter = new UserListFilter('owned');
-const starredFilter = new UserListFilter('starred');
-
export const UserListPicker = () => {
const classes = useStyles();
const configApi = useApi(configApiRef);
const orgName = configApi.getOptionalString('organization.name') ?? 'Company';
const filterGroups = getFilterGroups(orgName);
- const {
- filters,
- updateFilters,
- backendEntities,
- filterEnv,
- } = useEntityListProvider();
+ const { value: user } = useOwnUser();
+ const { isStarredEntity } = useStarredEntities();
+
+ // Static filters; used for generating counts of potentially unselected kinds
+ const ownedFilter = useMemo(
+ () => new UserListFilter('owned', user, isStarredEntity),
+ [user, isStarredEntity],
+ );
+ const starredFilter = useMemo(
+ () => new UserListFilter('starred', user, isStarredEntity),
+ [user, isStarredEntity],
+ );
+
+ const { filters, updateFilters, backendEntities } = useEntityListProvider();
// To show proper counts for each section, apply all other frontend filters _except_ the user
// filter that's controlled by this picker.
@@ -121,23 +129,22 @@ export const UserListPicker = () => {
useEffect(() => {
const filterFn = reduceEntityFilters(
compact(Object.values({ ...filters, user: undefined })),
- filterEnv,
);
setEntitiesWithoutUserFilter(backendEntities.filter(filterFn));
- }, [filters, backendEntities, filterEnv]);
+ }, [filters, backendEntities]);
function setSelectedFilter({ id }: { id: UserListFilterKind }) {
- updateFilters({ user: new UserListFilter(id) });
+ updateFilters({ user: new UserListFilter(id, user, isStarredEntity) });
}
function getFilterCount(id: UserListFilterKind) {
switch (id) {
case 'owned':
return entitiesWithoutUserFilter.filter(entity =>
- ownedFilter.filterEntity(entity, filterEnv),
+ ownedFilter.filterEntity(entity),
).length;
case 'starred':
return entitiesWithoutUserFilter.filter(entity =>
- starredFilter.filterEntity(entity, filterEnv),
+ starredFilter.filterEntity(entity),
).length;
default:
return entitiesWithoutUserFilter.length;
diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx
index ae0f7d3a25..0b17e0e48b 100644
--- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx
+++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx
@@ -127,7 +127,9 @@ describe('', () => {
const { result, waitFor } = renderHook(() => useEntityListProvider(), {
wrapper,
initialProps: {
- initialFilters: { user: new UserListFilter('owned') },
+ initialFilters: {
+ user: new UserListFilter('owned', mockUser, () => true),
+ },
},
});
await waitFor(() => !!result.current.entities.length);
@@ -147,7 +149,9 @@ describe('', () => {
expect(mockCatalogApi.getEntities).toHaveBeenCalledTimes(1);
act(() =>
- result.current.updateFilters({ user: new UserListFilter('owned') }),
+ result.current.updateFilters({
+ user: new UserListFilter('owned', mockUser, () => true),
+ }),
);
expect(mockCatalogApi.getEntities).toHaveBeenCalledTimes(1);
expect(result.current.entities.length).toBe(1);
diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx
index 99cebd6310..2cfeeca57c 100644
--- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx
+++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx
@@ -20,7 +20,6 @@ import React, {
useCallback,
useContext,
useEffect,
- useMemo,
useState,
} from 'react';
import { useAsyncFn, useDebounce } from 'react-use';
@@ -33,11 +32,8 @@ import {
EntityKindFilter,
EntityTagFilter,
EntityTypeFilter,
- FilterEnvironment,
UserListFilter,
} from '../types';
-import { useOwnUser } from './useOwnUser';
-import { useStarredEntities } from './useStarredEntities';
import { compact, isEqual } from 'lodash';
export type DefaultEntityFilters = {
@@ -56,12 +52,6 @@ export type EntityListContextProps<
*/
filters: EntityFilters;
- /**
- * The filter environment passed to frontend filters; contains information available in the React
- * tree.
- */
- filterEnv: FilterEnvironment;
-
/**
* The resolved list of catalog entities, after all filters are applied.
*/
@@ -101,8 +91,6 @@ export const EntityListProvider = ({
children,
}: PropsWithChildren>) => {
const catalogApi = useApi(catalogApiRef);
- const { value: user } = useOwnUser();
- const { isStarredEntity } = useStarredEntities();
// TODO(timbonicus): is it possible to register initial filters from query params? e.g. if the
// query key matches the generic definition, call a constructor with the value
@@ -113,14 +101,6 @@ export const EntityListProvider = ({
const [entities, setEntities] = useState([]);
const [backendEntities, setBackendEntities] = useState([]);
- const filterEnv: FilterEnvironment = useMemo(
- () => ({
- user,
- isStarredEntity,
- }),
- [user, isStarredEntity],
- );
-
// Store resolved catalog-backend filters and deep compare on filter updates, to avoid refetching
// when only frontend filters change
const [backendFilters, setBackendFilters] = useState<
@@ -153,10 +133,10 @@ export const EntityListProvider = ({
// Apply frontend filters
useEffect(() => {
const resolvedEntities = (backendEntities ?? []).filter(
- reduceEntityFilters(compact(Object.values(filters)), filterEnv),
+ reduceEntityFilters(compact(Object.values(filters))),
);
setEntities(resolvedEntities);
- }, [backendEntities, filterEnv, filters]);
+ }, [backendEntities, filters]);
const updateFilters = useCallback(
(
@@ -177,7 +157,6 @@ export const EntityListProvider = ({
false,
- },
loading: false,
};
diff --git a/plugins/catalog-react/src/types.ts b/plugins/catalog-react/src/types.ts
index e83eadb3fc..98ea2fb9b0 100644
--- a/plugins/catalog-react/src/types.ts
+++ b/plugins/catalog-react/src/types.ts
@@ -17,11 +17,6 @@
import { Entity, UserEntity } from '@backstage/catalog-model';
import { isOwnerOf } from './utils';
-export type FilterEnvironment = {
- user: UserEntity | undefined;
- isStarredEntity: (entity: Entity) => boolean;
-};
-
export type EntityFilter = {
/**
* Get filters to add to the catalog-backend request. These are a dot-delimited field with
@@ -39,33 +34,19 @@ export type EntityFilter = {
* @param entity
* @param env
*/
- filterEntity?: (entity: Entity, env: FilterEnvironment) => boolean;
+ filterEntity?: (entity: Entity) => boolean;
};
export class EntityKindFilter implements EntityFilter {
- private readonly _value: string;
- constructor(kind: string) {
- this._value = kind;
- }
-
- get value() {
- return this._value;
- }
+ constructor(readonly value: string) {}
getCatalogFilters(): Record {
- return { kind: this._value };
+ return { kind: this.value };
}
}
export class EntityTypeFilter implements EntityFilter {
- private _value: string;
- constructor(type: string) {
- this._value = type;
- }
-
- get value() {
- return this._value;
- }
+ constructor(readonly value: string) {}
getCatalogFilters(): Record {
return { 'spec.type': this.value };
@@ -73,14 +54,7 @@ export class EntityTypeFilter implements EntityFilter {
}
export class EntityTagFilter implements EntityFilter {
- private _values: string[];
- constructor(values: string[]) {
- this._values = values;
- }
-
- get values() {
- return this._values;
- }
+ constructor(readonly values: string[]) {}
filterEntity(entity: Entity): boolean {
return this.values.every(v => (entity.metadata.tags ?? []).includes(v));
@@ -89,17 +63,18 @@ export class EntityTagFilter implements EntityFilter {
export type UserListFilterKind = 'owned' | 'starred' | 'all';
export class UserListFilter implements EntityFilter {
- readonly value: UserListFilterKind;
- constructor(value: UserListFilterKind) {
- this.value = value;
- }
+ constructor(
+ readonly value: UserListFilterKind,
+ readonly user: UserEntity | undefined,
+ readonly isStarredEntity: (entity: Entity) => boolean,
+ ) {}
- filterEntity(entity: Entity, env: FilterEnvironment): boolean {
+ filterEntity(entity: Entity): boolean {
switch (this.value) {
case 'owned':
- return env.user !== undefined && isOwnerOf(env.user, entity);
+ return this.user !== undefined && isOwnerOf(this.user, entity);
case 'starred':
- return env.isStarredEntity(entity);
+ return this.isStarredEntity(entity);
default:
return true;
}
diff --git a/plugins/catalog-react/src/utils/filters.ts b/plugins/catalog-react/src/utils/filters.ts
index afe2b0b3d3..73f1763bb1 100644
--- a/plugins/catalog-react/src/utils/filters.ts
+++ b/plugins/catalog-react/src/utils/filters.ts
@@ -15,7 +15,7 @@
*/
import { Entity } from '@backstage/catalog-model';
-import { EntityFilter, FilterEnvironment } from '../types';
+import { EntityFilter } from '../types';
export function reduceCatalogFilters(
filters: EntityFilter[],
@@ -30,10 +30,9 @@ export function reduceCatalogFilters(
export function reduceEntityFilters(
filters: EntityFilter[],
- env: FilterEnvironment,
): (entity: Entity) => boolean {
return (entity: Entity) =>
filters.every(
- filter => !filter.filterEntity || filter.filterEntity(entity, env),
+ filter => !filter.filterEntity || filter.filterEntity(entity),
);
}
diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx
index 3569a3792c..95bd857221 100644
--- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx
+++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx
@@ -29,9 +29,11 @@ import {
EntityListProvider,
EntityTagPicker,
EntityTypePicker,
+ useOwnUser,
UserListFilter,
UserListFilterKind,
UserListPicker,
+ useStarredEntities,
} from '@backstage/plugin-catalog-react';
import { createComponentRouteRef } from '../../routes';
@@ -62,9 +64,11 @@ export const CatalogPage = ({
}: CatalogPageProps) => {
const styles = useStyles();
const createComponentLink = useRouteRef(createComponentRouteRef);
+ const { value: user } = useOwnUser();
+ const { isStarredEntity } = useStarredEntities();
const initialFilters = {
kind: new EntityKindFilter('component'),
- user: new UserListFilter(initiallySelectedFilter),
+ user: new UserListFilter(initiallySelectedFilter, user, isStarredEntity),
};
return (
diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx
index ce8447cf6d..1b40aa498b 100644
--- a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx
+++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx
@@ -72,7 +72,9 @@ describe('CatalogTable component', () => {
false),
+ },
}}
>