update to use getEntityByRef

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2022-03-03 11:26:42 +01:00
parent a52f69987a
commit 899f196af5
41 changed files with 157 additions and 111 deletions
+20
View File
@@ -0,0 +1,20 @@
---
'@backstage/plugin-api-docs': patch
'@backstage/plugin-auth-backend': patch
'@backstage/plugin-badges-backend': patch
'@backstage/plugin-catalog': patch
'@backstage/plugin-catalog-graph': patch
'@backstage/plugin-catalog-import': patch
'@backstage/plugin-catalog-react': patch
'@backstage/plugin-code-coverage-backend': patch
'@backstage/plugin-explore': patch
'@backstage/plugin-fossa': patch
'@backstage/plugin-jenkins-backend': patch
'@backstage/plugin-rollbar': patch
'@backstage/plugin-scaffolder-backend': patch
'@backstage/plugin-techdocs': patch
'@backstage/plugin-techdocs-backend': patch
'@backstage/plugin-todo-backend': patch
---
Use `getEntityByRef` instead of `getEntityByName` in the catalog client
+2 -2
View File
@@ -53,8 +53,8 @@ createDevApp()
items: mockEntities.slice(),
};
},
async getEntityByName(name: string) {
return mockEntities.find(e => e.metadata.name === name);
async getEntityByRef(ref: string) {
return mockEntities.find(e => e.metadata.name === ref);
},
} as unknown as typeof catalogApiRef.T),
})
@@ -14,7 +14,11 @@
* limitations under the License.
*/
import { Entity, RELATION_MEMBER_OF } from '@backstage/catalog-model';
import {
Entity,
parseEntityRef,
RELATION_MEMBER_OF,
} from '@backstage/catalog-model';
import { ConfigReader } from '@backstage/core-app-api';
import { TableColumn, TableProps } from '@backstage/core-components';
import {
@@ -60,11 +64,11 @@ describe('DefaultApiExplorerPage', () => {
}),
getLocationByRef: () =>
Promise.resolve({ id: 'id', type: 'url', target: 'url' }),
getEntityByName: async entityName => {
getEntityByRef: async entityRef => {
return {
apiVersion: 'backstage.io/v1alpha1',
kind: 'User',
metadata: { name: entityName.name },
metadata: { name: parseEntityRef(entityRef).name },
relations: [
{
type: RELATION_MEMBER_OF,
@@ -26,6 +26,7 @@ import { CatalogIdentityClient } from './CatalogIdentityClient';
describe('CatalogIdentityClient', () => {
const catalogApi: jest.Mocked<CatalogApi> = {
getLocationById: jest.fn(),
getEntityByRef: jest.fn(),
getEntityByName: jest.fn(),
getEntities: jest.fn(),
addLocation: jest.fn(),
@@ -60,6 +60,7 @@ describe('createRouter', () => {
catalog = {
addLocation: jest.fn(),
getEntities: jest.fn(),
getEntityByRef: jest.fn(),
getEntityByName: jest.fn(),
getLocationByRef: jest.fn(),
getLocationById: jest.fn(),
@@ -103,7 +104,7 @@ describe('createRouter', () => {
describe('GET /entity/:namespace/:kind/:name/badge-specs', () => {
it('returns all badge specs for entity', async () => {
catalog.getEntityByName.mockResolvedValueOnce(entity);
catalog.getEntityByRef.mockResolvedValueOnce(entity);
badgeBuilder.getBadges.mockResolvedValueOnce([{ id: badge.id }]);
badgeBuilder.createBadgeJson.mockResolvedValueOnce(badge);
@@ -115,8 +116,8 @@ describe('createRouter', () => {
expect(response.status).toEqual(200);
expect(response.text).toEqual(JSON.stringify([badge], null, 2));
expect(catalog.getEntityByName).toHaveBeenCalledTimes(1);
expect(catalog.getEntityByName).toHaveBeenCalledWith(
expect(catalog.getEntityByRef).toHaveBeenCalledTimes(1);
expect(catalog.getEntityByRef).toHaveBeenCalledWith(
{
namespace: 'default',
kind: 'service',
@@ -142,7 +143,7 @@ describe('createRouter', () => {
describe('GET /entity/:namespace/:kind/:name/badge/test-badge', () => {
it('returns badge for entity', async () => {
catalog.getEntityByName.mockResolvedValueOnce(entity);
catalog.getEntityByRef.mockResolvedValueOnce(entity);
const image = '<svg>...</svg>';
badgeBuilder.createBadgeSvg.mockResolvedValueOnce(image);
@@ -154,8 +155,8 @@ describe('createRouter', () => {
expect(response.status).toEqual(200);
expect(response.body).toEqual(Buffer.from(image));
expect(catalog.getEntityByName).toHaveBeenCalledTimes(1);
expect(catalog.getEntityByName).toHaveBeenCalledWith(
expect(catalog.getEntityByRef).toHaveBeenCalledTimes(1);
expect(catalog.getEntityByRef).toHaveBeenCalledWith(
{
namespace: 'default',
kind: 'service',
@@ -179,7 +180,7 @@ describe('createRouter', () => {
});
it('returns badge spec for entity', async () => {
catalog.getEntityByName.mockResolvedValueOnce(entity);
catalog.getEntityByRef.mockResolvedValueOnce(entity);
badgeBuilder.createBadgeJson.mockResolvedValueOnce(badge);
const url = '/entity/default/service/test/badge/test-badge?format=json';
@@ -192,7 +193,7 @@ describe('createRouter', () => {
describe('Errors', () => {
it('returns 404 for unknown entities', async () => {
catalog.getEntityByName.mockResolvedValue(undefined);
catalog.getEntityByRef.mockResolvedValue(undefined);
async function testUrl(url: string) {
const response = await request(app).get(url);
expect(response.status).toEqual(404);
+2 -2
View File
@@ -46,7 +46,7 @@ export async function createRouter(
router.get('/entity/:namespace/:kind/:name/badge-specs', async (req, res) => {
const { namespace, kind, name } = req.params;
const entity = await catalog.getEntityByName(
const entity = await catalog.getEntityByRef(
{ namespace, kind, name },
{
token: getBearerToken(req.headers.authorization),
@@ -84,7 +84,7 @@ export async function createRouter(
'/entity/:namespace/:kind/:name/badge/:badgeId',
async (req, res) => {
const { namespace, kind, name, badgeId } = req.params;
const entity = await catalog.getEntityByName(
const entity = await catalog.getEntityByRef(
{ namespace, kind, name },
{
token: getBearerToken(req.headers.authorization),
+5 -3
View File
@@ -139,10 +139,12 @@ createDevApp()
deps: {},
factory() {
return {
async getEntityByName(
name: CompoundEntityRef,
async getEntityByRef(
ref: string | CompoundEntityRef,
): Promise<Entity | undefined> {
return entities[stringifyEntityRef(name)];
return entities[
typeof ref === 'string' ? ref : stringifyEntityRef(ref)
];
},
async getEntities(): Promise<GetEntitiesResponse> {
return { items: Object.values(entities) };
@@ -57,7 +57,8 @@ describe('<CatalogGraphCard/>', () => {
};
catalog = {
getEntities: jest.fn(),
getEntityByName: jest.fn(async _ => ({ ...entity, relations: [] })),
getEntityByRef: jest.fn(async _ => ({ ...entity, relations: [] })),
getEntityByName: jest.fn(),
removeEntityByUid: jest.fn(),
getLocationById: jest.fn(),
getLocationByRef: jest.fn(),
@@ -88,7 +89,7 @@ describe('<CatalogGraphCard/>', () => {
expect(await findByText('b:d/c')).toBeInTheDocument();
expect(await findAllByTestId('node')).toHaveLength(1);
expect(catalog.getEntityByName).toBeCalledTimes(1);
expect(catalog.getEntityByRef).toBeCalledTimes(1);
});
test('renders with custom title', async () => {
@@ -88,7 +88,10 @@ describe('<CatalogGraphPage/>', () => {
};
catalog = {
getEntities: jest.fn(),
getEntityByName: jest.fn(async n => (n.name === 'e' ? entityE : entityC)),
getEntityByRef: jest.fn(async (n: any) =>
n === 'b:d/e' ? entityE : entityC,
),
getEntityByName: jest.fn(),
removeEntityByUid: jest.fn(),
getLocationById: jest.fn(),
getLocationByRef: jest.fn(),
@@ -128,7 +131,7 @@ describe('<CatalogGraphPage/>', () => {
expect(await findByText('b:d/c')).toBeInTheDocument();
expect(await findByText('b:d/e')).toBeInTheDocument();
expect(await findAllByTestId('node')).toHaveLength(2);
expect(catalog.getEntityByName).toBeCalledTimes(2);
expect(catalog.getEntityByRef).toBeCalledTimes(2);
});
test('should toggle filters', async () => {
@@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Entity,
RELATION_HAS_PART,
RELATION_OWNED_BY,
RELATION_OWNER_OF,
RELATION_PART_OF,
stringifyEntityRef,
} from '@backstage/catalog-model';
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
@@ -155,7 +155,8 @@ describe('<EntityRelationsGraph/>', () => {
};
catalog = {
getEntities: jest.fn(),
getEntityByName: jest.fn(async n => entities[stringifyEntityRef(n)]),
getEntityByRef: jest.fn(async n => entities[n as string]),
getEntityByName: jest.fn(),
removeEntityByUid: jest.fn(),
getLocationById: jest.fn(),
getLocationByRef: jest.fn(),
@@ -178,7 +179,7 @@ describe('<EntityRelationsGraph/>', () => {
});
test('renders a single node without exploding', async () => {
catalog.getEntityByName.mockResolvedValue({
catalog.getEntityByRef.mockResolvedValue({
apiVersion: 'a',
kind: 'b',
metadata: {
@@ -198,11 +199,11 @@ describe('<EntityRelationsGraph/>', () => {
expect(await findByText('b:d/c')).toBeInTheDocument();
expect(await findAllByTestId('node')).toHaveLength(1);
expect(catalog.getEntityByName).toBeCalledTimes(1);
expect(catalog.getEntityByRef).toBeCalledTimes(1);
});
test('renders a progress indicator while loading', async () => {
catalog.getEntityByName.mockImplementation(() => new Promise(() => {}));
catalog.getEntityByRef.mockImplementation(() => new Promise(() => {}));
const { findByRole } = await renderInTestApp(
<Wrapper>
@@ -213,12 +214,12 @@ describe('<EntityRelationsGraph/>', () => {
);
expect(await findByRole('progressbar')).toBeInTheDocument();
expect(catalog.getEntityByName).toBeCalledTimes(1);
expect(catalog.getEntityByRef).toBeCalledTimes(1);
});
test('does not explode if an entity is missing', async () => {
catalog.getEntityByName.mockImplementation(async n => {
if (n.name === 'c') {
catalog.getEntityByRef.mockImplementation(async (n: any) => {
if (n === 'b:d/c') {
return {
apiVersion: 'a',
kind: 'b',
@@ -253,7 +254,7 @@ describe('<EntityRelationsGraph/>', () => {
expect(await findByText('b:d/c')).toBeInTheDocument();
expect(await findAllByTestId('node')).toHaveLength(1);
expect(catalog.getEntityByName).toBeCalledTimes(2);
expect(catalog.getEntityByRef).toBeCalledTimes(2);
});
test('renders at max depth of one', async () => {
@@ -276,7 +277,7 @@ describe('<EntityRelationsGraph/>', () => {
expect(await findAllByText('hasPart')).toHaveLength(1);
expect(await findAllByTestId('label')).toHaveLength(2);
expect(catalog.getEntityByName).toBeCalledTimes(3);
expect(catalog.getEntityByRef).toBeCalledTimes(3);
});
test('renders simplied graph at full depth', async () => {
@@ -301,7 +302,7 @@ describe('<EntityRelationsGraph/>', () => {
expect(await findAllByText('hasPart')).toHaveLength(2);
expect(await findAllByTestId('label')).toHaveLength(3);
expect(catalog.getEntityByName).toBeCalledTimes(4);
expect(catalog.getEntityByRef).toBeCalledTimes(4);
});
test('renders full graph at full depth', async () => {
@@ -328,7 +329,7 @@ describe('<EntityRelationsGraph/>', () => {
expect(await findAllByText('partOf')).toHaveLength(2);
expect(await findAllByTestId('label')).toHaveLength(8);
expect(catalog.getEntityByName).toBeCalledTimes(4);
expect(catalog.getEntityByRef).toBeCalledTimes(4);
});
test('renders full graph at full depth with merged relations', async () => {
@@ -353,7 +354,7 @@ describe('<EntityRelationsGraph/>', () => {
expect(await findAllByText('hasPart')).toHaveLength(2);
expect(await findAllByTestId('label')).toHaveLength(4);
expect(catalog.getEntityByName).toBeCalledTimes(4);
expect(catalog.getEntityByRef).toBeCalledTimes(4);
});
test('renders a graph with multiple root nodes', async () => {
@@ -379,7 +380,7 @@ describe('<EntityRelationsGraph/>', () => {
expect(await findAllByText('partOf')).toHaveLength(2);
expect(await findAllByTestId('label')).toHaveLength(3);
expect(catalog.getEntityByName).toBeCalledTimes(4);
expect(catalog.getEntityByRef).toBeCalledTimes(4);
});
test('renders a graph with filtered kinds and relations', async () => {
@@ -401,7 +402,7 @@ describe('<EntityRelationsGraph/>', () => {
expect(await findAllByText('ownerOf')).toHaveLength(1);
expect(await findAllByTestId('label')).toHaveLength(1);
expect(catalog.getEntityByName).toBeCalledTimes(2);
expect(catalog.getEntityByRef).toBeCalledTimes(2);
});
test('handle clicks on a node', async () => {
@@ -29,6 +29,7 @@ describe('useEntityStore', () => {
beforeEach(() => {
catalogApi = {
getEntities: jest.fn(),
getEntityByRef: jest.fn(),
getEntityByName: jest.fn(),
removeEntityByUid: jest.fn(),
getLocationById: jest.fn(),
@@ -64,7 +65,7 @@ describe('useEntityStore', () => {
},
};
catalogApi.getEntityByName.mockResolvedValue(entity);
catalogApi.getEntityByRef.mockResolvedValue(entity);
const { result, waitFor } = renderHook(() => useEntityStore());
@@ -84,7 +85,7 @@ describe('useEntityStore', () => {
test('handles request failures', async () => {
const err = new Error('Hello World');
catalogApi.getEntityByName.mockRejectedValue(err);
catalogApi.getEntityByRef.mockRejectedValue(err);
const { result, waitFor } = renderHook(() => useEntityStore());
@@ -101,7 +102,7 @@ describe('useEntityStore', () => {
});
test('handles loading', async () => {
catalogApi.getEntityByName.mockReturnValue(new Promise(() => {}));
catalogApi.getEntityByRef.mockReturnValue(new Promise(() => {}));
const { result } = renderHook(() => useEntityStore());
@@ -133,7 +134,7 @@ describe('useEntityStore', () => {
},
};
catalogApi.getEntityByName.mockResolvedValue(entity1);
catalogApi.getEntityByRef.mockResolvedValue(entity1);
const { result, waitFor } = renderHook(() => useEntityStore());
@@ -150,7 +151,7 @@ describe('useEntityStore', () => {
});
});
catalogApi.getEntityByName.mockResolvedValue(entity2);
catalogApi.getEntityByRef.mockResolvedValue(entity2);
act(() => {
result.current.requestEntities([
@@ -188,7 +189,7 @@ describe('useEntityStore', () => {
},
};
catalogApi.getEntityByName.mockResolvedValue(entity1);
catalogApi.getEntityByRef.mockResolvedValue(entity1);
const { result, waitFor } = renderHook(() => useEntityStore());
@@ -205,7 +206,7 @@ describe('useEntityStore', () => {
});
});
catalogApi.getEntityByName.mockResolvedValue(entity2);
catalogApi.getEntityByRef.mockResolvedValue(entity2);
act(() => {
result.current.requestEntities(['kind:namespace/name2']);
@@ -233,6 +234,6 @@ describe('useEntityStore', () => {
});
});
expect(catalogApi.getEntityByName).toBeCalledTimes(2);
expect(catalogApi.getEntityByRef).toBeCalledTimes(2);
});
});
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Entity, parseEntityRef } from '@backstage/catalog-model';
import { Entity } from '@backstage/catalog-model';
import { useApi } from '@backstage/core-plugin-api';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import limiterFactory from 'p-limit';
@@ -73,9 +73,7 @@ export function useEntityStore(): {
return;
}
const promise = catalogClient.getEntityByName(
parseEntityRef(entityRef),
);
const promise = catalogClient.getEntityByRef(entityRef);
outstandingEntities.set(entityRef, promise);
@@ -93,6 +93,7 @@ describe('CatalogImportClient', () => {
getEntities: jest.fn(),
addLocation: jest.fn(),
removeLocationById: jest.fn(),
getEntityByRef: jest.fn(),
getEntityByName: jest.fn(),
getLocationByRef: jest.fn(),
getLocationById: jest.fn(),
@@ -38,6 +38,7 @@ describe('<StepPrepareCreatePullRequest />', () => {
const catalogApi: jest.Mocked<typeof catalogApiRef.T> = {
getEntities: jest.fn(),
addLocation: jest.fn(),
getEntityByRef: jest.fn(),
getEntityByName: jest.fn(),
getLocationByRef: jest.fn(),
getLocationById: jest.fn(),
@@ -55,7 +55,7 @@ const mockConfigApi = {
} as Partial<ConfigApi>;
const mockCatalogApi = {
getEntityByName: () => Promise.resolve(mockUser),
getEntityByRef: () => Promise.resolve(mockUser),
} as Partial<CatalogApi>;
const mockIdentityApi = {
@@ -115,7 +115,7 @@ export const useEntityFromUrl = (): EntityLoadingStatus => {
loading,
retry: refresh,
} = useAsyncRetry(
() => catalogApi.getEntityByName({ kind, namespace, name }),
() => catalogApi.getEntityByRef({ kind, namespace, name }),
[catalogApi, kind, namespace, name],
);
@@ -76,7 +76,7 @@ const mockIdentityApi: Partial<IdentityApi> = {
};
const mockCatalogApi: Partial<CatalogApi> = {
getEntities: jest.fn().mockImplementation(async () => ({ items: entities })),
getEntityByName: async () => undefined,
getEntityByRef: async () => undefined,
};
const wrapper = ({
@@ -30,13 +30,13 @@ import { loadCatalogOwnerRefs, useEntityOwnership } from './useEntityOwnership';
describe('useEntityOwnership', () => {
type MockIdentityApi = jest.Mocked<Pick<IdentityApi, 'getBackstageIdentity'>>;
type MockCatalogApi = jest.Mocked<Pick<CatalogApi, 'getEntityByName'>>;
type MockCatalogApi = jest.Mocked<Pick<CatalogApi, 'getEntityByRef'>>;
const mockIdentityApi: MockIdentityApi = {
getBackstageIdentity: jest.fn(),
};
const mockCatalogApi: MockCatalogApi = {
getEntityByName: jest.fn(),
getEntityByRef: jest.fn(),
};
const identityApi = mockIdentityApi as unknown as IdentityApi;
@@ -102,11 +102,11 @@ describe('useEntityOwnership', () => {
describe('loadCatalogOwnerRefs', () => {
it('loads the first user from the catalog', async () => {
mockCatalogApi.getEntityByName.mockResolvedValueOnce(user2Entity);
mockCatalogApi.getEntityByRef.mockResolvedValueOnce(user2Entity);
await expect(
loadCatalogOwnerRefs(catalogApi, ['user:default/user2']),
).resolves.toEqual(['group:default/group1']);
expect(mockCatalogApi.getEntityByName).toBeCalledWith({
expect(mockCatalogApi.getEntityByRef).toBeCalledWith({
kind: 'user',
namespace: 'default',
name: 'user2',
@@ -114,11 +114,11 @@ describe('useEntityOwnership', () => {
});
it('gracefully handles missing user', async () => {
mockCatalogApi.getEntityByName.mockResolvedValueOnce(undefined);
mockCatalogApi.getEntityByRef.mockResolvedValueOnce(undefined);
await expect(
loadCatalogOwnerRefs(catalogApi, ['user:default/user2']),
).resolves.toEqual([]);
expect(mockCatalogApi.getEntityByName).toBeCalledWith({
expect(mockCatalogApi.getEntityByRef).toBeCalledWith({
kind: 'user',
namespace: 'default',
name: 'user2',
@@ -133,7 +133,7 @@ describe('useEntityOwnership', () => {
userEntityRef: 'user:default/user1',
ownershipEntityRefs: ['user:default/user1', 'group:default/group1'],
});
mockCatalogApi.getEntityByName.mockResolvedValue(undefined);
mockCatalogApi.getEntityByRef.mockResolvedValue(undefined);
const { result, waitForValueToChange } = renderHook(
() => useEntityOwnership(),
@@ -48,7 +48,7 @@ export async function loadCatalogOwnerRefs(
const primaryUserRef = identityOwnerRefs.find(ref => ref.startsWith('user:'));
if (primaryUserRef) {
const entity = await catalogApi.getEntityByName(
const entity = await catalogApi.getEntityByRef(
parseEntityRef(primaryUserRef),
);
if (entity) {
@@ -34,7 +34,12 @@ export function useOwnUser(): AsyncState<UserEntity | undefined> {
return useAsync(async () => {
const identity = await identityApi.getBackstageIdentity();
return catalogApi.getEntityByName(
// TODO(freben): Defensively parse with defaults even though getEntityByRef
// supports the string form, since some auth resolvers have been known to
// return incomplete refs (just the name part) historically. This can be
// simplified in the future to just pass the ref immediately to
// getEntityByRef.
return catalogApi.getEntityByRef(
parseEntityRef(identity.userEntityRef, {
defaultKind: 'User',
defaultNamespace: DEFAULT_NAMESPACE,
@@ -40,7 +40,7 @@ export const useEntityFromUrl = (): EntityLoadingStatus => {
loading,
retry: refresh,
} = useAsyncRetry(
() => catalogApi.getEntityByName({ kind, namespace, name }),
() => catalogApi.getEntityByRef({ kind, namespace, name }),
[catalogApi, kind, namespace, name],
);
@@ -17,6 +17,7 @@
import { CatalogApi } from '@backstage/catalog-client';
import {
Entity,
parseEntityRef,
RELATION_MEMBER_OF,
RELATION_OWNED_BY,
} from '@backstage/catalog-model';
@@ -104,11 +105,11 @@ describe('DefaultCatalogPage', () => {
}),
getLocationByRef: () =>
Promise.resolve({ id: 'id', type: 'url', target: 'url' }),
getEntityByName: async entityName => {
getEntityByRef: async entityRef => {
return {
apiVersion: 'backstage.io/v1alpha1',
kind: 'User',
metadata: { name: entityName.name },
metadata: { name: parseEntityRef(entityRef).name },
relations: [
{
type: RELATION_MEMBER_OF,
@@ -18,7 +18,7 @@ import express from 'express';
import Router from 'express-promise-router';
import { Logger } from 'winston';
import xmlparser from 'express-xml-bodyparser';
import { CatalogClient } from '@backstage/catalog-client';
import { CatalogApi, CatalogClient } from '@backstage/catalog-client';
import {
errorHandler,
PluginDatabaseManager,
@@ -33,10 +33,7 @@ import { aggregateCoverage, CoverageUtils } from './CoverageUtils';
import { Cobertura } from './converter/cobertura';
import { Jacoco } from './converter/jacoco';
import { Converter } from './converter';
import {
getEntitySourceLocation,
parseEntityRef,
} from '@backstage/catalog-model';
import { getEntitySourceLocation } from '@backstage/catalog-model';
export interface RouterOptions {
config: Config;
@@ -59,7 +56,7 @@ export const makeRouter = async (
await database.getClient(),
);
const codecovUrl = await discovery.getExternalBaseUrl('code-coverage');
const catalogApi = new CatalogClient({ discoveryApi: discovery });
const catalogApi: CatalogApi = new CatalogClient({ discoveryApi: discovery });
const scm = ScmIntegrations.fromConfig(config);
const router = Router();
@@ -77,8 +74,7 @@ export const makeRouter = async (
*/
router.get('/report', async (req, res) => {
const { entity } = req.query;
const entityName = parseEntityRef(entity as string);
const entityLookup = await catalogApi.getEntityByName(entityName);
const entityLookup = await catalogApi.getEntityByRef(entity as string);
if (!entityLookup) {
throw new NotFoundError(`No entity found matching ${entity}`);
}
@@ -100,8 +96,7 @@ export const makeRouter = async (
*/
router.get('/history', async (req, res) => {
const { entity } = req.query;
const entityName = parseEntityRef(entity as string);
const entityLookup = await catalogApi.getEntityByName(entityName);
const entityLookup = await catalogApi.getEntityByRef(entity as string);
if (!entityLookup) {
throw new NotFoundError(`No entity found matching ${entity}`);
}
@@ -119,8 +114,7 @@ export const makeRouter = async (
*/
router.get('/file-content', async (req, res) => {
const { entity, path } = req.query;
const entityName = parseEntityRef(entity as string);
const entityLookup = await catalogApi.getEntityByName(entityName);
const entityLookup = await catalogApi.getEntityByRef(entity as string);
if (!entityLookup) {
throw new NotFoundError(`No entity found matching ${entity}`);
}
@@ -171,8 +165,7 @@ export const makeRouter = async (
*/
router.post('/report', async (req, res) => {
const { entity, coverageType } = req.query;
const entityName = parseEntityRef(entity as string);
const entityLookup = await catalogApi.getEntityByName(entityName);
const entityLookup = await catalogApi.getEntityByRef(entity as string);
if (!entityLookup) {
throw new NotFoundError(`No entity found matching ${entity}`);
}
@@ -28,6 +28,7 @@ describe('<DefaultExplorePage />', () => {
getLocationById: jest.fn(),
removeLocationById: jest.fn(),
removeEntityByUid: jest.fn(),
getEntityByRef: jest.fn(),
getEntityByName: jest.fn(),
refreshEntity: jest.fn(),
getEntityAncestors: jest.fn(),
@@ -29,6 +29,7 @@ describe('<DomainExplorerContent />', () => {
getLocationById: jest.fn(),
removeLocationById: jest.fn(),
removeEntityByUid: jest.fn(),
getEntityByRef: jest.fn(),
getEntityByName: jest.fn(),
refreshEntity: jest.fn(),
getEntityAncestors: jest.fn(),
@@ -29,6 +29,7 @@ describe('<GroupsExplorerContent />', () => {
getLocationById: jest.fn(),
removeLocationById: jest.fn(),
removeEntityByUid: jest.fn(),
getEntityByRef: jest.fn(),
getEntityByName: jest.fn(),
refreshEntity: jest.fn(),
getEntityAncestors: jest.fn(),
@@ -29,6 +29,7 @@ describe('<FossaPage />', () => {
const catalogApi: jest.Mocked<CatalogApi> = {
addLocation: jest.fn(),
getEntities: jest.fn(),
getEntityByRef: jest.fn(),
getEntityByName: jest.fn(),
getLocationByRef: jest.fn(),
getLocationById: jest.fn(),
+1 -1
View File
@@ -166,7 +166,7 @@ class AcmeJenkinsInfoProvider implements JenkinsInfoProvider {
const PAAS_ANNOTATION = 'acme.example.com/paas-project-name';
// lookup pass-project-name from entity annotation
const entity = await this.catalog.getEntityByName(opt.entityRef);
const entity = await this.catalog.getEntityByRef(opt.entityRef);
if (!entity) {
throw new Error(
`Couldn't find entity with name: ${stringifyEntityRef(opt.entityRef)}`,
@@ -160,7 +160,7 @@ describe('JenkinsConfig', () => {
describe('DefaultJenkinsInfoProvider', () => {
const mockCatalog: jest.Mocked<CatalogApi> = {
getEntityByName: jest.fn(),
getEntityByRef: jest.fn(),
} as any as jest.Mocked<CatalogApi>;
const entityRef: CompoundEntityRef = {
@@ -171,7 +171,7 @@ describe('DefaultJenkinsInfoProvider', () => {
function configureProvider(configData: any, entityData: any) {
const config = new ConfigReader(configData);
mockCatalog.getEntityByName.mockReturnValueOnce(
mockCatalog.getEntityByRef.mockReturnValueOnce(
Promise.resolve(entityData as Entity),
);
@@ -185,7 +185,7 @@ describe('DefaultJenkinsInfoProvider', () => {
const provider = configureProvider({ jenkins: {} }, undefined);
await expect(provider.getInstance({ entityRef })).rejects.toThrowError();
expect(mockCatalog.getEntityByName).toBeCalledWith(entityRef);
expect(mockCatalog.getEntityByRef).toBeCalledWith(entityRef);
});
it('Reads simple config and annotation', async () => {
@@ -207,7 +207,7 @@ describe('DefaultJenkinsInfoProvider', () => {
);
const info: JenkinsInfo = await provider.getInstance({ entityRef });
expect(mockCatalog.getEntityByName).toBeCalledWith(entityRef);
expect(mockCatalog.getEntityByRef).toBeCalledWith(entityRef);
expect(info).toStrictEqual({
baseUrl: 'https://jenkins.example.com',
crumbIssuer: undefined,
@@ -243,7 +243,7 @@ describe('DefaultJenkinsInfoProvider', () => {
);
const info: JenkinsInfo = await provider.getInstance({ entityRef });
expect(mockCatalog.getEntityByName).toBeCalledWith(entityRef);
expect(mockCatalog.getEntityByRef).toBeCalledWith(entityRef);
expect(info).toMatchObject({
baseUrl: 'https://jenkins.example.com',
jobFullName: 'teamA/artistLookup-build',
@@ -280,7 +280,7 @@ describe('DefaultJenkinsInfoProvider', () => {
);
const info: JenkinsInfo = await provider.getInstance({ entityRef });
expect(mockCatalog.getEntityByName).toBeCalledWith(entityRef);
expect(mockCatalog.getEntityByRef).toBeCalledWith(entityRef);
expect(info).toMatchObject({
baseUrl: 'https://jenkins.example.com',
jobFullName: 'teamA/artistLookup-build',
@@ -317,7 +317,7 @@ describe('DefaultJenkinsInfoProvider', () => {
);
const info: JenkinsInfo = await provider.getInstance({ entityRef });
expect(mockCatalog.getEntityByName).toBeCalledWith(entityRef);
expect(mockCatalog.getEntityByRef).toBeCalledWith(entityRef);
expect(info).toMatchObject({
baseUrl: 'https://jenkins-other.example.com',
jobFullName: 'teamA/artistLookup-build',
@@ -343,7 +343,7 @@ describe('DefaultJenkinsInfoProvider', () => {
);
const info: JenkinsInfo = await provider.getInstance({ entityRef });
expect(mockCatalog.getEntityByName).toBeCalledWith(entityRef);
expect(mockCatalog.getEntityByRef).toBeCalledWith(entityRef);
expect(info).toMatchObject({
baseUrl: 'https://jenkins.example.com',
jobFullName: 'teamA/artistLookup-build',
@@ -369,7 +369,7 @@ describe('DefaultJenkinsInfoProvider', () => {
);
const info: JenkinsInfo = await provider.getInstance({ entityRef });
expect(mockCatalog.getEntityByName).toBeCalledWith(entityRef);
expect(mockCatalog.getEntityByRef).toBeCalledWith(entityRef);
expect(info).toMatchObject({
baseUrl: 'https://jenkins.example.com',
jobFullName: 'teamA/artistLookup-build',
@@ -400,7 +400,7 @@ describe('DefaultJenkinsInfoProvider', () => {
);
const info: JenkinsInfo = await provider.getInstance({ entityRef });
expect(mockCatalog.getEntityByName).toBeCalledWith(entityRef);
expect(mockCatalog.getEntityByRef).toBeCalledWith(entityRef);
expect(info).toMatchObject({
baseUrl: 'https://jenkins-other.example.com',
jobFullName: 'teamA/artistLookup-build',
@@ -186,7 +186,7 @@ export class DefaultJenkinsInfoProvider implements JenkinsInfoProvider {
jobFullName?: string;
}): Promise<JenkinsInfo> {
// load entity
const entity = await this.catalog.getEntityByName(opt.entityRef);
const entity = await this.catalog.getEntityByRef(opt.entityRef);
if (!entity) {
throw new Error(
`Couldn't find entity with name: ${stringifyEntityRef(opt.entityRef)}`,
@@ -30,7 +30,7 @@ export function useCatalogEntity() {
error,
loading,
} = useAsync(
() => catalogApi.getEntityByName({ kind: 'Component', namespace, name }),
() => catalogApi.getEntityByRef({ kind: 'Component', namespace, name }),
[catalogApi, namespace, name],
);
@@ -22,6 +22,7 @@ import {
ANNOTATION_SOURCE_LOCATION,
CompoundEntityRef,
DEFAULT_NAMESPACE,
stringifyEntityRef,
} from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { assertError, InputError, NotFoundError } from '@backstage/errors';
@@ -106,9 +107,11 @@ export async function findTemplate(options: {
throw new InputError(`Invalid kind, only 'Template' kind is supported`);
}
const template = await catalogApi.getEntityByName(entityRef, { token });
const template = await catalogApi.getEntityByRef(entityRef, { token });
if (!template) {
throw new NotFoundError(`Template ${entityRef} not found`);
throw new NotFoundError(
`Template ${stringifyEntityRef(entityRef)} not found`,
);
}
return template as TemplateEntityV1beta3 | TemplateEntityV1beta2;
@@ -53,7 +53,7 @@ import { stringifyEntityRef } from '@backstage/catalog-model';
const createCatalogClient = (template: any) =>
({
getEntityByName: async () => template,
getEntityByRef: async () => template,
} as unknown as CatalogApi);
function createDatabase(): PluginDatabaseManager {
@@ -20,7 +20,7 @@ import { CompoundEntityRef } from '@backstage/catalog-model';
describe('CachedEntityLoader', () => {
const catalog: jest.Mocked<CatalogClient> = {
getEntityByName: jest.fn(),
getEntityByRef: jest.fn(),
} as any;
const cache: jest.Mocked<CacheClient> = {
@@ -53,7 +53,7 @@ describe('CachedEntityLoader', () => {
it('writes entities to cache', async () => {
cache.get.mockResolvedValue(undefined);
catalog.getEntityByName.mockResolvedValue(entity);
catalog.getEntityByRef.mockResolvedValue(entity);
const result = await loader.load(entityName, token);
@@ -71,12 +71,12 @@ describe('CachedEntityLoader', () => {
const result = await loader.load(entityName, token);
expect(result).toEqual(entity);
expect(catalog.getEntityByName).not.toBeCalled();
expect(catalog.getEntityByRef).not.toBeCalled();
});
it('does not cache missing entites', async () => {
cache.get.mockResolvedValue(undefined);
catalog.getEntityByName.mockResolvedValue(undefined);
catalog.getEntityByRef.mockResolvedValue(undefined);
const result = await loader.load(entityName, token);
@@ -86,7 +86,7 @@ describe('CachedEntityLoader', () => {
it('uses entity ref as cache key for anonymous users', async () => {
cache.get.mockResolvedValue(undefined);
catalog.getEntityByName.mockResolvedValue(entity);
catalog.getEntityByRef.mockResolvedValue(entity);
const result = await loader.load(entityName, undefined);
@@ -103,7 +103,7 @@ describe('CachedEntityLoader', () => {
setTimeout(() => resolve(undefined), 10000);
}),
);
catalog.getEntityByName.mockResolvedValue(entity);
catalog.getEntityByRef.mockResolvedValue(entity);
const result = await loader.load(entityName, token);
@@ -37,17 +37,17 @@ export class CachedEntityLoader {
}
async load(
entityName: CompoundEntityRef,
entityRef: CompoundEntityRef,
token: string | undefined,
): Promise<Entity | undefined> {
const cacheKey = this.getCacheKey(entityName, token);
const cacheKey = this.getCacheKey(entityRef, token);
let result = await this.getFromCache(cacheKey);
if (result) {
return result;
}
result = await this.catalog.getEntityByName(entityName, { token });
result = await this.catalog.getEntityByRef(entityRef, { token });
if (result) {
this.cache.set(cacheKey, result, { ttl: 5000 });
@@ -45,7 +45,7 @@ jest.mock('@backstage/plugin-catalog-react', () => {
});
const mockCatalogApi = {
getEntityByName: () => Promise.resolve(),
getEntityByRef: () => Promise.resolve(),
getEntities: async () => ({
items: [
{
@@ -33,7 +33,7 @@ jest.mock('@backstage/plugin-catalog-react', () => {
});
const mockCatalogApi = {
getEntityByName: jest.fn(),
getEntityByRef: jest.fn(),
getEntities: async () => ({
items: [
{
@@ -31,7 +31,7 @@ jest.mock('@backstage/plugin-catalog-react', () => {
});
const mockCatalogApi = {
getEntityByName: jest.fn(),
getEntityByRef: jest.fn(),
getEntities: async () => ({
items: [
{
@@ -232,7 +232,12 @@ function useOwnUser(): AsyncState<UserEntity | undefined> {
return useAsync(async () => {
const identity = await identityApi.getBackstageIdentity();
return catalogApi.getEntityByName(
// TODO(freben): Defensively parse with defaults even though getEntityByRef
// supports the string form, since some auth resolvers have been known to
// return incomplete refs (just the name part) historically. This can be
// simplified in the future to just pass the ref immediately to
// getEntityByRef.
return catalogApi.getEntityByRef(
parseEntityRef(identity.userEntityRef, {
defaultKind: 'User',
defaultNamespace: DEFAULT_NAMESPACE,
@@ -44,6 +44,7 @@ function mockCatalogClient(entity?: Entity): jest.Mocked<CatalogApi> {
const mock = {
addLocation: jest.fn(),
getEntities: jest.fn(),
getEntityByRef: jest.fn(),
getEntityByName: jest.fn(),
getLocationByRef: jest.fn(),
getLocationById: jest.fn(),
@@ -54,7 +55,7 @@ function mockCatalogClient(entity?: Entity): jest.Mocked<CatalogApi> {
getEntityFacets: jest.fn(),
};
if (entity) {
mock.getEntityByName.mockReturnValue(entity);
mock.getEntityByRef.mockReturnValue(entity);
}
return mock;
}
@@ -93,7 +94,7 @@ describe('TodoReaderService', () => {
offset: 0,
limit: 10,
});
expect(catalogClient.getEntityByName).toHaveBeenCalledWith(entityName, {
expect(catalogClient.getEntityByRef).toHaveBeenCalledWith(entityName, {
token: undefined,
});
});
@@ -304,7 +305,7 @@ describe('TodoReaderService', () => {
message: 'Entity not found, component:default/my-component',
}),
);
expect(catalogClient.getEntityByName).toHaveBeenCalledWith(entityName, {
expect(catalogClient.getEntityByRef).toHaveBeenCalledWith(entityName, {
token: undefined,
});
});
@@ -66,7 +66,7 @@ export class TodoReaderService implements TodoService {
throw new InputError('Entity filter is required to list TODOs');
}
const token = options?.token;
const entity = await this.catalogClient.getEntityByName(req.entity, {
const entity = await this.catalogClient.getEntityByRef(req.entity, {
token,
});
if (!entity) {