diff --git a/.changeset/yellow-cows-tickle.md b/.changeset/yellow-cows-tickle.md new file mode 100644 index 0000000000..026c7444f0 --- /dev/null +++ b/.changeset/yellow-cows-tickle.md @@ -0,0 +1,7 @@ +--- +'@backstage/catalog-client': minor +'@backstage/plugin-catalog-react': minor +'@backstage/plugin-catalog-node': minor +--- + +Add `getLocations` method to `CatalogApi` and `CatalogClient`. This method calls the [`GET /locations`](https://backstage.io/docs/features/software-catalog/software-catalog-api/#get-locations) endpoint from the catalog backend. diff --git a/packages/catalog-client/report-testUtils.api.md b/packages/catalog-client/report-testUtils.api.md index 88d14b4f7a..bb6a8dd469 100644 --- a/packages/catalog-client/report-testUtils.api.md +++ b/packages/catalog-client/report-testUtils.api.md @@ -16,6 +16,7 @@ import { GetEntityAncestorsRequest } from '@backstage/catalog-client'; import { GetEntityAncestorsResponse } from '@backstage/catalog-client'; import { GetEntityFacetsRequest } from '@backstage/catalog-client'; import { GetEntityFacetsResponse } from '@backstage/catalog-client'; +import { GetLocationsResponse } from '@backstage/catalog-client'; import { Location as Location_2 } from '@backstage/catalog-client'; import { QueryEntitiesRequest } from '@backstage/catalog-client'; import { QueryEntitiesResponse } from '@backstage/catalog-client'; @@ -53,6 +54,8 @@ export class InMemoryCatalogClient implements CatalogApi { // (undocumented) getLocationByRef(_locationRef: string): Promise; // (undocumented) + getLocations(_request?: {}): Promise; + // (undocumented) queryEntities(request?: QueryEntitiesRequest): Promise; // (undocumented) refreshEntity(_entityRef: string): Promise; diff --git a/packages/catalog-client/report.api.md b/packages/catalog-client/report.api.md index 203028edce..0a37ed7e42 100644 --- a/packages/catalog-client/report.api.md +++ b/packages/catalog-client/report.api.md @@ -62,6 +62,10 @@ export interface CatalogApi { locationRef: string, options?: CatalogRequestOptions, ): Promise; + getLocations( + request?: {}, + options?: CatalogRequestOptions, + ): Promise; queryEntities( request?: QueryEntitiesRequest, options?: CatalogRequestOptions, @@ -136,6 +140,10 @@ export class CatalogClient implements CatalogApi { locationRef: string, options?: CatalogRequestOptions, ): Promise; + getLocations( + request?: {}, + options?: CatalogRequestOptions, + ): Promise; queryEntities( request?: QueryEntitiesRequest, options?: CatalogRequestOptions, @@ -250,6 +258,12 @@ export interface GetEntityFacetsResponse { >; } +// @public +export interface GetLocationsResponse { + // (undocumented) + items: Location_2[]; +} + // @public type Location_2 = { id: string; diff --git a/packages/catalog-client/src/CatalogClient.test.ts b/packages/catalog-client/src/CatalogClient.test.ts index ea61634a8b..abafeb18a5 100644 --- a/packages/catalog-client/src/CatalogClient.test.ts +++ b/packages/catalog-client/src/CatalogClient.test.ts @@ -24,6 +24,7 @@ import { QueryEntitiesResponse, } from './types/api'; import { DiscoveryApi } from './types/discovery'; +import { GetLocations200ResponseInner } from './schema/openapi'; const server = setupServer(); const token = 'fake-token'; @@ -593,6 +594,88 @@ describe('CatalogClient', () => { }); }); + describe('getLocations', () => { + const defaultResponse = [ + { + data: { + id: '42', + type: 'url', + target: 'https://example.com', + }, + }, + { + data: { + id: '43', + type: 'url', + target: 'https://example.com', + }, + }, + ] satisfies GetLocations200ResponseInner[]; + + beforeEach(() => { + server.use( + rest.get(`${mockBaseUrl}/locations`, (_, res, ctx) => { + return res(ctx.json(defaultResponse)); + }), + ); + }); + + it('should return locations from correct endpoint', async () => { + const response = await client.getLocations({}, { token }); + expect(response).toEqual({ + items: [ + { + id: '42', + type: 'url', + target: 'https://example.com', + }, + { + id: '43', + type: 'url', + target: 'https://example.com', + }, + ], + }); + }); + + it('should return empty list with empty result', async () => { + server.use( + rest.get(`${mockBaseUrl}/locations`, (_, res, ctx) => { + return res(ctx.json([])); + }), + ); + + const response = await client.getLocations({}, { token }); + expect(response).toEqual({ items: [] }); + }); + + it('should forward token', async () => { + expect.assertions(1); + + server.use( + rest.get(`${mockBaseUrl}/locations`, (req, res, ctx) => { + expect(req.headers.get('authorization')).toBe(`Bearer ${token}`); + return res(ctx.json(defaultResponse)); + }), + ); + + await client.getLocations({}, { token }); + }); + + it('should not forward token if omitted', async () => { + expect.assertions(1); + + server.use( + rest.get(`${mockBaseUrl}/locations`, (req, res, ctx) => { + expect(req.headers.get('authorization')).toBeNull(); + return res(ctx.json(defaultResponse)); + }), + ); + + await client.getLocations(); + }); + }); + describe('getLocationById', () => { const defaultResponse = { data: { diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index e820716945..b3f8df1aa6 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -36,6 +36,7 @@ import { GetEntityAncestorsResponse, GetEntityFacetsRequest, GetEntityFacetsResponse, + GetLocationsResponse, Location, QueryEntitiesRequest, QueryEntitiesResponse, @@ -75,6 +76,21 @@ export class CatalogClient implements CatalogApi { ); } + /** + * {@inheritdoc CatalogApi.getLocations} + */ + async getLocations( + request?: {}, + options?: CatalogRequestOptions, + ): Promise { + const res = await this.requestRequired( + await this.apiClient.getLocations(request ?? {}, options), + ); + return { + items: res.map(item => item.data), + }; + } + /** * {@inheritdoc CatalogApi.getLocationById} */ diff --git a/packages/catalog-client/src/testUtils/InMemoryCatalogClient.ts b/packages/catalog-client/src/testUtils/InMemoryCatalogClient.ts index 13ce190e88..a7dd333729 100644 --- a/packages/catalog-client/src/testUtils/InMemoryCatalogClient.ts +++ b/packages/catalog-client/src/testUtils/InMemoryCatalogClient.ts @@ -28,6 +28,7 @@ import { GetEntityAncestorsResponse, GetEntityFacetsRequest, GetEntityFacetsResponse, + GetLocationsResponse, Location, QueryEntitiesRequest, QueryEntitiesResponse, @@ -228,6 +229,10 @@ export class InMemoryCatalogClient implements CatalogApi { }; } + async getLocations(_request?: {}): Promise { + throw new NotImplementedError('Method not implemented.'); + } + async getLocationById(_id: string): Promise { throw new NotImplementedError('Method not implemented.'); } diff --git a/packages/catalog-client/src/types/api.ts b/packages/catalog-client/src/types/api.ts index 85cea17660..c999b29390 100644 --- a/packages/catalog-client/src/types/api.ts +++ b/packages/catalog-client/src/types/api.ts @@ -349,6 +349,15 @@ export type Location = { target: string; }; +/** + * The response type for {@link CatalogClient.getLocations} + * + * @public + */ +export interface GetLocationsResponse { + items: Location[]; +} + /** * The request type for {@link CatalogClient.addLocation}. * @@ -590,6 +599,17 @@ export interface CatalogApi { // Locations + /** + * List locations + * + * @param request - Request parameters + * @param options - Additional options + */ + getLocations( + request?: {}, + options?: CatalogRequestOptions, + ): Promise; + /** * Gets a registered location by its ID. * diff --git a/packages/catalog-client/src/types/index.ts b/packages/catalog-client/src/types/index.ts index 9bf35886c8..73b16f4554 100644 --- a/packages/catalog-client/src/types/index.ts +++ b/packages/catalog-client/src/types/index.ts @@ -31,6 +31,7 @@ export type { GetEntityAncestorsResponse, GetEntityFacetsRequest, GetEntityFacetsResponse, + GetLocationsResponse, Location, ValidateEntityResponse, QueryEntitiesCursorRequest, diff --git a/plugins/catalog-node/report-testUtils.api.md b/plugins/catalog-node/report-testUtils.api.md index 35dee113c0..3098888fcb 100644 --- a/plugins/catalog-node/report-testUtils.api.md +++ b/plugins/catalog-node/report-testUtils.api.md @@ -19,6 +19,7 @@ import { GetEntityAncestorsRequest } from '@backstage/catalog-client'; import { GetEntityAncestorsResponse } from '@backstage/catalog-client'; import { GetEntityFacetsRequest } from '@backstage/catalog-client'; import { GetEntityFacetsResponse } from '@backstage/catalog-client'; +import { GetLocationsResponse } from '@backstage/catalog-client'; import { Location as Location_2 } from '@backstage/catalog-client'; import { QueryEntitiesRequest } from '@backstage/catalog-client'; import { QueryEntitiesResponse } from '@backstage/catalog-client'; @@ -74,6 +75,11 @@ export interface CatalogServiceMock extends CatalogService, CatalogApi { options?: CatalogServiceRequestOptions | CatalogRequestOptions, ): Promise; // (undocumented) + getLocations( + request?: {}, + options?: CatalogServiceRequestOptions | CatalogRequestOptions, + ): Promise; + // (undocumented) queryEntities( request?: QueryEntitiesRequest, options?: CatalogServiceRequestOptions | CatalogRequestOptions, diff --git a/plugins/catalog-node/report.api.md b/plugins/catalog-node/report.api.md index a51212e412..f2fe86a38c 100644 --- a/plugins/catalog-node/report.api.md +++ b/plugins/catalog-node/report.api.md @@ -19,6 +19,7 @@ import { GetEntityAncestorsRequest } from '@backstage/catalog-client'; import { GetEntityAncestorsResponse } from '@backstage/catalog-client'; import { GetEntityFacetsRequest } from '@backstage/catalog-client'; import { GetEntityFacetsResponse } from '@backstage/catalog-client'; +import { GetLocationsResponse } from '@backstage/catalog-client'; import { JsonValue } from '@backstage/types'; import { Location as Location_2 } from '@backstage/catalog-client'; import { LocationEntityV1alpha1 } from '@backstage/catalog-model'; @@ -164,6 +165,11 @@ export interface CatalogService { options: CatalogServiceRequestOptions, ): Promise; // (undocumented) + getLocations( + request: {} | undefined, + options: CatalogServiceRequestOptions, + ): Promise; + // (undocumented) queryEntities( request: QueryEntitiesRequest | undefined, options: CatalogServiceRequestOptions, diff --git a/plugins/catalog-node/src/catalogService.ts b/plugins/catalog-node/src/catalogService.ts index 5e8c920dad..51bb1d92e5 100644 --- a/plugins/catalog-node/src/catalogService.ts +++ b/plugins/catalog-node/src/catalogService.ts @@ -35,6 +35,7 @@ import { GetEntityAncestorsResponse, GetEntityFacetsRequest, GetEntityFacetsResponse, + GetLocationsResponse, Location, QueryEntitiesRequest, QueryEntitiesResponse, @@ -96,6 +97,11 @@ export interface CatalogService { options: CatalogServiceRequestOptions, ): Promise; + getLocations( + request: {} | undefined, + options: CatalogServiceRequestOptions, + ): Promise; + getLocationById( id: string, options: CatalogServiceRequestOptions, @@ -223,6 +229,16 @@ class DefaultCatalogService implements CatalogService { ); } + async getLocations( + request: {} | undefined, + options: CatalogServiceRequestOptions, + ): Promise { + return this.#catalogApi.getLocations( + request, + await this.#getOptions(options), + ); + } + async getLocationById( id: string, options: CatalogServiceRequestOptions, diff --git a/plugins/catalog-node/src/testUtils/catalogServiceMock.ts b/plugins/catalog-node/src/testUtils/catalogServiceMock.ts index 2e8a772dc3..ec469fc927 100644 --- a/plugins/catalog-node/src/testUtils/catalogServiceMock.ts +++ b/plugins/catalog-node/src/testUtils/catalogServiceMock.ts @@ -95,6 +95,7 @@ export namespace catalogServiceMock { removeEntityByUid: jest.fn(), refreshEntity: jest.fn(), getEntityFacets: jest.fn(), + getLocations: jest.fn(), getLocationById: jest.fn(), getLocationByRef: jest.fn(), addLocation: jest.fn(), diff --git a/plugins/catalog-node/src/testUtils/types.ts b/plugins/catalog-node/src/testUtils/types.ts index 1fbbec98f1..6e6331a630 100644 --- a/plugins/catalog-node/src/testUtils/types.ts +++ b/plugins/catalog-node/src/testUtils/types.ts @@ -27,6 +27,7 @@ import { GetEntityAncestorsResponse, GetEntityFacetsRequest, GetEntityFacetsResponse, + GetLocationsResponse, Location, QueryEntitiesRequest, QueryEntitiesResponse, @@ -90,6 +91,11 @@ export interface CatalogServiceMock extends CatalogService, CatalogApi { options?: CatalogServiceRequestOptions | CatalogRequestOptions, ): Promise; + getLocations( + request?: {}, + options?: CatalogServiceRequestOptions | CatalogRequestOptions, + ): Promise; + getLocationById( id: string, options?: CatalogServiceRequestOptions | CatalogRequestOptions, diff --git a/plugins/catalog-react/src/testUtils/catalogApiMock.ts b/plugins/catalog-react/src/testUtils/catalogApiMock.ts index 8cf3b740b0..08520e2a9b 100644 --- a/plugins/catalog-react/src/testUtils/catalogApiMock.ts +++ b/plugins/catalog-react/src/testUtils/catalogApiMock.ts @@ -94,6 +94,7 @@ export namespace catalogApiMock { removeEntityByUid: jest.fn(), refreshEntity: jest.fn(), getEntityFacets: jest.fn(), + getLocations: jest.fn(), getLocationById: jest.fn(), getLocationByRef: jest.fn(), addLocation: jest.fn(),