diff --git a/.changeset/five-feet-call.md b/.changeset/five-feet-call.md new file mode 100644 index 0000000000..528c21a932 --- /dev/null +++ b/.changeset/five-feet-call.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Update the OpenAPI spec to support the use of `openapi-generator`. diff --git a/.changeset/forty-clocks-hug.md b/.changeset/forty-clocks-hug.md new file mode 100644 index 0000000000..ea0fd2480f --- /dev/null +++ b/.changeset/forty-clocks-hug.md @@ -0,0 +1,5 @@ +--- +'@backstage/catalog-client': minor +--- + +The internals of `CatalogClient` are now auto-generated using the `backstage-repo-tools schema openapi generate-client` command. diff --git a/.changeset/proud-seahorses-explain.md b/.changeset/proud-seahorses-explain.md new file mode 100644 index 0000000000..328e4bf838 --- /dev/null +++ b/.changeset/proud-seahorses-explain.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': minor +--- + +Updates the ESLint config to ignore issues created by generated files in `**/src/generated/**`. diff --git a/.changeset/silent-rats-reflect.md b/.changeset/silent-rats-reflect.md new file mode 100644 index 0000000000..b7884f06d0 --- /dev/null +++ b/.changeset/silent-rats-reflect.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': minor +--- + +Adds a new command `schema openapi generate-client` that creates a Typescript client with Backstage flavor, including the discovery API and fetch API. This command doesn't currently generate a complete client and needs to be wrapped or exported manually by a separate Backstage plugin. See `@backstage/catalog-client/src/generated` for example output. diff --git a/docs/openapi/generate-client.md b/docs/openapi/generate-client.md new file mode 100644 index 0000000000..10e6ec3b2f --- /dev/null +++ b/docs/openapi/generate-client.md @@ -0,0 +1,25 @@ +## How to generate a client with `repo-tools schema openapi generate-client`? + +### Prerequisites + +1. Add your plugin ID as the last `servers` item, like this, + +```yaml +servers: + # first value, used for OpenAPI router validation. + - url: / + + # final value, pluginId. + - url: catalog +``` + +2. Find or create a new plugin to house your new generated client. Currently, we do not support generating an entirely new plugin and instead just generate client files. + +### Generating your client + +1. Run `yarn backstage-repo-tools schema openapi generate-client --input-spec --output-directory `. This will create a new folder in `/src/generated` to house the generated content. +2. You should use the generated files as follows, + +- `apis/DefaultApi.client.ts` - this is the client that you should use. It has types for all of the various operations on your API. +- `models/*` - These are the types generated from your OpenAPI file, ideally you should not need to use these directly and can instead use the inferred types from `apis/DefaultApi.client.ts`. +- everything else is directory specific and shouldn't be touched. diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 02c30ee19b..4f545262c3 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -35,7 +35,8 @@ "dependencies": { "@backstage/catalog-model": "workspace:^", "@backstage/errors": "workspace:^", - "cross-fetch": "^4.0.0" + "cross-fetch": "^4.0.0", + "uri-template": "^2.0.0" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/packages/catalog-client/src/CatalogClient.test.ts b/packages/catalog-client/src/CatalogClient.test.ts index f5c221569d..4892017032 100644 --- a/packages/catalog-client/src/CatalogClient.test.ts +++ b/packages/catalog-client/src/CatalogClient.test.ts @@ -86,7 +86,7 @@ describe('CatalogClient', () => { server.use( rest.get(`${mockBaseUrl}/entities`, (req, res, ctx) => { - expect(req.url.search).toBe( + expect(decodeURIComponent(req.url.search)).toBe( '?filter=a=1,b=2,b=3,%C3%B6=%3D&filter=a=2&filter=c', ); return res(ctx.json([])); @@ -120,7 +120,9 @@ describe('CatalogClient', () => { server.use( rest.get(`${mockBaseUrl}/entities`, (req, res, ctx) => { - expect(req.url.search).toBe('?filter=a=1,b=2,b=3,%C3%B6=%3D,c'); + expect(decodeURIComponent(req.url.search)).toBe( + '?filter=a=1,b=2,b=3,%C3%B6=%3D,c', + ); return res(ctx.json([])); }), ); @@ -145,7 +147,7 @@ describe('CatalogClient', () => { server.use( rest.get(`${mockBaseUrl}/entities`, (req, res, ctx) => { - expect(req.url.search).toBe('?fields=a.b,%C3%B6'); + expect(decodeURIComponent(req.url.search)).toBe('?fields=a.b,%C3%B6'); return res(ctx.json([])); }), ); @@ -185,7 +187,7 @@ describe('CatalogClient', () => { server.use( rest.get(`${mockBaseUrl}/entities`, (req, res, ctx) => { - expect(req.url.search).toBe('?offset=1&limit=2&after=%3D'); + expect(req.url.search).toBe('?limit=2&offset=1&after=%3D'); return res(ctx.json([])); }), ); @@ -203,7 +205,7 @@ describe('CatalogClient', () => { server.use( rest.get(`${mockBaseUrl}/entities`, (req, res, ctx) => { - expect(req.url.search).toBe( + expect(decodeURIComponent(req.url.search)).toBe( '?order=asc:kind&order=desc:metadata.name', ); return res(ctx.json([])); @@ -326,9 +328,9 @@ describe('CatalogClient', () => { expect(response).toEqual({ items: [], totalItems: 0 }); expect(mockedEndpoint).toHaveBeenCalledTimes(1); - expect(mockedEndpoint.mock.calls[0][0].url.search).toBe( - '?filter=a=1,b=2,b=3,%C3%B6=%3D&filter=a=2&filter=c', - ); + expect( + decodeURIComponent(mockedEndpoint.mock.calls[0][0].url.search), + ).toBe('?filter=a=1,b=2,b=3,%C3%B6=%3D&filter=a=2&filter=c'); }); it('should send query params correctly on initial request', async () => { @@ -351,8 +353,10 @@ describe('CatalogClient', () => { { field: 'metadata.uid', order: 'desc' }, ], }); - expect(mockedEndpoint.mock.calls[0][0].url.search).toBe( - '?limit=100&orderField=metadata.name,asc&orderField=metadata.uid,desc&fields=a,b&fullTextFilterTerm=query', + expect( + decodeURIComponent(mockedEndpoint.mock.calls[0][0].url.search), + ).toBe( + '?fields=a,b&limit=100&orderField=metadata.name%2Casc&orderField=metadata.uid%2Cdesc&fullTextFilterTerm=query', ); }); @@ -375,7 +379,7 @@ describe('CatalogClient', () => { cursor: 'cursor', }); expect(mockedEndpoint.mock.calls[0][0].url.search).toBe( - '?cursor=cursor&limit=100&fields=a,b', + '?fields=a,b&limit=100&cursor=cursor', ); }); diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index c388519e05..511bf45c51 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -22,7 +22,6 @@ import { stringifyLocationRef, } from '@backstage/catalog-model'; import { ResponseError } from '@backstage/errors'; -import crossFetch from 'cross-fetch'; import { CATALOG_FILTER_EXISTS, AddLocationRequest, @@ -40,12 +39,11 @@ import { GetEntitiesByRefsRequest, GetEntitiesByRefsResponse, QueryEntitiesRequest, - QueryEntitiesResponse, EntityFilterQuery, + QueryEntitiesResponse, } from './types/api'; -import { DiscoveryApi } from './types/discovery'; -import { FetchApi } from './types/fetch'; import { isQueryEntitiesInitialRequest } from './utils'; +import { DefaultApiClient } from './generated'; /** * A frontend and backend compatible client for communicating with the Backstage @@ -54,15 +52,13 @@ import { isQueryEntitiesInitialRequest } from './utils'; * @public */ export class CatalogClient implements CatalogApi { - private readonly discoveryApi: DiscoveryApi; - private readonly fetchApi: FetchApi; + private readonly apiClient: DefaultApiClient; constructor(options: { discoveryApi: { getBaseUrl(pluginId: string): Promise }; fetchApi?: { fetch: typeof fetch }; }) { - this.discoveryApi = options.discoveryApi; - this.fetchApi = options.fetchApi || { fetch: crossFetch }; + this.apiClient = new DefaultApiClient(options); } /** @@ -72,13 +68,11 @@ export class CatalogClient implements CatalogApi { request: GetEntityAncestorsRequest, options?: CatalogRequestOptions, ): Promise { - const { kind, namespace, name } = parseEntityRef(request.entityRef); return await this.requestRequired( - 'GET', - `/entities/by-name/${encodeURIComponent(kind)}/${encodeURIComponent( - namespace, - )}/${encodeURIComponent(name)}/ancestry`, - options, + await this.apiClient.getEntityAncestryByName( + { path: parseEntityRef(request.entityRef) }, + options, + ), ); } @@ -90,9 +84,7 @@ export class CatalogClient implements CatalogApi { options?: CatalogRequestOptions, ): Promise { return await this.requestOptional( - 'GET', - `/locations/${encodeURIComponent(id)}`, - options, + await this.apiClient.getLocation({ path: { id } }, options), ); } @@ -111,17 +103,12 @@ export class CatalogClient implements CatalogApi { limit, after, } = request ?? {}; - const params = this.getParams(filter); - - if (fields.length) { - params.push(`fields=${fields.map(encodeURIComponent).join(',')}`); - } - + const encodedOrder = []; if (order) { for (const directive of [order].flat()) { if (directive) { - params.push( - `order=${encodeURIComponent(directive.order)}:${encodeURIComponent( + encodedOrder.push( + `${encodeURIComponent(directive.order)}:${encodeURIComponent( directive.field, )}`, ); @@ -129,21 +116,20 @@ export class CatalogClient implements CatalogApi { } } - if (offset !== undefined) { - params.push(`offset=${offset}`); - } - if (limit !== undefined) { - params.push(`limit=${limit}`); - } - if (after !== undefined) { - params.push(`after=${encodeURIComponent(after)}`); - } - - const query = params.length ? `?${params.join('&')}` : ''; const entities: Entity[] = await this.requestRequired( - 'GET', - `/entities${query}`, - options, + await this.apiClient.getEntities( + { + query: { + fields: fields.map(encodeURIComponent), + limit, + filter: this.getFilterValue(filter), + offset, + after, + order: order ? encodedOrder : undefined, + }, + }, + options, + ), ); const refCompare = (a: Entity, b: Entity) => { @@ -178,22 +164,12 @@ export class CatalogClient implements CatalogApi { request: GetEntitiesByRefsRequest, options?: CatalogRequestOptions, ): Promise { - const body: any = { entityRefs: request.entityRefs }; - if (request.fields?.length) { - body.fields = request.fields; - } - - const baseUrl = await this.discoveryApi.getBaseUrl('catalog'); - const url = `${baseUrl}/entities/by-refs`; - - const response = await this.fetchApi.fetch(url, { - headers: { - 'Content-Type': 'application/json', - ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + const response = await this.apiClient.getEntitiesByRefs( + { + body: request, }, - method: 'POST', - body: JSON.stringify(body), - }); + options, + ); if (!response.ok) { throw await ResponseError.fromResponse(response); @@ -212,8 +188,10 @@ export class CatalogClient implements CatalogApi { async queryEntities( request: QueryEntitiesRequest = {}, options?: CatalogRequestOptions, - ) { - const params: string[] = []; + ): Promise { + const params: Partial< + Parameters[0]['query'] + > = {}; if (isQueryEntitiesInitialRequest(request)) { const { @@ -223,45 +201,42 @@ export class CatalogClient implements CatalogApi { orderFields, fullTextFilter, } = request; - params.push(...this.getParams(filter)); + params.filter = this.getFilterValue(filter); if (limit !== undefined) { - params.push(`limit=${limit}`); + params.limit = limit; } if (orderFields !== undefined) { - (Array.isArray(orderFields) ? orderFields : [orderFields]).forEach( - ({ field, order }) => params.push(`orderField=${field},${order}`), - ); + params.orderField = ( + Array.isArray(orderFields) ? orderFields : [orderFields] + ).map(({ field, order }) => encodeURIComponent(`${field},${order}`)); } if (fields.length) { - params.push(`fields=${fields.map(encodeURIComponent).join(',')}`); + params.fields = fields.map(encodeURIComponent); } const normalizedFullTextFilterTerm = fullTextFilter?.term?.trim(); if (normalizedFullTextFilterTerm) { - params.push(`fullTextFilterTerm=${normalizedFullTextFilterTerm}`); + params.fullTextFilterTerm = normalizedFullTextFilterTerm; } if (fullTextFilter?.fields?.length) { - params.push(`fullTextFilterFields=${fullTextFilter.fields.join(',')}`); + params.fullTextFilterFields = fullTextFilter.fields; } } else { const { fields = [], limit, cursor } = request; - params.push(`cursor=${cursor}`); + params.cursor = cursor; if (limit !== undefined) { - params.push(`limit=${limit}`); + params.limit = limit; } if (fields.length) { - params.push(`fields=${fields.map(encodeURIComponent).join(',')}`); + params.fields = fields.map(encodeURIComponent); } } - const query = params.length ? `?${params.join('&')}` : ''; - return this.requestRequired( - 'GET', - `/entities/by-query${query}`, - options, - ); + return this.apiClient + .getEntitiesByQuery({ query: params }, options) + .then(r => r.json()); } /** @@ -271,13 +246,13 @@ export class CatalogClient implements CatalogApi { entityRef: string | CompoundEntityRef, options?: CatalogRequestOptions, ): Promise { - const { kind, namespace, name } = parseEntityRef(entityRef); return this.requestOptional( - 'GET', - `/entities/by-name/${encodeURIComponent(kind)}/${encodeURIComponent( - namespace, - )}/${encodeURIComponent(name)}`, - options, + await this.apiClient.getEntityByName( + { + path: parseEntityRef(entityRef), + }, + options, + ), ); } @@ -294,11 +269,10 @@ export class CatalogClient implements CatalogApi { ): Promise { const { kind, namespace = 'default', name } = compoundName; return this.requestOptional( - 'GET', - `/entities/by-name/${encodeURIComponent(kind)}/${encodeURIComponent( - namespace, - )}/${encodeURIComponent(name)}`, - options, + await this.apiClient.getEntityByName( + { path: { kind, namespace, name } }, + options, + ), ); } @@ -306,16 +280,9 @@ export class CatalogClient implements CatalogApi { * {@inheritdoc CatalogApi.refreshEntity} */ async refreshEntity(entityRef: string, options?: CatalogRequestOptions) { - const response = await this.fetchApi.fetch( - `${await this.discoveryApi.getBaseUrl('catalog')}/refresh`, - { - headers: { - 'Content-Type': 'application/json', - ...(options?.token && { Authorization: `Bearer ${options?.token}` }), - }, - method: 'POST', - body: JSON.stringify({ entityRef }), - }, + const response = await this.apiClient.refreshEntity( + { body: { entityRef } }, + options, ); if (response.status !== 200) { @@ -331,14 +298,14 @@ export class CatalogClient implements CatalogApi { options?: CatalogRequestOptions, ): Promise { const { filter = [], facets } = request; - const params = this.getParams(filter); - - for (const facet of facets) { - params.push(`facet=${encodeURIComponent(facet)}`); - } - - const query = params.length ? `?${params.join('&')}` : ''; - return await this.requestOptional('GET', `/entity-facets${query}`, options); + return await this.requestOptional( + await this.apiClient.getEntityFacets( + { + query: { facet: facets, filter: this.getFilterValue(filter) }, + }, + options, + ), + ); } /** @@ -350,18 +317,12 @@ export class CatalogClient implements CatalogApi { ): Promise { const { type = 'url', target, dryRun } = request; - const response = await this.fetchApi.fetch( - `${await this.discoveryApi.getBaseUrl('catalog')}/locations${ - dryRun ? '?dryRun=true' : '' - }`, + const response = await this.apiClient.createLocation( { - headers: { - 'Content-Type': 'application/json', - ...(options?.token && { Authorization: `Bearer ${options?.token}` }), - }, - method: 'POST', - body: JSON.stringify({ type, target }), + body: { type, target }, + query: { dryRun: dryRun ? 'true' : undefined }, }, + options, ); if (response.status !== 201) { @@ -389,9 +350,7 @@ export class CatalogClient implements CatalogApi { options?: CatalogRequestOptions, ): Promise { const all: { data: Location }[] = await this.requestRequired( - 'GET', - '/locations', - options, + await this.apiClient.getLocation({ path: { id: locationRef } }, options), ); return all .map(r => r.data) @@ -406,9 +365,7 @@ export class CatalogClient implements CatalogApi { options?: CatalogRequestOptions, ): Promise { await this.requestIgnored( - 'DELETE', - `/locations/${encodeURIComponent(id)}`, - options, + await this.apiClient.deleteLocation({ path: { id } }, options), ); } @@ -420,9 +377,7 @@ export class CatalogClient implements CatalogApi { options?: CatalogRequestOptions, ): Promise { await this.requestIgnored( - 'DELETE', - `/entities/by-uid/${encodeURIComponent(uid)}`, - options, + await this.apiClient.deleteEntityByUid({ path: { uid } }, options), ); } @@ -434,16 +389,9 @@ export class CatalogClient implements CatalogApi { locationRef: string, options?: CatalogRequestOptions, ): Promise { - const response = await this.fetchApi.fetch( - `${await this.discoveryApi.getBaseUrl('catalog')}/validate-entity`, - { - headers: { - 'Content-Type': 'application/json', - ...(options?.token && { Authorization: `Bearer ${options?.token}` }), - }, - method: 'POST', - body: JSON.stringify({ entity, location: locationRef }), - }, + const response = await this.apiClient.validateEntity( + { body: { entity, location: locationRef } }, + options, ); if (response.ok) { @@ -456,7 +404,7 @@ export class CatalogClient implements CatalogApi { throw await ResponseError.fromResponse(response); } - const { errors = [] } = await response.json(); + const { errors = [] } = (await response.json()) as any; return { valid: false, @@ -468,33 +416,13 @@ export class CatalogClient implements CatalogApi { // Private methods // - private async requestIgnored( - method: string, - path: string, - options?: CatalogRequestOptions, - ): Promise { - const url = `${await this.discoveryApi.getBaseUrl('catalog')}${path}`; - const headers: Record = options?.token - ? { Authorization: `Bearer ${options.token}` } - : {}; - const response = await this.fetchApi.fetch(url, { method, headers }); - + private async requestIgnored(response: Response): Promise { if (!response.ok) { throw await ResponseError.fromResponse(response); } } - private async requestRequired( - method: string, - path: string, - options?: CatalogRequestOptions, - ): Promise { - const url = `${await this.discoveryApi.getBaseUrl('catalog')}${path}`; - const headers: Record = options?.token - ? { Authorization: `Bearer ${options.token}` } - : {}; - const response = await this.fetchApi.fetch(url, { method, headers }); - + private async requestRequired(response: Response): Promise { if (!response.ok) { throw await ResponseError.fromResponse(response); } @@ -502,17 +430,7 @@ export class CatalogClient implements CatalogApi { return response.json(); } - private async requestOptional( - method: string, - path: string, - options?: CatalogRequestOptions, - ): Promise { - const url = `${await this.discoveryApi.getBaseUrl('catalog')}${path}`; - const headers: Record = options?.token - ? { Authorization: `Bearer ${options.token}` } - : {}; - const response = await this.fetchApi.fetch(url, { method, headers }); - + private async requestOptional(response: Response): Promise { if (!response.ok) { if (response.status === 404) { return undefined; @@ -523,8 +441,8 @@ export class CatalogClient implements CatalogApi { return await response.json(); } - private getParams(filter: EntityFilterQuery = []) { - const params: string[] = []; + private getFilterValue(filter: EntityFilterQuery = []) { + const filters: string[] = []; // filter param can occur multiple times, for example // /api/catalog/entities?filter=metadata.name=wayback-search,kind=component&filter=metadata.name=www-artist,kind=component' // the "outer array" defined by `filter` occurrences corresponds to "anyOf" filters @@ -544,9 +462,9 @@ export class CatalogClient implements CatalogApi { } if (filterParts.length) { - params.push(`filter=${filterParts.join(',')}`); + filters.push(filterParts.join(',')); } } - return params; + return filters; } } diff --git a/packages/catalog-client/src/generated/apis/DefaultApi.client.ts b/packages/catalog-client/src/generated/apis/DefaultApi.client.ts new file mode 100644 index 0000000000..260f7c9fc8 --- /dev/null +++ b/packages/catalog-client/src/generated/apis/DefaultApi.client.ts @@ -0,0 +1,535 @@ +/* + * 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 { DiscoveryApi } from '../types/discovery'; +import { FetchApi } from '../types/fetch'; +import crossFetch from 'cross-fetch'; +import { pluginId } from '../pluginId'; +import * as parser from 'uri-template'; + +import { AnalyzeLocationRequest } from '../models/AnalyzeLocationRequest.model'; +import { AnalyzeLocationResponse } from '../models/AnalyzeLocationResponse.model'; +import { CreateLocation201Response } from '../models/CreateLocation201Response.model'; +import { CreateLocationRequest } from '../models/CreateLocationRequest.model'; +import { EntitiesBatchResponse } from '../models/EntitiesBatchResponse.model'; +import { EntitiesQueryResponse } from '../models/EntitiesQueryResponse.model'; +import { Entity } from '../models/Entity.model'; +import { EntityAncestryResponse } from '../models/EntityAncestryResponse.model'; +import { EntityFacetsResponse } from '../models/EntityFacetsResponse.model'; +import { GetEntitiesByRefsRequest } from '../models/GetEntitiesByRefsRequest.model'; +import { GetLocations200ResponseInner } from '../models/GetLocations200ResponseInner.model'; +import { Location } from '../models/Location.model'; +import { RefreshEntityRequest } from '../models/RefreshEntityRequest.model'; +import { ValidateEntityRequest } from '../models/ValidateEntityRequest.model'; + +type TypedResponse = Omit & { + json: () => Promise; +}; + +/** + * Options you can pass into a request for additional information. + * + * @public + */ +export interface RequestOptions { + token?: string; +} + +/** + * no description + */ +export class DefaultApiClient { + private readonly discoveryApi: DiscoveryApi; + private readonly fetchApi: FetchApi; + + constructor(options: { + discoveryApi: { getBaseUrl(pluginId: string): Promise }; + fetchApi?: { fetch: typeof fetch }; + }) { + this.discoveryApi = options.discoveryApi; + this.fetchApi = options.fetchApi || { fetch: crossFetch }; + } + + /** + * Validate a given location. + * @param analyzeLocationRequest + */ + public async analyzeLocation( + // @ts-ignore + request: { + body: AnalyzeLocationRequest; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/analyze-location`; + + const uri = parser.parse(uriTemplate).expand({}); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'POST', + body: JSON.stringify(request.body), + }); + } + + /** + * Create a location for a given target. + * @param createLocationRequest + * @param dryRun + */ + public async createLocation( + // @ts-ignore + request: { + body: CreateLocationRequest; + query: { + dryRun?: string; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/locations{?dryRun}`; + + const uri = parser.parse(uriTemplate).expand({ + ...request.query, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'POST', + body: JSON.stringify(request.body), + }); + } + + /** + * Delete a single entity by UID. + * @param uid + */ + public async deleteEntityByUid( + // @ts-ignore + request: { + path: { + uid: string; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/entities/by-uid/{uid}`; + + const uri = parser.parse(uriTemplate).expand({ + uid: request.path.uid, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'DELETE', + }); + } + + /** + * Delete a location by id. + * @param id + */ + public async deleteLocation( + // @ts-ignore + request: { + path: { + id: string; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/locations/{id}`; + + const uri = parser.parse(uriTemplate).expand({ + id: request.path.id, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'DELETE', + }); + } + + /** + * Get all entities matching a given filter. + * @param fields Restrict to just these fields in the response. + * @param limit Number of records to return in the response. + * @param filter Filter for just the entities defined by this filter. + * @param offset Number of records to skip in the query page. + * @param after Pointer to the previous page of results. + * @param order + */ + public async getEntities( + // @ts-ignore + request: { + query: { + fields?: Array; + limit?: number; + filter?: Array; + offset?: number; + after?: string; + order?: Array; + }; + }, + options?: RequestOptions, + ): Promise>> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/entities{?fields,limit,filter*,offset,after,order*}`; + + const uri = parser.parse(uriTemplate).expand({ + ...request.query, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Search for entities by a given query. + * @param fields Restrict to just these fields in the response. + * @param limit Number of records to return in the response. + * @param orderField The fields to sort returned results by. + * @param cursor Cursor to a set page of results. + * @param filter Filter for just the entities defined by this filter. + * @param fullTextFilterTerm Text search term. + * @param fullTextFilterFields A comma separated list of fields to sort returned results by. + */ + public async getEntitiesByQuery( + // @ts-ignore + request: { + query: { + fields?: Array; + limit?: number; + orderField?: Array; + cursor?: string; + filter?: Array; + fullTextFilterTerm?: string; + fullTextFilterFields?: Array; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/entities/by-query{?fields,limit,orderField*,cursor,filter*,fullTextFilterTerm,fullTextFilterFields}`; + + const uri = parser.parse(uriTemplate).expand({ + ...request.query, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Get a batch set of entities given an array of entityRefs. + * @param getEntitiesByRefsRequest + */ + public async getEntitiesByRefs( + // @ts-ignore + request: { + body: GetEntitiesByRefsRequest; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/entities/by-refs`; + + const uri = parser.parse(uriTemplate).expand({}); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'POST', + body: JSON.stringify(request.body), + }); + } + + /** + * Get an entity's ancestry by entity ref. + * @param kind + * @param namespace + * @param name + */ + public async getEntityAncestryByName( + // @ts-ignore + request: { + path: { + kind: string; + namespace: string; + name: string; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/entities/by-name/{kind}/{namespace}/{name}/ancestry`; + + const uri = parser.parse(uriTemplate).expand({ + kind: request.path.kind, + namespace: request.path.namespace, + name: request.path.name, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Get an entity by an entity ref. + * @param kind + * @param namespace + * @param name + */ + public async getEntityByName( + // @ts-ignore + request: { + path: { + kind: string; + namespace: string; + name: string; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/entities/by-name/{kind}/{namespace}/{name}`; + + const uri = parser.parse(uriTemplate).expand({ + kind: request.path.kind, + namespace: request.path.namespace, + name: request.path.name, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Get a single entity by the UID. + * @param uid + */ + public async getEntityByUid( + // @ts-ignore + request: { + path: { + uid: string; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/entities/by-uid/{uid}`; + + const uri = parser.parse(uriTemplate).expand({ + uid: request.path.uid, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Get all entity facets that match the given filters. + * @param facet + * @param filter Filter for just the entities defined by this filter. + */ + public async getEntityFacets( + // @ts-ignore + request: { + query: { + facet: Array; + filter?: Array; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/entity-facets{?facet*,filter*}`; + + const uri = parser.parse(uriTemplate).expand({ + ...request.query, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Get a location by id. + * @param id + */ + public async getLocation( + // @ts-ignore + request: { + path: { + id: string; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/locations/{id}`; + + const uri = parser.parse(uriTemplate).expand({ + id: request.path.id, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Get all locations + */ + public async getLocations( + // @ts-ignore + request: {}, + options?: RequestOptions, + ): Promise>> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/locations`; + + const uri = parser.parse(uriTemplate).expand({}); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Refresh the entity related to entityRef. + * @param refreshEntityRequest + */ + public async refreshEntity( + // @ts-ignore + request: { + body: RefreshEntityRequest; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/refresh`; + + const uri = parser.parse(uriTemplate).expand({}); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'POST', + body: JSON.stringify(request.body), + }); + } + + /** + * Validate that a passed in entity has no errors in schema. + * @param validateEntityRequest + */ + public async validateEntity( + // @ts-ignore + request: { + body: ValidateEntityRequest; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/validate-entity`; + + const uri = parser.parse(uriTemplate).expand({}); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'POST', + body: JSON.stringify(request.body), + }); + } +} diff --git a/packages/catalog-client/src/generated/apis/index.ts b/packages/catalog-client/src/generated/apis/index.ts new file mode 100644 index 0000000000..ad2f72c5b2 --- /dev/null +++ b/packages/catalog-client/src/generated/apis/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export * from './DefaultApi.client'; diff --git a/packages/catalog-client/src/generated/index.ts b/packages/catalog-client/src/generated/index.ts new file mode 100644 index 0000000000..7df0cfd292 --- /dev/null +++ b/packages/catalog-client/src/generated/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export * from './apis'; diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationEntityField.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationEntityField.model.ts new file mode 100644 index 0000000000..a1c1e1f669 --- /dev/null +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationEntityField.model.ts @@ -0,0 +1,36 @@ +/* + * 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. + */ + +export interface AnalyzeLocationEntityField { + /** + * A text to show to the user to inform about the choices made. Like, it could say \"Found a CODEOWNERS file that covers this target, so we suggest leaving this field empty; which would currently make it owned by X\" where X is taken from the codeowners file. + */ + description: string; + value: string | null; + /** + * The outcome of the analysis for this particular field + */ + state: AnalyzeLocationEntityFieldStateEnum; + /** + * e.g. \"spec.owner\"? The frontend needs to know how to \"inject\" the field into the entity again if the user wants to change it + */ + field: string; +} + +export type AnalyzeLocationEntityFieldStateEnum = + | 'analysisSuggestedValue' + | 'analysisSuggestedNoValue' + | 'needsUserInput'; diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationExistingEntity.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationExistingEntity.model.ts new file mode 100644 index 0000000000..5ede0fb60b --- /dev/null +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationExistingEntity.model.ts @@ -0,0 +1,27 @@ +/* + * 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 { Entity } from '../models/Entity.model'; +import { LocationSpec } from '../models/LocationSpec.model'; + +/** + * If the folder pointed to already contained catalog info yaml files, they are read and emitted like this so that the frontend can inform the user that it located them and can make sure to register them as well if they weren't already + */ +export interface AnalyzeLocationExistingEntity { + entity: Entity; + isRegistered: boolean; + location: LocationSpec; +} diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationGenerateEntity.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationGenerateEntity.model.ts new file mode 100644 index 0000000000..886aac05d9 --- /dev/null +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationGenerateEntity.model.ts @@ -0,0 +1,26 @@ +/* + * 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 { AnalyzeLocationEntityField } from '../models/AnalyzeLocationEntityField.model'; +import { RecursivePartialEntity } from '../models/RecursivePartialEntity.model'; + +/** + * This is some form of representation of what the analyzer could deduce. We should probably have a chat about how this can best be conveyed to the frontend. It'll probably contain a (possibly incomplete) entity, plus enough info for the frontend to know what form data to show to the user for overriding/completing the info. + */ +export interface AnalyzeLocationGenerateEntity { + fields: Array; + entity: RecursivePartialEntity; +} diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationRequest.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationRequest.model.ts new file mode 100644 index 0000000000..e8e9542f5a --- /dev/null +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationRequest.model.ts @@ -0,0 +1,22 @@ +/* + * 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 { LocationInput } from '../models/LocationInput.model'; + +export interface AnalyzeLocationRequest { + catalogFileName?: string; + location: LocationInput; +} diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationResponse.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationResponse.model.ts new file mode 100644 index 0000000000..bf60aa8018 --- /dev/null +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationResponse.model.ts @@ -0,0 +1,23 @@ +/* + * 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 { AnalyzeLocationExistingEntity } from '../models/AnalyzeLocationExistingEntity.model'; +import { AnalyzeLocationGenerateEntity } from '../models/AnalyzeLocationGenerateEntity.model'; + +export interface AnalyzeLocationResponse { + generateEntities: Array; + existingEntityFiles: Array; +} diff --git a/packages/catalog-client/src/generated/models/CreateLocation201Response.model.ts b/packages/catalog-client/src/generated/models/CreateLocation201Response.model.ts new file mode 100644 index 0000000000..d090d80bf2 --- /dev/null +++ b/packages/catalog-client/src/generated/models/CreateLocation201Response.model.ts @@ -0,0 +1,24 @@ +/* + * 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 { Entity } from '../models/Entity.model'; +import { Location } from '../models/Location.model'; + +export interface CreateLocation201Response { + exists?: boolean; + entities: Array; + location: Location; +} diff --git a/packages/catalog-client/src/generated/models/CreateLocationRequest.model.ts b/packages/catalog-client/src/generated/models/CreateLocationRequest.model.ts new file mode 100644 index 0000000000..6ef950072e --- /dev/null +++ b/packages/catalog-client/src/generated/models/CreateLocationRequest.model.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export interface CreateLocationRequest { + target: string; + type: string; +} diff --git a/packages/catalog-client/src/generated/models/EntitiesBatchResponse.model.ts b/packages/catalog-client/src/generated/models/EntitiesBatchResponse.model.ts new file mode 100644 index 0000000000..d09b2cc086 --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntitiesBatchResponse.model.ts @@ -0,0 +1,24 @@ +/* + * 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 { NullableEntity } from '../models/NullableEntity.model'; + +export interface EntitiesBatchResponse { + /** + * The list of entities, in the same order as the refs in the request. Entries that are null signify that no entity existed with that ref. + */ + items: Array; +} diff --git a/packages/catalog-client/src/generated/models/EntitiesQueryResponse.model.ts b/packages/catalog-client/src/generated/models/EntitiesQueryResponse.model.ts new file mode 100644 index 0000000000..10d4747caf --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntitiesQueryResponse.model.ts @@ -0,0 +1,27 @@ +/* + * 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 { EntitiesQueryResponsePageInfo } from '../models/EntitiesQueryResponsePageInfo.model'; +import { Entity } from '../models/Entity.model'; + +export interface EntitiesQueryResponse { + /** + * The list of entities paginated by a specific filter. + */ + items: Array; + totalItems: number; + pageInfo: EntitiesQueryResponsePageInfo; +} diff --git a/packages/catalog-client/src/generated/models/EntitiesQueryResponsePageInfo.model.ts b/packages/catalog-client/src/generated/models/EntitiesQueryResponsePageInfo.model.ts new file mode 100644 index 0000000000..8a7d5b39bb --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntitiesQueryResponsePageInfo.model.ts @@ -0,0 +1,26 @@ +/* + * 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. + */ + +export interface EntitiesQueryResponsePageInfo { + /** + * The cursor for the next batch of entities. + */ + nextCursor?: string; + /** + * The cursor for the previous batch of entities. + */ + prevCursor?: string; +} diff --git a/packages/catalog-client/src/generated/models/Entity.model.ts b/packages/catalog-client/src/generated/models/Entity.model.ts new file mode 100644 index 0000000000..59ef9bc741 --- /dev/null +++ b/packages/catalog-client/src/generated/models/Entity.model.ts @@ -0,0 +1,41 @@ +/* + * 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 { EntityMeta } from '../models/EntityMeta.model'; +import { EntityRelation } from '../models/EntityRelation.model'; + +/** + * The parts of the format that's common to all versions/kinds of entity. + */ +export interface Entity { + /** + * The relations that this entity has with other entities. + */ + relations?: Array; + /** + * A type representing all allowed JSON object values. + */ + spec?: { [key: string]: any }; + metadata: EntityMeta; + /** + * The high level entity type being described. + */ + kind: string; + /** + * The version of specification format for this particular entity that this is written against. + */ + apiVersion: string; +} diff --git a/packages/catalog-client/src/generated/models/EntityAncestryResponse.model.ts b/packages/catalog-client/src/generated/models/EntityAncestryResponse.model.ts new file mode 100644 index 0000000000..08076fcf36 --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntityAncestryResponse.model.ts @@ -0,0 +1,22 @@ +/* + * 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 { EntityAncestryResponseItemsInner } from '../models/EntityAncestryResponseItemsInner.model'; + +export interface EntityAncestryResponse { + items: Array; + rootEntityRef: string; +} diff --git a/packages/catalog-client/src/generated/models/EntityAncestryResponseItemsInner.model.ts b/packages/catalog-client/src/generated/models/EntityAncestryResponseItemsInner.model.ts new file mode 100644 index 0000000000..7713c21295 --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntityAncestryResponseItemsInner.model.ts @@ -0,0 +1,22 @@ +/* + * 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 { Entity } from '../models/Entity.model'; + +export interface EntityAncestryResponseItemsInner { + parentEntityRefs: Array; + entity: Entity; +} diff --git a/packages/catalog-client/src/generated/models/EntityFacet.model.ts b/packages/catalog-client/src/generated/models/EntityFacet.model.ts new file mode 100644 index 0000000000..3872091d77 --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntityFacet.model.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export interface EntityFacet { + value: string; + count: number; +} diff --git a/packages/catalog-client/src/generated/models/EntityFacetsResponse.model.ts b/packages/catalog-client/src/generated/models/EntityFacetsResponse.model.ts new file mode 100644 index 0000000000..0ff37e4f2f --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntityFacetsResponse.model.ts @@ -0,0 +1,21 @@ +/* + * 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 { EntityFacet } from '../models/EntityFacet.model'; + +export interface EntityFacetsResponse { + facets: { [key: string]: Array }; +} diff --git a/packages/catalog-client/src/generated/models/EntityLink.model.ts b/packages/catalog-client/src/generated/models/EntityLink.model.ts new file mode 100644 index 0000000000..f52f9f2df0 --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntityLink.model.ts @@ -0,0 +1,37 @@ +/* + * 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. + */ + +/** + * A link to external information that is related to the entity. + */ +export interface EntityLink { + /** + * An optional value to categorize links into specific groups + */ + type?: string; + /** + * An optional semantic key that represents a visual icon. + */ + icon?: string; + /** + * An optional descriptive title for the link. + */ + title?: string; + /** + * The url to the external site, document, etc. + */ + url: string; +} diff --git a/packages/catalog-client/src/generated/models/EntityMeta.model.ts b/packages/catalog-client/src/generated/models/EntityMeta.model.ts new file mode 100644 index 0000000000..cb0fd28fdc --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntityMeta.model.ts @@ -0,0 +1,64 @@ +/* + * 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 { EntityLink } from '../models/EntityLink.model'; + +/** + * Metadata fields common to all versions/kinds of entity. + */ +export interface EntityMeta { + [key: string]: any; + /** + * A list of external hyperlinks related to the entity. + */ + links?: Array; + /** + * A list of single-valued strings, to for example classify catalog entities in various ways. + */ + tags?: Array; + /** + * Construct a type with a set of properties K of type T + */ + annotations?: { [key: string]: string }; + /** + * Construct a type with a set of properties K of type T + */ + labels?: { [key: string]: string }; + /** + * A short (typically relatively few words, on one line) description of the entity. + */ + description?: string; + /** + * A display name of the entity, to be presented in user interfaces instead of the `name` property above, when available. This field is sometimes useful when the `name` is cumbersome or ends up being perceived as overly technical. The title generally does not have as stringent format requirements on it, so it may contain special characters and be more explanatory. Do keep it very short though, and avoid situations where a title can be confused with the name of another entity, or where two entities share a title. Note that this is only for display purposes, and may be ignored by some parts of the code. Entity references still always make use of the `name` property, not the title. + */ + title?: string; + /** + * The namespace that the entity belongs to. + */ + namespace?: string; + /** + * The name of the entity. Must be unique within the catalog at any given point in time, for any given namespace + kind pair. This value is part of the technical identifier of the entity, and as such it will appear in URLs, database tables, entity references, and similar. It is subject to restrictions regarding what characters are allowed. If you want to use a different, more human readable string with fewer restrictions on it in user interfaces, see the `title` field below. + */ + name: string; + /** + * An opaque string that changes for each update operation to any part of the entity, including metadata. This field can not be set by the user at creation time, and the server will reject an attempt to do so. The field will be populated in read operations. The field can (optionally) be specified when performing update or delete operations, and the server will then reject the operation if it does not match the current stored value. + */ + etag?: string; + /** + * A globally unique ID for the entity. This field can not be set by the user at creation time, and the server will reject an attempt to do so. The field will be populated in read operations. The field can (optionally) be specified when performing update or delete operations, but the server is free to reject requests that do so in such a way that it breaks semantics. + */ + uid?: string; +} diff --git a/packages/catalog-client/src/generated/models/EntityRelation.model.ts b/packages/catalog-client/src/generated/models/EntityRelation.model.ts new file mode 100644 index 0000000000..1107f7563c --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntityRelation.model.ts @@ -0,0 +1,29 @@ +/* + * 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. + */ + +/** + * A relation of a specific type to another entity in the catalog. + */ +export interface EntityRelation { + /** + * The entity ref of the target of this relation. + */ + targetRef: string; + /** + * The type of the relation. + */ + type: string; +} diff --git a/packages/catalog-client/src/generated/models/ErrorError.model.ts b/packages/catalog-client/src/generated/models/ErrorError.model.ts new file mode 100644 index 0000000000..9dbfa22813 --- /dev/null +++ b/packages/catalog-client/src/generated/models/ErrorError.model.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +export interface ErrorError { + name: string; + message: string; + stack?: string; + code?: string; +} diff --git a/packages/catalog-client/src/generated/models/ErrorRequest.model.ts b/packages/catalog-client/src/generated/models/ErrorRequest.model.ts new file mode 100644 index 0000000000..e6306b87f5 --- /dev/null +++ b/packages/catalog-client/src/generated/models/ErrorRequest.model.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export interface ErrorRequest { + method: string; + url: string; +} diff --git a/packages/catalog-client/src/generated/models/ErrorResponse.model.ts b/packages/catalog-client/src/generated/models/ErrorResponse.model.ts new file mode 100644 index 0000000000..1f55c34b4a --- /dev/null +++ b/packages/catalog-client/src/generated/models/ErrorResponse.model.ts @@ -0,0 +1,19 @@ +/* + * 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. + */ + +export interface ErrorResponse { + statusCode: number; +} diff --git a/packages/catalog-client/src/generated/models/GetEntitiesByRefsRequest.model.ts b/packages/catalog-client/src/generated/models/GetEntitiesByRefsRequest.model.ts new file mode 100644 index 0000000000..bdda7c2fa6 --- /dev/null +++ b/packages/catalog-client/src/generated/models/GetEntitiesByRefsRequest.model.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export interface GetEntitiesByRefsRequest { + entityRefs: Array; + fields?: Array; +} diff --git a/packages/catalog-client/src/generated/models/GetLocations200ResponseInner.model.ts b/packages/catalog-client/src/generated/models/GetLocations200ResponseInner.model.ts new file mode 100644 index 0000000000..134306cce7 --- /dev/null +++ b/packages/catalog-client/src/generated/models/GetLocations200ResponseInner.model.ts @@ -0,0 +1,21 @@ +/* + * 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 { Location } from '../models/Location.model'; + +export interface GetLocations200ResponseInner { + data: Location; +} diff --git a/packages/catalog-client/src/generated/models/Location.model.ts b/packages/catalog-client/src/generated/models/Location.model.ts new file mode 100644 index 0000000000..27e47a5a25 --- /dev/null +++ b/packages/catalog-client/src/generated/models/Location.model.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/** + * Entity location for a specific entity. + */ +export interface Location { + target: string; + type: string; + id: string; +} diff --git a/packages/catalog-client/src/generated/models/LocationInput.model.ts b/packages/catalog-client/src/generated/models/LocationInput.model.ts new file mode 100644 index 0000000000..8de7e3e6c9 --- /dev/null +++ b/packages/catalog-client/src/generated/models/LocationInput.model.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export interface LocationInput { + type: string; + target: string; +} diff --git a/packages/catalog-client/src/generated/models/LocationSpec.model.ts b/packages/catalog-client/src/generated/models/LocationSpec.model.ts new file mode 100644 index 0000000000..80db2cadf2 --- /dev/null +++ b/packages/catalog-client/src/generated/models/LocationSpec.model.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * Holds the entity location information. + */ +export interface LocationSpec { + target: string; + type: string; +} diff --git a/packages/catalog-client/src/generated/models/ModelError.model.ts b/packages/catalog-client/src/generated/models/ModelError.model.ts new file mode 100644 index 0000000000..989e755c9a --- /dev/null +++ b/packages/catalog-client/src/generated/models/ModelError.model.ts @@ -0,0 +1,26 @@ +/* + * 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 { ErrorError } from '../models/ErrorError.model'; +import { ErrorRequest } from '../models/ErrorRequest.model'; +import { ErrorResponse } from '../models/ErrorResponse.model'; + +export interface ModelError { + [key: string]: any; + error: ErrorError; + request?: ErrorRequest; + response: ErrorResponse; +} diff --git a/packages/catalog-client/src/generated/models/NullableEntity.model.ts b/packages/catalog-client/src/generated/models/NullableEntity.model.ts new file mode 100644 index 0000000000..36aff86ba8 --- /dev/null +++ b/packages/catalog-client/src/generated/models/NullableEntity.model.ts @@ -0,0 +1,41 @@ +/* + * 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 { EntityMeta } from '../models/EntityMeta.model'; +import { EntityRelation } from '../models/EntityRelation.model'; + +/** + * The parts of the format that's common to all versions/kinds of entity. + */ +export interface NullableEntity { + /** + * The relations that this entity has with other entities. + */ + relations?: Array; + /** + * A type representing all allowed JSON object values. + */ + spec?: { [key: string]: any }; + metadata: EntityMeta; + /** + * The high level entity type being described. + */ + kind: string; + /** + * The version of specification format for this particular entity that this is written against. + */ + apiVersion: string; +} diff --git a/packages/catalog-client/src/generated/models/RecursivePartialEntity.model.ts b/packages/catalog-client/src/generated/models/RecursivePartialEntity.model.ts new file mode 100644 index 0000000000..8de7824550 --- /dev/null +++ b/packages/catalog-client/src/generated/models/RecursivePartialEntity.model.ts @@ -0,0 +1,41 @@ +/* + * 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 { RecursivePartialEntityMeta } from '../models/RecursivePartialEntityMeta.model'; +import { RecursivePartialEntityRelation } from '../models/RecursivePartialEntityRelation.model'; + +/** + * Makes all keys of an entire hierarchy optional. + */ +export interface RecursivePartialEntity { + /** + * The version of specification format for this particular entity that this is written against. + */ + apiVersion?: string; + /** + * The high level entity type being described. + */ + kind?: string; + metadata?: RecursivePartialEntityMeta; + /** + * A type representing all allowed JSON object values. + */ + spec?: { [key: string]: any }; + /** + * The relations that this entity has with other entities. + */ + relations?: Array; +} diff --git a/packages/catalog-client/src/generated/models/RecursivePartialEntityMeta.model.ts b/packages/catalog-client/src/generated/models/RecursivePartialEntityMeta.model.ts new file mode 100644 index 0000000000..b21cc1bb21 --- /dev/null +++ b/packages/catalog-client/src/generated/models/RecursivePartialEntityMeta.model.ts @@ -0,0 +1,60 @@ +/* + * 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 { EntityLink } from '../models/EntityLink.model'; + +export interface RecursivePartialEntityMeta { + /** + * A list of external hyperlinks related to the entity. + */ + links?: Array; + /** + * A list of single-valued strings, to for example classify catalog entities in various ways. + */ + tags?: Array; + /** + * Construct a type with a set of properties K of type T + */ + annotations?: { [key: string]: string }; + /** + * Construct a type with a set of properties K of type T + */ + labels?: { [key: string]: string }; + /** + * A short (typically relatively few words, on one line) description of the entity. + */ + description?: string; + /** + * A display name of the entity, to be presented in user interfaces instead of the `name` property above, when available. This field is sometimes useful when the `name` is cumbersome or ends up being perceived as overly technical. The title generally does not have as stringent format requirements on it, so it may contain special characters and be more explanatory. Do keep it very short though, and avoid situations where a title can be confused with the name of another entity, or where two entities share a title. Note that this is only for display purposes, and may be ignored by some parts of the code. Entity references still always make use of the `name` property, not the title. + */ + title?: string; + /** + * The namespace that the entity belongs to. + */ + namespace?: string; + /** + * The name of the entity. Must be unique within the catalog at any given point in time, for any given namespace + kind pair. This value is part of the technical identifier of the entity, and as such it will appear in URLs, database tables, entity references, and similar. It is subject to restrictions regarding what characters are allowed. If you want to use a different, more human readable string with fewer restrictions on it in user interfaces, see the `title` field below. + */ + name?: string; + /** + * An opaque string that changes for each update operation to any part of the entity, including metadata. This field can not be set by the user at creation time, and the server will reject an attempt to do so. The field will be populated in read operations. The field can (optionally) be specified when performing update or delete operations, and the server will then reject the operation if it does not match the current stored value. + */ + etag?: string; + /** + * A globally unique ID for the entity. This field can not be set by the user at creation time, and the server will reject an attempt to do so. The field will be populated in read operations. The field can (optionally) be specified when performing update or delete operations, but the server is free to reject requests that do so in such a way that it breaks semantics. + */ + uid?: string; +} diff --git a/packages/catalog-client/src/generated/models/RecursivePartialEntityMetaAllOf.model.ts b/packages/catalog-client/src/generated/models/RecursivePartialEntityMetaAllOf.model.ts new file mode 100644 index 0000000000..5111cd7f9f --- /dev/null +++ b/packages/catalog-client/src/generated/models/RecursivePartialEntityMetaAllOf.model.ts @@ -0,0 +1,63 @@ +/* + * 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 { EntityLink } from '../models/EntityLink.model'; + +/** + * Metadata fields common to all versions/kinds of entity. + */ +export interface RecursivePartialEntityMetaAllOf { + /** + * A list of external hyperlinks related to the entity. + */ + links?: Array; + /** + * A list of single-valued strings, to for example classify catalog entities in various ways. + */ + tags?: Array; + /** + * Construct a type with a set of properties K of type T + */ + annotations?: { [key: string]: string }; + /** + * Construct a type with a set of properties K of type T + */ + labels?: { [key: string]: string }; + /** + * A short (typically relatively few words, on one line) description of the entity. + */ + description?: string; + /** + * A display name of the entity, to be presented in user interfaces instead of the `name` property above, when available. This field is sometimes useful when the `name` is cumbersome or ends up being perceived as overly technical. The title generally does not have as stringent format requirements on it, so it may contain special characters and be more explanatory. Do keep it very short though, and avoid situations where a title can be confused with the name of another entity, or where two entities share a title. Note that this is only for display purposes, and may be ignored by some parts of the code. Entity references still always make use of the `name` property, not the title. + */ + title?: string; + /** + * The namespace that the entity belongs to. + */ + namespace?: string; + /** + * The name of the entity. Must be unique within the catalog at any given point in time, for any given namespace + kind pair. This value is part of the technical identifier of the entity, and as such it will appear in URLs, database tables, entity references, and similar. It is subject to restrictions regarding what characters are allowed. If you want to use a different, more human readable string with fewer restrictions on it in user interfaces, see the `title` field below. + */ + name?: string; + /** + * An opaque string that changes for each update operation to any part of the entity, including metadata. This field can not be set by the user at creation time, and the server will reject an attempt to do so. The field will be populated in read operations. The field can (optionally) be specified when performing update or delete operations, and the server will then reject the operation if it does not match the current stored value. + */ + etag?: string; + /** + * A globally unique ID for the entity. This field can not be set by the user at creation time, and the server will reject an attempt to do so. The field will be populated in read operations. The field can (optionally) be specified when performing update or delete operations, but the server is free to reject requests that do so in such a way that it breaks semantics. + */ + uid?: string; +} diff --git a/packages/catalog-client/src/generated/models/RecursivePartialEntityRelation.model.ts b/packages/catalog-client/src/generated/models/RecursivePartialEntityRelation.model.ts new file mode 100644 index 0000000000..50635b69f4 --- /dev/null +++ b/packages/catalog-client/src/generated/models/RecursivePartialEntityRelation.model.ts @@ -0,0 +1,29 @@ +/* + * 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. + */ + +/** + * A relation of a specific type to another entity in the catalog. + */ +export interface RecursivePartialEntityRelation { + /** + * The entity ref of the target of this relation. + */ + targetRef?: string; + /** + * The type of the relation. + */ + type?: string; +} diff --git a/packages/catalog-client/src/generated/models/RefreshEntityRequest.model.ts b/packages/catalog-client/src/generated/models/RefreshEntityRequest.model.ts new file mode 100644 index 0000000000..573523385b --- /dev/null +++ b/packages/catalog-client/src/generated/models/RefreshEntityRequest.model.ts @@ -0,0 +1,26 @@ +/* + * 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. + */ + +/** + * Options for requesting a refresh of entities in the catalog. + */ +export interface RefreshEntityRequest { + authorizationToken?: string; + /** + * The reference to a single entity that should be refreshed + */ + entityRef: string; +} diff --git a/packages/catalog-client/src/generated/models/ValidateEntity400Response.model.ts b/packages/catalog-client/src/generated/models/ValidateEntity400Response.model.ts new file mode 100644 index 0000000000..0031b2edc2 --- /dev/null +++ b/packages/catalog-client/src/generated/models/ValidateEntity400Response.model.ts @@ -0,0 +1,21 @@ +/* + * 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 { ValidateEntity400ResponseErrorsInner } from '../models/ValidateEntity400ResponseErrorsInner.model'; + +export interface ValidateEntity400Response { + errors: Array; +} diff --git a/packages/catalog-client/src/generated/models/ValidateEntity400ResponseErrorsInner.model.ts b/packages/catalog-client/src/generated/models/ValidateEntity400ResponseErrorsInner.model.ts new file mode 100644 index 0000000000..6223df27af --- /dev/null +++ b/packages/catalog-client/src/generated/models/ValidateEntity400ResponseErrorsInner.model.ts @@ -0,0 +1,21 @@ +/* + * 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. + */ + +export interface ValidateEntity400ResponseErrorsInner { + [key: string]: any; + name: string; + message: string; +} diff --git a/packages/catalog-client/src/generated/models/ValidateEntityRequest.model.ts b/packages/catalog-client/src/generated/models/ValidateEntityRequest.model.ts new file mode 100644 index 0000000000..d5ce1be5c9 --- /dev/null +++ b/packages/catalog-client/src/generated/models/ValidateEntityRequest.model.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export interface ValidateEntityRequest { + location: string; + entity: { [key: string]: any }; +} diff --git a/packages/catalog-client/src/generated/pluginId.ts b/packages/catalog-client/src/generated/pluginId.ts new file mode 100644 index 0000000000..c8c8ce1ca3 --- /dev/null +++ b/packages/catalog-client/src/generated/pluginId.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export const pluginId = 'catalog'; diff --git a/packages/catalog-client/src/generated/types/discovery.ts b/packages/catalog-client/src/generated/types/discovery.ts new file mode 100644 index 0000000000..a7f87d3780 --- /dev/null +++ b/packages/catalog-client/src/generated/types/discovery.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * This is a copy of the DiscoveryApi, to avoid importing core-plugin-api. + */ +export type DiscoveryApi = { + getBaseUrl(pluginId: string): Promise; +}; diff --git a/packages/catalog-client/src/generated/types/fetch.ts b/packages/catalog-client/src/generated/types/fetch.ts new file mode 100644 index 0000000000..3de56c028e --- /dev/null +++ b/packages/catalog-client/src/generated/types/fetch.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * This is a copy of FetchApi, to avoid importing core-plugin-api. + */ +export type FetchApi = { + fetch: typeof fetch; +}; diff --git a/packages/cli/config/eslint-factory.js b/packages/cli/config/eslint-factory.js index 58a9362051..3717943427 100644 --- a/packages/cli/config/eslint-factory.js +++ b/packages/cli/config/eslint-factory.js @@ -66,7 +66,7 @@ function createConfig(dir, extraConfig = {}) { ...(extraExtends ?? []), ], parser: '@typescript-eslint/parser', - plugins: ['import', ...(plugins ?? [])], + plugins: ['import', 'unused-imports', ...(plugins ?? [])], env: { jest: true, ...env, @@ -165,6 +165,23 @@ function createConfig(dir, extraConfig = {}) { ], }, }, + { + files: ['**/src/generated/**/*.ts'], + rules: { + ...tsRules, + 'no-unused-vars': 'off', + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': [ + 'warn', + { + vars: 'all', + varsIgnorePattern: '^_', + args: 'none', + argsIgnorePattern: '^_', + }, + ], + }, + }, ...(overrides ?? []), ], }; diff --git a/packages/cli/package.json b/packages/cli/package.json index 40880607e5..1d1d3a8f59 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -89,6 +89,7 @@ "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-react": "^7.28.0", "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-unused-imports": "^3.0.0", "eslint-webpack-plugin": "^3.1.1", "express": "^4.17.1", "fork-ts-checker-webpack-plugin": "^7.0.0-alpha.8", diff --git a/packages/repo-tools/openapitools.json b/packages/repo-tools/openapitools.json new file mode 100644 index 0000000000..65c0d8ead7 --- /dev/null +++ b/packages/repo-tools/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "6.5.0" + } +} diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index 3bd76a6e60..9dad92966b 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -39,6 +39,7 @@ "@manypkg/get-packages": "^1.1.3", "@microsoft/api-documenter": "^7.22.33", "@microsoft/api-extractor": "^7.36.4", + "@openapitools/openapi-generator-cli": "^2.7.0", "@stoplight/spectral-core": "^1.18.0", "@stoplight/spectral-formatters": "^1.1.0", "@stoplight/spectral-functions": "^1.7.2", diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index 4f9ea87708..d9c6d892f5 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -32,14 +32,18 @@ function registerSchemaCommand(program: Command) { .description( 'Verify that all OpenAPI schemas are valid and have a matching `schemas/openapi.generated.ts` file.', ) - .action(lazy(() => import('./openapi/verify').then(m => m.bulkCommand))); + .action( + lazy(() => import('./openapi/schema/verify').then(m => m.bulkCommand)), + ); openApiCommand .command('generate [paths...]') .description( 'Generates a Typescript file from an OpenAPI yaml spec. For use with the `@backstage/backend-openapi-utils` ApiRouter type.', ) - .action(lazy(() => import('./openapi/generate').then(m => m.bulkCommand))); + .action( + lazy(() => import('./openapi/schema/generate').then(m => m.bulkCommand)), + ); openApiCommand .command('lint [paths...]') @@ -60,6 +64,16 @@ function registerSchemaCommand(program: Command) { .command('init ') .description('Creates any config needed for the test command.') .action(lazy(() => import('./openapi/test/init').then(m => m.default))); + + openApiCommand + .command('generate-client') + .requiredOption('--input-spec ') + .requiredOption('--output-directory ') + .action( + lazy(() => + import('./openapi/client/generate').then(m => m.singleCommand), + ), + ); } export function registerCommands(program: Command) { diff --git a/packages/repo-tools/src/commands/openapi/client/generate.ts b/packages/repo-tools/src/commands/openapi/client/generate.ts new file mode 100644 index 0000000000..baa7845436 --- /dev/null +++ b/packages/repo-tools/src/commands/openapi/client/generate.ts @@ -0,0 +1,97 @@ +/* + * 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 chalk from 'chalk'; +import { resolve } from 'path'; +import { OPENAPI_IGNORE_FILES, OUTPUT_PATH } from '../constants'; +import { paths as cliPaths } from '../../../lib/paths'; +import { mkdirpSync } from 'fs-extra'; +import fs from 'fs-extra'; +import { exec } from '../../../lib/exec'; + +async function generate(spec: string, outputDirectory: string) { + const resolvedOpenapiPath = resolve(spec); + const resolvedOutputDirectory = resolve(outputDirectory, OUTPUT_PATH); + mkdirpSync(resolvedOutputDirectory); + + await fs.mkdirp(resolvedOutputDirectory); + + await fs.writeFile( + resolve(resolvedOutputDirectory, '.openapi-generator-ignore'), + OPENAPI_IGNORE_FILES.join('\n'), + ); + + await exec( + // The actual main.js file for the binary isn't executable but yarn does _something_ to make it executable. + // TODO (sennyeya@): Make this use the actual binary + `yarn openapi-generator-cli`, + [ + 'generate', + '-i', + resolvedOpenapiPath, + '-o', + resolvedOutputDirectory, + '-g', + 'typescript', + '-c', + 'templates/typescript-backstage.yaml', + '--generator-key', + 'v3.0', + ], + { + maxBuffer: Number.MAX_VALUE, + cwd: cliPaths.ownDir, + env: { + ...process.env, + // PWD: outputDirectory, + }, + }, + ); + + await exec( + `yarn backstage-cli package lint --fix ${resolvedOutputDirectory}`, + ); + + if (cliPaths.resolveTargetRoot('node_modules/.bin/prettier')) { + await exec(`yarn prettier --write ${resolvedOutputDirectory}`); + } + + fs.removeSync(resolve(resolvedOutputDirectory, '.openapi-generator-ignore')); + + fs.rmSync(resolve(resolvedOutputDirectory, '.openapi-generator'), { + recursive: true, + force: true, + }); +} + +export async function singleCommand({ + inputSpec, + outputDirectory, +}: { + inputSpec: string; + outputDirectory: string; +}): Promise { + try { + await generate(inputSpec, outputDirectory); + console.log(chalk.green(`Generated client for ${inputSpec}`)); + } catch (err) { + console.log(); + console.log(chalk.red(`Client generation failed in ${outputDirectory}:`)); + console.log(err); + + process.exit(1); + } +} diff --git a/packages/repo-tools/src/commands/openapi/constants.ts b/packages/repo-tools/src/commands/openapi/constants.ts index defef4ab53..2e7a57522a 100644 --- a/packages/repo-tools/src/commands/openapi/constants.ts +++ b/packages/repo-tools/src/commands/openapi/constants.ts @@ -19,3 +19,44 @@ export const YAML_SCHEMA_PATH = 'src/schema/openapi.yaml'; export const TS_MODULE = 'src/schema/openapi.generated'; export const TS_SCHEMA_PATH = `${TS_MODULE}.ts`; + +export const GENERATOR_VERSION = `1.0.0`; +export const GENERATOR_NAME = 'typescript-backstage'; +export const GENERATOR_FILE = `packages/template-openapi-plugin-client/generator/target/${GENERATOR_NAME}-openapi-generator-${GENERATOR_VERSION}.jar`; + +export const OUTPUT_PATH = 'src/generated'; + +export const OPENAPI_IGNORE_FILES = [ + // Get rid of the default files. + '*.md', + // The rest of these have to be explicit, otherwise they get added if this was a *.* + 'apis/baseapi.ts', + 'apis/exception.ts', + 'auth/*', + 'http/*', + 'middleware.ts', + 'servers.ts', + 'util.ts', + 'configuration.ts', + 'rxjsStub.ts', + '.gitignore', + + // Override the created version. + 'apis/*.ts', + '!apis/*.client.ts', + 'models/*.ts', + '!models/*.model.ts', + + // Always include index.ts files. + '!index.ts', + '!**/index.ts', + + // Weird API typings. + 'types/ObjectParamAPI.ts', + 'types/ObservableAPI.ts', + 'types/PromiseAPI.ts', + + 'git_push.sh', + 'package.json', + 'tsconfig.json', +]; diff --git a/packages/repo-tools/src/commands/openapi/generate.ts b/packages/repo-tools/src/commands/openapi/schema/generate.ts similarity index 95% rename from packages/repo-tools/src/commands/openapi/generate.ts rename to packages/repo-tools/src/commands/openapi/schema/generate.ts index 52b8e5b840..cd9f572f2d 100644 --- a/packages/repo-tools/src/commands/openapi/generate.ts +++ b/packages/repo-tools/src/commands/openapi/schema/generate.ts @@ -18,9 +18,9 @@ import fs from 'fs-extra'; import YAML from 'js-yaml'; import chalk from 'chalk'; import { resolve } from 'path'; -import { paths as cliPaths } from '../../lib/paths'; -import { runner } from './runner'; -import { TS_SCHEMA_PATH, YAML_SCHEMA_PATH } from './constants'; +import { paths as cliPaths } from '../../../lib/paths'; +import { runner } from '../runner'; +import { TS_SCHEMA_PATH, YAML_SCHEMA_PATH } from '../constants'; import { promisify } from 'util'; import { exec as execCb } from 'child_process'; diff --git a/packages/repo-tools/src/commands/openapi/verify.ts b/packages/repo-tools/src/commands/openapi/schema/verify.ts similarity index 93% rename from packages/repo-tools/src/commands/openapi/verify.ts rename to packages/repo-tools/src/commands/openapi/schema/verify.ts index 2808e72702..7120dd67ad 100644 --- a/packages/repo-tools/src/commands/openapi/verify.ts +++ b/packages/repo-tools/src/commands/openapi/schema/verify.ts @@ -21,9 +21,9 @@ import { join } from 'path'; import chalk from 'chalk'; import { relative as relativePath, resolve as resolvePath } from 'path'; import Parser from '@apidevtools/swagger-parser'; -import { runner } from './runner'; -import { paths as cliPaths } from '../../lib/paths'; -import { TS_MODULE, TS_SCHEMA_PATH, YAML_SCHEMA_PATH } from './constants'; +import { runner } from '../runner'; +import { paths as cliPaths } from '../../../lib/paths'; +import { TS_MODULE, TS_SCHEMA_PATH, YAML_SCHEMA_PATH } from '../constants'; async function verify(directoryPath: string) { const openapiPath = join(directoryPath, YAML_SCHEMA_PATH); diff --git a/packages/repo-tools/templates/typescript-backstage.yaml b/packages/repo-tools/templates/typescript-backstage.yaml new file mode 100644 index 0000000000..eaa72d7179 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage.yaml @@ -0,0 +1,18 @@ +templateDir: templates/typescript-backstage + +files: + api.mustache: + templateType: API + # For some reason, they check for destinationFilename differences. We have to change the ending to override the file. + destinationFilename: .client.ts + model.mustache: + templateType: Model + destinationFilename: .model.ts + types/fetch.ts: {} + types/discovery.ts: {} + apis/index.mustache: + templateType: SupportingFiles + destinationFilename: apis/index.ts + pluginId.mustache: + templateType: SupportingFiles + destinationFilename: pluginId.ts \ No newline at end of file diff --git a/packages/repo-tools/templates/typescript-backstage/api.mustache b/packages/repo-tools/templates/typescript-backstage/api.mustache new file mode 100644 index 0000000000..1b8739ba57 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/api.mustache @@ -0,0 +1,114 @@ +{{>licenseInfo}} +import { DiscoveryApi } from '../types/discovery'; +import { FetchApi } from '../types/fetch'; +import crossFetch from 'cross-fetch'; +import {pluginId} from '../pluginId'; +import * as parser from 'uri-template'; + +{{#imports}} +import { {{classname}} } from '{{filename}}.model{{importFileExtension}}'; +{{/imports}} + +type TypedResponse = Omit & { + json: () => Promise; +}; + +{{#operations}} + +/** + * Options you can pass into a request for additional information. + * + * @public + */ +export interface RequestOptions { + token?: string; +} + +/** + * {{{description}}}{{^description}}no description{{/description}} + */ +export class {{classname}}Client { + private readonly discoveryApi: DiscoveryApi; + private readonly fetchApi: FetchApi; + + constructor(options: { + discoveryApi: { getBaseUrl(pluginId: string): Promise }; + fetchApi?: { fetch: typeof fetch }; + }) { + this.discoveryApi = options.discoveryApi; + this.fetchApi = options.fetchApi || { fetch: crossFetch }; + } + + {{#operation}} + /** + {{#notes}} + * {{¬es}} + {{/notes}} + {{#summary}} + * {{&summary}} + {{/summary}} + {{#allParams}} + * @param {{paramName}} {{description}} + {{/allParams}} + */ + public async {{nickname}}( + // @ts-ignore + request: { + {{#hasPathParams}} + path: { + {{#pathParams}} + {{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, + {{/pathParams}} + }, + {{/hasPathParams}} + {{#hasBodyParam}} + {{#bodyParam}} + body: {{{dataType}}}, + {{/bodyParam}} + {{/hasBodyParam}} + {{#hasQueryParams}} + query: { + {{#queryParams}} + {{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, + {{/queryParams}} + }, + {{/hasQueryParams}} + {{#hasHeaderParams}} + header: { + {{#headerParams}} + {{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, + {{/headerParams}} + }, + {{/hasHeaderParams}} + }, + options?: RequestOptions + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `{{{path}}}{{#hasQueryParams}}{?{{#queryParams}}{{baseName}}{{#isArray}}{{#isExplode}}*{{/isExplode}}{{/isArray}}{{^-last}},{{/-last}}{{/queryParams}}}{{/hasQueryParams}}`; + + const uri = parser.parse(uriTemplate).expand({ + {{#pathParams}} + {{baseName}}: request.path.{{paramName}}, + {{/pathParams}} + {{#hasQueryParams}} + ...request.query, + {{/hasQueryParams}} + }) + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + {{#hasHeaderParams}} + ...request.header, + {{/hasHeaderParams}} + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: '{{httpMethod}}', + {{#hasBodyParam}} body: JSON.stringify(request.body), {{/hasBodyParam}} + }); + } + + {{/operation}} +} +{{/operations}} diff --git a/packages/repo-tools/templates/typescript-backstage/apis/index.mustache b/packages/repo-tools/templates/typescript-backstage/apis/index.mustache new file mode 100644 index 0000000000..4c8996c5ee --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/apis/index.mustache @@ -0,0 +1,3 @@ +// + +export * from './DefaultApi.client'; diff --git a/packages/repo-tools/templates/typescript-backstage/index.mustache b/packages/repo-tools/templates/typescript-backstage/index.mustache new file mode 100644 index 0000000000..8436cce60b --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/index.mustache @@ -0,0 +1,3 @@ +// + +export * from './apis' diff --git a/packages/repo-tools/templates/typescript-backstage/model.mustache b/packages/repo-tools/templates/typescript-backstage/model.mustache new file mode 100644 index 0000000000..1f288380e0 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/model.mustache @@ -0,0 +1,43 @@ +// + +{{#models}} +{{#model}} +{{#tsImports}} +import { {{classname}} } from '{{filename}}.model{{importFileExtension}}'; +{{/tsImports}} + +{{#description}} +/** +* {{{.}}} +*/ +{{/description}} +{{^isEnum}} +export interface {{classname}} { +{{#additionalPropertiesType}} + [key: string]: {{{additionalPropertiesType}}}; +{{/additionalPropertiesType}} +{{#vars}} +{{#description}} + /** + * {{{.}}} + */ +{{/description}} + '{{name}}'{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}}; +{{/vars}} +} + +{{#hasEnums}} + +{{#vars}} +{{#isEnum}} +export type {{classname}}{{enumName}} ={{#allowableValues}}{{#values}} "{{.}}" {{^-last}}|{{/-last}}{{/values}}{{/allowableValues}}; +{{/isEnum}} +{{/vars}} + +{{/hasEnums}} +{{/isEnum}} +{{#isEnum}} +export type {{classname}} ={{#allowableValues}}{{#values}} "{{.}}" {{^-last}}|{{/-last}}{{/values}}{{/allowableValues}}; +{{/isEnum}} +{{/model}} +{{/models}} \ No newline at end of file diff --git a/packages/repo-tools/templates/typescript-backstage/models/models_all.mustache b/packages/repo-tools/templates/typescript-backstage/models/models_all.mustache new file mode 100644 index 0000000000..d076cfb3ff --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/models/models_all.mustache @@ -0,0 +1,7 @@ +// + +{{#models}} +{{#model}} +export * from '{{{ importPath }}}.model{{importFileExtension}}' +{{/model}} +{{/models}} \ No newline at end of file diff --git a/packages/repo-tools/templates/typescript-backstage/pluginId.mustache b/packages/repo-tools/templates/typescript-backstage/pluginId.mustache new file mode 100644 index 0000000000..8bc8461cc9 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/pluginId.mustache @@ -0,0 +1,6 @@ + +{{#servers}} +{{#-last}} +export const pluginId = "{{url}}"; +{{/-last}} +{{/servers}} \ No newline at end of file diff --git a/packages/repo-tools/templates/typescript-backstage/types/discovery.ts b/packages/repo-tools/templates/typescript-backstage/types/discovery.ts new file mode 100644 index 0000000000..a7f87d3780 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/types/discovery.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * This is a copy of the DiscoveryApi, to avoid importing core-plugin-api. + */ +export type DiscoveryApi = { + getBaseUrl(pluginId: string): Promise; +}; diff --git a/packages/repo-tools/templates/typescript-backstage/types/fetch.ts b/packages/repo-tools/templates/typescript-backstage/types/fetch.ts new file mode 100644 index 0000000000..3de56c028e --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/types/fetch.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * This is a copy of FetchApi, to avoid importing core-plugin-api. + */ +export type FetchApi = { + fetch: typeof fetch; +}; diff --git a/plugins/catalog-backend/src/schema/openapi.generated.ts b/plugins/catalog-backend/src/schema/openapi.generated.ts index a31615dfc6..fe64c75373 100644 --- a/plugins/catalog-backend/src/schema/openapi.generated.ts +++ b/plugins/catalog-backend/src/schema/openapi.generated.ts @@ -36,6 +36,9 @@ export const spec = { { url: '/', }, + { + url: 'catalog', + }, ], components: { examples: {}, @@ -105,6 +108,7 @@ export const spec = { description: 'Restrict to just these fields in the response.', required: false, allowReserved: true, + explode: false, schema: { type: 'array', items: { @@ -248,12 +252,13 @@ export const spec = { }, }, required: ['error', 'response'], + additionalProperties: {}, }, JsonObject: { type: 'object', properties: {}, description: 'A type representing all allowed JSON object values.', - additionalProperties: true, + additionalProperties: {}, }, MapStringString: { type: 'object', @@ -291,70 +296,62 @@ export const spec = { additionalProperties: false, }, EntityMeta: { - allOf: [ - { - $ref: '#/components/schemas/JsonObject', - }, - { - type: 'object', - properties: { - links: { - type: 'array', - items: { - $ref: '#/components/schemas/EntityLink', - }, - description: - 'A list of external hyperlinks related to the entity.', - }, - tags: { - type: 'array', - items: { - type: 'string', - }, - description: - 'A list of single-valued strings, to for example classify catalog entities in\nvarious ways.', - }, - annotations: { - $ref: '#/components/schemas/MapStringString', - }, - labels: { - $ref: '#/components/schemas/MapStringString', - }, - description: { - type: 'string', - description: - 'A short (typically relatively few words, on one line) description of the\nentity.', - }, - title: { - type: 'string', - description: - 'A display name of the entity, to be presented in user interfaces instead\nof the `name` property above, when available.\nThis field is sometimes useful when the `name` is cumbersome or ends up\nbeing perceived as overly technical. The title generally does not have\nas stringent format requirements on it, so it may contain special\ncharacters and be more explanatory. Do keep it very short though, and\navoid situations where a title can be confused with the name of another\nentity, or where two entities share a title.\nNote that this is only for display purposes, and may be ignored by some\nparts of the code. Entity references still always make use of the `name`\nproperty, not the title.', - }, - namespace: { - type: 'string', - description: 'The namespace that the entity belongs to.', - }, - name: { - type: 'string', - description: - 'The name of the entity.\nMust be unique within the catalog at any given point in time, for any\ngiven namespace + kind pair. This value is part of the technical\nidentifier of the entity, and as such it will appear in URLs, database\ntables, entity references, and similar. It is subject to restrictions\nregarding what characters are allowed.\nIf you want to use a different, more human readable string with fewer\nrestrictions on it in user interfaces, see the `title` field below.', - }, - etag: { - type: 'string', - description: - 'An opaque string that changes for each update operation to any part of\nthe entity, including metadata.\nThis field can not be set by the user at creation time, and the server\nwill reject an attempt to do so. The field will be populated in read\noperations. The field can (optionally) be specified when performing\nupdate or delete operations, and the server will then reject the\noperation if it does not match the current stored value.', - }, - uid: { - type: 'string', - description: - 'A globally unique ID for the entity.\nThis field can not be set by the user at creation time, and the server\nwill reject an attempt to do so. The field will be populated in read\noperations. The field can (optionally) be specified when performing\nupdate or delete operations, but the server is free to reject requests\nthat do so in such a way that it breaks semantics.', - }, + type: 'object', + properties: { + links: { + type: 'array', + items: { + $ref: '#/components/schemas/EntityLink', }, - required: ['name'], + description: 'A list of external hyperlinks related to the entity.', }, - ], + tags: { + type: 'array', + items: { + type: 'string', + }, + description: + 'A list of single-valued strings, to for example classify catalog entities in\nvarious ways.', + }, + annotations: { + $ref: '#/components/schemas/MapStringString', + }, + labels: { + $ref: '#/components/schemas/MapStringString', + }, + description: { + type: 'string', + description: + 'A short (typically relatively few words, on one line) description of the\nentity.', + }, + title: { + type: 'string', + description: + 'A display name of the entity, to be presented in user interfaces instead\nof the `name` property above, when available.\nThis field is sometimes useful when the `name` is cumbersome or ends up\nbeing perceived as overly technical. The title generally does not have\nas stringent format requirements on it, so it may contain special\ncharacters and be more explanatory. Do keep it very short though, and\navoid situations where a title can be confused with the name of another\nentity, or where two entities share a title.\nNote that this is only for display purposes, and may be ignored by some\nparts of the code. Entity references still always make use of the `name`\nproperty, not the title.', + }, + namespace: { + type: 'string', + description: 'The namespace that the entity belongs to.', + }, + name: { + type: 'string', + description: + 'The name of the entity.\nMust be unique within the catalog at any given point in time, for any\ngiven namespace + kind pair. This value is part of the technical\nidentifier of the entity, and as such it will appear in URLs, database\ntables, entity references, and similar. It is subject to restrictions\nregarding what characters are allowed.\nIf you want to use a different, more human readable string with fewer\nrestrictions on it in user interfaces, see the `title` field below.', + }, + etag: { + type: 'string', + description: + 'An opaque string that changes for each update operation to any part of\nthe entity, including metadata.\nThis field can not be set by the user at creation time, and the server\nwill reject an attempt to do so. The field will be populated in read\noperations. The field can (optionally) be specified when performing\nupdate or delete operations, and the server will then reject the\noperation if it does not match the current stored value.', + }, + uid: { + type: 'string', + description: + 'A globally unique ID for the entity.\nThis field can not be set by the user at creation time, and the server\nwill reject an attempt to do so. The field will be populated in read\noperations. The field can (optionally) be specified when performing\nupdate or delete operations, but the server is free to reject requests\nthat do so in such a way that it breaks semantics.', + }, + }, + required: ['name'], description: 'Metadata fields common to all versions/kinds of entity.', - additionalProperties: true, + additionalProperties: {}, }, EntityRelation: { type: 'object', @@ -403,7 +400,6 @@ export const spec = { required: ['metadata', 'kind', 'apiVersion'], description: "The parts of the format that's common to all versions/kinds of entity.", - additionalProperties: true, }, NullableEntity: { type: 'object', @@ -435,7 +431,6 @@ export const spec = { required: ['metadata', 'kind', 'apiVersion'], description: "The parts of the format that's common to all versions/kinds of entity.", - additionalProperties: true, nullable: true, }, EntityAncestryResponse: { @@ -472,11 +467,7 @@ export const spec = { items: { type: 'array', items: { - anyOf: [ - { - $ref: '#/components/schemas/NullableEntity', - }, - ], + $ref: '#/components/schemas/NullableEntity', }, description: 'The list of entities, in the same order as the refs in the request. Entries\nthat are null signify that no entity existed with that ref.', @@ -495,6 +486,7 @@ export const spec = { type: 'number', }, }, + required: ['value', 'count'], additionalProperties: false, }, EntityFacetsResponse: { @@ -782,6 +774,7 @@ export const spec = { }, }, }, + required: ['items', 'totalItems', 'pageInfo'], additionalProperties: false, }, }, @@ -1076,11 +1069,6 @@ export const spec = { JWT: [], }, ], - parameters: [ - { - $ref: '#/components/parameters/fields', - }, - ], requestBody: { required: false, content: { @@ -1257,8 +1245,8 @@ export const spec = { operationId: 'CreateLocation', description: 'Create a location for a given target.', responses: { - '200': { - description: 'Ok', + '201': { + description: 'Created', content: { 'application/json': { schema: { @@ -1282,38 +1270,6 @@ export const spec = { }, }, }, - '201': { - description: '201 response', - content: { - 'application/json': { - schema: { - type: 'object', - properties: { - location: { - type: 'object', - properties: { - id: { - type: 'string', - }, - type: { - type: 'string', - }, - target: { - type: 'string', - }, - }, - required: ['id', 'type', 'target'], - }, - entities: { - type: 'array', - items: {}, - }, - }, - required: ['location', 'entities'], - }, - }, - }, - }, '400': { $ref: '#/components/responses/ErrorResponse', }, @@ -1524,7 +1480,7 @@ export const spec = { description: 'Ok', }, '400': { - description: '400 response', + description: 'Validation errors.', content: { 'application/json; charset=utf-8': { schema: { @@ -1543,6 +1499,7 @@ export const spec = { }, }, required: ['name', 'message'], + additionalProperties: {}, }, }, }, @@ -1571,7 +1528,7 @@ export const spec = { }, entity: { type: 'object', - additionalProperties: true, + additionalProperties: {}, }, }, required: ['location', 'entity'], diff --git a/plugins/catalog-backend/src/schema/openapi.yaml b/plugins/catalog-backend/src/schema/openapi.yaml index 3375f7fe15..d3ef718286 100644 --- a/plugins/catalog-backend/src/schema/openapi.yaml +++ b/plugins/catalog-backend/src/schema/openapi.yaml @@ -9,6 +9,7 @@ info: contact: {} servers: - url: / + - url: catalog components: examples: {} headers: {} @@ -65,6 +66,7 @@ components: description: Restrict to just these fields in the response. required: false allowReserved: true + explode: false schema: type: array items: @@ -180,11 +182,12 @@ components: required: - error - response + additionalProperties: {} JsonObject: type: object properties: {} description: A type representing all allowed JSON object values. - additionalProperties: true + additionalProperties: {} MapStringString: type: object properties: {} @@ -211,82 +214,80 @@ components: description: A link to external information that is related to the entity. additionalProperties: false EntityMeta: - allOf: - - $ref: '#/components/schemas/JsonObject' - - type: object - properties: - links: - type: array - items: - $ref: '#/components/schemas/EntityLink' - description: A list of external hyperlinks related to the entity. - tags: - type: array - items: - type: string - description: |- - A list of single-valued strings, to for example classify catalog entities in - various ways. - annotations: - $ref: '#/components/schemas/MapStringString' - labels: - $ref: '#/components/schemas/MapStringString' - description: - type: string - description: |- - A short (typically relatively few words, on one line) description of the - entity. - title: - type: string - description: |- - A display name of the entity, to be presented in user interfaces instead - of the `name` property above, when available. - This field is sometimes useful when the `name` is cumbersome or ends up - being perceived as overly technical. The title generally does not have - as stringent format requirements on it, so it may contain special - characters and be more explanatory. Do keep it very short though, and - avoid situations where a title can be confused with the name of another - entity, or where two entities share a title. - Note that this is only for display purposes, and may be ignored by some - parts of the code. Entity references still always make use of the `name` - property, not the title. - namespace: - type: string - description: The namespace that the entity belongs to. - name: - type: string - description: |- - The name of the entity. - Must be unique within the catalog at any given point in time, for any - given namespace + kind pair. This value is part of the technical - identifier of the entity, and as such it will appear in URLs, database - tables, entity references, and similar. It is subject to restrictions - regarding what characters are allowed. - If you want to use a different, more human readable string with fewer - restrictions on it in user interfaces, see the `title` field below. - etag: - type: string - description: |- - An opaque string that changes for each update operation to any part of - the entity, including metadata. - This field can not be set by the user at creation time, and the server - will reject an attempt to do so. The field will be populated in read - operations. The field can (optionally) be specified when performing - update or delete operations, and the server will then reject the - operation if it does not match the current stored value. - uid: - type: string - description: |- - A globally unique ID for the entity. - This field can not be set by the user at creation time, and the server - will reject an attempt to do so. The field will be populated in read - operations. The field can (optionally) be specified when performing - update or delete operations, but the server is free to reject requests - that do so in such a way that it breaks semantics. - required: - - name + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/EntityLink' + description: A list of external hyperlinks related to the entity. + tags: + type: array + items: + type: string + description: |- + A list of single-valued strings, to for example classify catalog entities in + various ways. + annotations: + $ref: '#/components/schemas/MapStringString' + labels: + $ref: '#/components/schemas/MapStringString' + description: + type: string + description: |- + A short (typically relatively few words, on one line) description of the + entity. + title: + type: string + description: |- + A display name of the entity, to be presented in user interfaces instead + of the `name` property above, when available. + This field is sometimes useful when the `name` is cumbersome or ends up + being perceived as overly technical. The title generally does not have + as stringent format requirements on it, so it may contain special + characters and be more explanatory. Do keep it very short though, and + avoid situations where a title can be confused with the name of another + entity, or where two entities share a title. + Note that this is only for display purposes, and may be ignored by some + parts of the code. Entity references still always make use of the `name` + property, not the title. + namespace: + type: string + description: The namespace that the entity belongs to. + name: + type: string + description: |- + The name of the entity. + Must be unique within the catalog at any given point in time, for any + given namespace + kind pair. This value is part of the technical + identifier of the entity, and as such it will appear in URLs, database + tables, entity references, and similar. It is subject to restrictions + regarding what characters are allowed. + If you want to use a different, more human readable string with fewer + restrictions on it in user interfaces, see the `title` field below. + etag: + type: string + description: |- + An opaque string that changes for each update operation to any part of + the entity, including metadata. + This field can not be set by the user at creation time, and the server + will reject an attempt to do so. The field will be populated in read + operations. The field can (optionally) be specified when performing + update or delete operations, and the server will then reject the + operation if it does not match the current stored value. + uid: + type: string + description: |- + A globally unique ID for the entity. + This field can not be set by the user at creation time, and the server + will reject an attempt to do so. The field will be populated in read + operations. The field can (optionally) be specified when performing + update or delete operations, but the server is free to reject requests + that do so in such a way that it breaks semantics. + required: + - name description: Metadata fields common to all versions/kinds of entity. - additionalProperties: true + additionalProperties: {} EntityRelation: type: object properties: @@ -326,7 +327,6 @@ components: - kind - apiVersion description: The parts of the format that's common to all versions/kinds of entity. - additionalProperties: true NullableEntity: type: object properties: @@ -352,7 +352,6 @@ components: - kind - apiVersion description: The parts of the format that's common to all versions/kinds of entity. - additionalProperties: true nullable: true EntityAncestryResponse: type: object @@ -383,8 +382,7 @@ components: items: type: array items: - anyOf: - - $ref: '#/components/schemas/NullableEntity' + $ref: '#/components/schemas/NullableEntity' description: |- The list of entities, in the same order as the refs in the request. Entries that are null signify that no entity existed with that ref. @@ -398,6 +396,9 @@ components: type: string count: type: number + required: + - value + - count additionalProperties: false EntityFacetsResponse: type: object @@ -660,6 +661,10 @@ components: prevCursor: type: string description: The cursor for the previous batch of entities. + required: + - items + - totalItems + - pageInfo additionalProperties: false securitySchemes: JWT: @@ -829,8 +834,6 @@ paths: security: - {} - JWT: [] - parameters: - - $ref: '#/components/parameters/fields' requestBody: required: false content: @@ -942,8 +945,8 @@ paths: operationId: CreateLocation description: Create a location for a given target. responses: - '200': - description: Ok + '201': + description: Created content: application/json: schema: @@ -960,32 +963,6 @@ paths: required: - entities - location - '201': - description: 201 response - content: - application/json: - schema: - type: object - properties: - location: - type: object - properties: - id: - type: string - type: - type: string - target: - type: string - required: - - id - - type - - target - entities: - type: array - items: {} - required: - - location - - entities '400': $ref: '#/components/responses/ErrorResponse' default: @@ -1120,7 +1097,7 @@ paths: '200': description: Ok '400': - description: 400 response + description: Validation errors. content: application/json; charset=utf-8: schema: @@ -1138,6 +1115,7 @@ paths: required: - name - message + additionalProperties: {} required: - errors security: @@ -1155,7 +1133,7 @@ paths: type: string entity: type: object - additionalProperties: true + additionalProperties: {} required: - location - entity diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index 700e35edc1..05ebf6e1a9 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -198,6 +198,45 @@ describe('createRouter readonly disabled', () => { }); }); + it('parses encoded params request', async () => { + entitiesCatalog.queryEntities.mockResolvedValueOnce({ + items: [], + pageInfo: {}, + totalItems: 0, + }); + const response = await request(app).get( + `/entities/by-query?filter=${encodeURIComponent( + 'a=1,a=2,b=3', + )}&filter=c=4&orderField=${encodeURIComponent( + 'metadata.name,asc', + )}&orderField=metadata.uid,desc`, + ); + + expect(response.status).toEqual(200); + expect(entitiesCatalog.queryEntities).toHaveBeenCalledTimes(1); + expect(entitiesCatalog.queryEntities).toHaveBeenCalledWith({ + filter: { + anyOf: [ + { + allOf: [ + { key: 'a', values: ['1', '2'] }, + { key: 'b', values: ['3'] }, + ], + }, + { allOf: [{ key: 'c', values: ['4'] }] }, + ], + }, + orderFields: [ + { field: 'metadata.name', order: 'asc' }, + { field: 'metadata.uid', order: 'desc' }, + ], + fullTextFilter: { + fields: undefined, + term: '', + }, + }); + }); + it('parses cursor request', async () => { const items: Entity[] = [ { apiVersion: 'a', kind: 'b', metadata: { name: 'n' } }, diff --git a/yarn.lock b/yarn.lock index 2399f06ac5..97224acaac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3529,6 +3529,7 @@ __metadata: "@backstage/errors": "workspace:^" cross-fetch: ^4.0.0 msw: ^1.0.0 + uri-template: ^2.0.0 languageName: unknown linkType: soft @@ -3663,6 +3664,7 @@ __metadata: eslint-plugin-jsx-a11y: ^6.5.1 eslint-plugin-react: ^7.28.0 eslint-plugin-react-hooks: ^4.3.0 + eslint-plugin-unused-imports: ^3.0.0 eslint-webpack-plugin: ^3.1.1 express: ^4.17.1 fork-ts-checker-webpack-plugin: ^7.0.0-alpha.8 @@ -10072,6 +10074,7 @@ __metadata: "@manypkg/get-packages": ^1.1.3 "@microsoft/api-documenter": ^7.22.33 "@microsoft/api-extractor": ^7.36.4 + "@openapitools/openapi-generator-cli": ^2.7.0 "@stoplight/spectral-core": ^1.18.0 "@stoplight/spectral-formatters": ^1.1.0 "@stoplight/spectral-functions": ^1.7.2 @@ -14249,7 +14252,7 @@ __metadata: languageName: node linkType: hard -"@openapitools/openapi-generator-cli@npm:^2.4.26": +"@openapitools/openapi-generator-cli@npm:^2.4.26, @openapitools/openapi-generator-cli@npm:^2.7.0": version: 2.7.0 resolution: "@openapitools/openapi-generator-cli@npm:2.7.0" dependencies: @@ -26211,6 +26214,28 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-unused-imports@npm:^3.0.0": + version: 3.0.0 + resolution: "eslint-plugin-unused-imports@npm:3.0.0" + dependencies: + eslint-rule-composer: ^0.3.0 + peerDependencies: + "@typescript-eslint/eslint-plugin": ^6.0.0 + eslint: ^8.0.0 + peerDependenciesMeta: + "@typescript-eslint/eslint-plugin": + optional: true + checksum: 51666f62cc8dccba2895ced83f3c1e0b78b68c357e17360e156c4db548bfdeda34cbd8725192fb4903f22d5069400fb22ded6039631df01ee82fd618dc307247 + languageName: node + linkType: hard + +"eslint-rule-composer@npm:^0.3.0": + version: 0.3.0 + resolution: "eslint-rule-composer@npm:0.3.0" + checksum: c2f57cded8d1c8f82483e0ce28861214347e24fd79fd4144667974cd334d718f4ba05080aaef2399e3bbe36f7d6632865110227e6b176ed6daa2d676df9281b1 + languageName: node + linkType: hard + "eslint-scope@npm:5.1.1, eslint-scope@npm:^5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" @@ -36761,6 +36786,13 @@ __metadata: languageName: node linkType: hard +"pct-encode@npm:~1.0.0": + version: 1.0.2 + resolution: "pct-encode@npm:1.0.2" + checksum: 11edce15c8a9012cf5fdee006a05f10e3668a755a15aa25b6afbb8cc20d67f600702eb83e5eaca7a98ee78f9b362fb7d9ada9745428dceb6cdc44e0143851509 + languageName: node + linkType: hard + "peek-readable@npm:^4.0.1": version: 4.0.1 resolution: "peek-readable@npm:4.0.1" @@ -43591,6 +43623,15 @@ __metadata: languageName: node linkType: hard +"uri-template@npm:^2.0.0": + version: 2.0.0 + resolution: "uri-template@npm:2.0.0" + dependencies: + pct-encode: ~1.0.0 + checksum: 6eb3254368ca11330502525c6c0ab42af3cb646bfc96a4021666d6ac6653ede1ac0df7fde84a2e35e7f03f42d91b41251963122cfb3de9b54b84bc0ef3583ffc + languageName: node + linkType: hard + "urijs@npm:^1.19.10, urijs@npm:^1.19.11": version: 1.19.11 resolution: "urijs@npm:1.19.11" @@ -44500,9 +44541,9 @@ __metadata: linkType: hard "whatwg-fetch@npm:>=0.10.0": - version: 3.6.2 - resolution: "whatwg-fetch@npm:3.6.2" - checksum: ee976b7249e7791edb0d0a62cd806b29006ad7ec3a3d89145921ad8c00a3a67e4be8f3fb3ec6bc7b58498724fd568d11aeeeea1f7827e7e1e5eae6c8a275afed + version: 3.6.19 + resolution: "whatwg-fetch@npm:3.6.19" + checksum: 2896bc9ca867ea514392c73e2a272f65d5c4916248fe0837a9df5b1b92f247047bc76cf7c29c28a01ac6c5fb4314021d2718958c8a08292a96d56f72b2f56806 languageName: node linkType: hard