feat(openapi): Create Backstage plugins through generator tooling.
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com> Signed-off-by: Aramis Sennyey <sennyeyaramis@gmail.com> Signed-off-by: Aramis Sennyey <aramiss@spotify.com>
This commit is contained in:
committed by
Aramis Sennyey
parent
998850c368
commit
38340678c3
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Update the OpenAPI spec to support the use of `openapi-generator`.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': minor
|
||||
---
|
||||
|
||||
Updates the ESLint config to ignore issues created by generated files in `**/src/generated/**`.
|
||||
@@ -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.
|
||||
@@ -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 <file> --output-directory <directory>`. This will create a new folder in `<directory>/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.
|
||||
@@ -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:^",
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -43,9 +42,21 @@ import {
|
||||
QueryEntitiesResponse,
|
||||
EntityFilterQuery,
|
||||
} from './types/api';
|
||||
import { DiscoveryApi } from './types/discovery';
|
||||
import { FetchApi } from './types/fetch';
|
||||
import { DefaultApiClient } from './generated/apis/DefaultApi.client';
|
||||
import { isQueryEntitiesInitialRequest } from './utils';
|
||||
import { ValidateEntity400Response } from './generated/models/ValidateEntity400Response.model';
|
||||
|
||||
const isResponseError = (e: Error): e is ResponseError => {
|
||||
return (e as any).response;
|
||||
};
|
||||
|
||||
const ignore404 = (responseError: Error) => {
|
||||
if (!isResponseError(responseError)) throw responseError;
|
||||
if (responseError.response.status === 404) {
|
||||
return undefined;
|
||||
}
|
||||
throw responseError;
|
||||
};
|
||||
|
||||
/**
|
||||
* A frontend and backend compatible client for communicating with the Backstage
|
||||
@@ -54,15 +65,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<string> };
|
||||
fetchApi?: { fetch: typeof fetch };
|
||||
}) {
|
||||
this.discoveryApi = options.discoveryApi;
|
||||
this.fetchApi = options.fetchApi || { fetch: crossFetch };
|
||||
this.apiClient = new DefaultApiClient(options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,13 +82,9 @@ export class CatalogClient implements CatalogApi {
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<GetEntityAncestorsResponse> {
|
||||
const { kind, namespace, name } = parseEntityRef(request.entityRef);
|
||||
return await this.requestRequired(
|
||||
'GET',
|
||||
`/entities/by-name/${encodeURIComponent(kind)}/${encodeURIComponent(
|
||||
namespace,
|
||||
)}/${encodeURIComponent(name)}/ancestry`,
|
||||
options,
|
||||
);
|
||||
return await this.apiClient
|
||||
.getEntityAncestryByName({ path: { kind, namespace, name } }, options)
|
||||
.then(r => r.json());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,11 +94,13 @@ export class CatalogClient implements CatalogApi {
|
||||
id: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Location | undefined> {
|
||||
return await this.requestOptional(
|
||||
'GET',
|
||||
`/locations/${encodeURIComponent(id)}`,
|
||||
options,
|
||||
);
|
||||
try {
|
||||
return await this.apiClient
|
||||
.getLocation({ path: { id } }, options)
|
||||
.then(r => r.json());
|
||||
} catch (e) {
|
||||
return ignore404(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,17 +118,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,22 +131,21 @@ 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,
|
||||
);
|
||||
const entities: Entity[] = await this.apiClient
|
||||
.getEntities(
|
||||
{
|
||||
query: {
|
||||
fields: fields.map(encodeURIComponent),
|
||||
limit,
|
||||
filter: this.getFilterValue(filter),
|
||||
offset,
|
||||
after,
|
||||
order: order ? encodedOrder : undefined,
|
||||
},
|
||||
},
|
||||
options,
|
||||
)
|
||||
.then(r => r.json());
|
||||
|
||||
const refCompare = (a: Entity, b: Entity) => {
|
||||
// in case field filtering is used, these fields might not be part of the response
|
||||
@@ -178,30 +179,9 @@ export class CatalogClient implements CatalogApi {
|
||||
request: GetEntitiesByRefsRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<GetEntitiesByRefsResponse> {
|
||||
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}` }),
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
const { items } = (await response.json()) as {
|
||||
items: Array<Entity | null>;
|
||||
};
|
||||
const { items } = await this.apiClient
|
||||
.getEntitiesByRefs({ body: request }, options)
|
||||
.then(r => r.json());
|
||||
|
||||
return { items: items.map(i => i ?? undefined) };
|
||||
}
|
||||
@@ -212,8 +192,10 @@ export class CatalogClient implements CatalogApi {
|
||||
async queryEntities(
|
||||
request: QueryEntitiesRequest = {},
|
||||
options?: CatalogRequestOptions,
|
||||
) {
|
||||
const params: string[] = [];
|
||||
): Promise<QueryEntitiesResponse> {
|
||||
const params: Partial<
|
||||
Parameters<typeof this.apiClient.getEntitiesByQuery>[0]['query']
|
||||
> = {};
|
||||
|
||||
if (isQueryEntitiesInitialRequest(request)) {
|
||||
const {
|
||||
@@ -223,45 +205,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<QueryEntitiesResponse>(
|
||||
'GET',
|
||||
`/entities/by-query${query}`,
|
||||
options,
|
||||
);
|
||||
return await this.apiClient
|
||||
.getEntitiesByQuery({ query: params }, options)
|
||||
.then(r => r.json());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,14 +250,13 @@ export class CatalogClient implements CatalogApi {
|
||||
entityRef: string | CompoundEntityRef,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Entity | undefined> {
|
||||
const { kind, namespace, name } = parseEntityRef(entityRef);
|
||||
return this.requestOptional(
|
||||
'GET',
|
||||
`/entities/by-name/${encodeURIComponent(kind)}/${encodeURIComponent(
|
||||
namespace,
|
||||
)}/${encodeURIComponent(name)}`,
|
||||
options,
|
||||
);
|
||||
try {
|
||||
return await this.apiClient
|
||||
.getEntityByName({ path: { ...parseEntityRef(entityRef) } }, options)
|
||||
.then(r => r.json());
|
||||
} catch (e) {
|
||||
return ignore404(e);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(freben): When we deprecate getEntityByName from the interface, we may
|
||||
@@ -293,34 +271,36 @@ export class CatalogClient implements CatalogApi {
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Entity | undefined> {
|
||||
const { kind, namespace = 'default', name } = compoundName;
|
||||
return this.requestOptional(
|
||||
'GET',
|
||||
`/entities/by-name/${encodeURIComponent(kind)}/${encodeURIComponent(
|
||||
namespace,
|
||||
)}/${encodeURIComponent(name)}`,
|
||||
options,
|
||||
);
|
||||
try {
|
||||
return await this.apiClient
|
||||
.getEntityByName(
|
||||
{
|
||||
path: {
|
||||
kind,
|
||||
namespace,
|
||||
name,
|
||||
},
|
||||
},
|
||||
options,
|
||||
)
|
||||
.then(r => r.json());
|
||||
} catch (e) {
|
||||
return ignore404(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc CatalogApi.refreshEntity}
|
||||
*/
|
||||
async refreshEntity(entityRef: string, options?: CatalogRequestOptions) {
|
||||
const response = await this.fetchApi.fetch(
|
||||
`${await this.discoveryApi.getBaseUrl('catalog')}/refresh`,
|
||||
await this.apiClient.refreshEntity(
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
body: {
|
||||
entityRef,
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ entityRef }),
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -331,14 +311,22 @@ export class CatalogClient implements CatalogApi {
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<GetEntityFacetsResponse> {
|
||||
const { filter = [], facets } = request;
|
||||
const params = this.getParams(filter);
|
||||
|
||||
for (const facet of facets) {
|
||||
params.push(`facet=${encodeURIComponent(facet)}`);
|
||||
try {
|
||||
return await this.apiClient
|
||||
.getEntityFacets(
|
||||
{
|
||||
query: {
|
||||
facet: facets,
|
||||
filter: this.getFilterValue(filter),
|
||||
},
|
||||
},
|
||||
options,
|
||||
)
|
||||
.then(r => r.json());
|
||||
} catch (e) {
|
||||
return ignore404(e) as any;
|
||||
}
|
||||
|
||||
const query = params.length ? `?${params.join('&')}` : '';
|
||||
return await this.requestOptional('GET', `/entity-facets${query}`, options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -350,25 +338,18 @@ export class CatalogClient implements CatalogApi {
|
||||
): Promise<AddLocationResponse> {
|
||||
const { type = 'url', target, dryRun } = request;
|
||||
|
||||
const response = await this.fetchApi.fetch(
|
||||
`${await this.discoveryApi.getBaseUrl('catalog')}/locations${
|
||||
dryRun ? '?dryRun=true' : ''
|
||||
}`,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
const { location, entities, exists } = await this.apiClient
|
||||
.createLocation(
|
||||
{
|
||||
body: {
|
||||
type,
|
||||
target,
|
||||
},
|
||||
query: { dryRun: dryRun ? 'true' : undefined },
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ type, target }),
|
||||
},
|
||||
);
|
||||
|
||||
if (response.status !== 201) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
|
||||
const { location, entities, exists } = await response.json();
|
||||
options,
|
||||
)
|
||||
.then(r => r.json());
|
||||
|
||||
if (!location) {
|
||||
throw new Error(`Location wasn't added: ${target}`);
|
||||
@@ -388,11 +369,9 @@ export class CatalogClient implements CatalogApi {
|
||||
locationRef: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Location | undefined> {
|
||||
const all: { data: Location }[] = await this.requestRequired(
|
||||
'GET',
|
||||
'/locations',
|
||||
options,
|
||||
);
|
||||
const all: { data: Location }[] = await this.apiClient
|
||||
.getLocations({}, options)
|
||||
.then(r => r.json());
|
||||
return all
|
||||
.map(r => r.data)
|
||||
.find(l => locationRef === stringifyLocationRef(l));
|
||||
@@ -405,11 +384,11 @@ export class CatalogClient implements CatalogApi {
|
||||
id: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<void> {
|
||||
await this.requestIgnored(
|
||||
'DELETE',
|
||||
`/locations/${encodeURIComponent(id)}`,
|
||||
options,
|
||||
);
|
||||
try {
|
||||
await this.apiClient.deleteLocation({ path: { id } }, options);
|
||||
} catch (e) {
|
||||
ignore404(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -419,11 +398,11 @@ export class CatalogClient implements CatalogApi {
|
||||
uid: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<void> {
|
||||
await this.requestIgnored(
|
||||
'DELETE',
|
||||
`/entities/by-uid/${encodeURIComponent(uid)}`,
|
||||
options,
|
||||
);
|
||||
try {
|
||||
await this.apiClient.deleteEntityByUid({ path: { uid } }, options);
|
||||
} catch (e) {
|
||||
ignore404(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -434,101 +413,38 @@ export class CatalogClient implements CatalogApi {
|
||||
locationRef: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<ValidateEntityResponse> {
|
||||
const response = await this.fetchApi.fetch(
|
||||
`${await this.discoveryApi.getBaseUrl('catalog')}/validate-entity`,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
try {
|
||||
await this.apiClient.validateEntity(
|
||||
{
|
||||
body: {
|
||||
entity,
|
||||
location: locationRef,
|
||||
},
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ entity, location: locationRef }),
|
||||
},
|
||||
);
|
||||
options,
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (response.status !== 400) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
const { errors = [] } = await response.json();
|
||||
|
||||
return {
|
||||
valid: false,
|
||||
errors,
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
|
||||
private async requestIgnored(
|
||||
method: string,
|
||||
path: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<void> {
|
||||
const url = `${await this.discoveryApi.getBaseUrl('catalog')}${path}`;
|
||||
const headers: Record<string, string> = options?.token
|
||||
? { Authorization: `Bearer ${options.token}` }
|
||||
: {};
|
||||
const response = await this.fetchApi.fetch(url, { method, headers });
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
private async requestRequired<T = any>(
|
||||
method: string,
|
||||
path: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<T> {
|
||||
const url = `${await this.discoveryApi.getBaseUrl('catalog')}${path}`;
|
||||
const headers: Record<string, string> = options?.token
|
||||
? { Authorization: `Bearer ${options.token}` }
|
||||
: {};
|
||||
const response = await this.fetchApi.fetch(url, { method, headers });
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
private async requestOptional(
|
||||
method: string,
|
||||
path: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<any | undefined> {
|
||||
const url = `${await this.discoveryApi.getBaseUrl('catalog')}${path}`;
|
||||
const headers: Record<string, string> = options?.token
|
||||
? { Authorization: `Bearer ${options.token}` }
|
||||
: {};
|
||||
const response = await this.fetchApi.fetch(url, { method, headers });
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
return undefined;
|
||||
} catch (e) {
|
||||
if (!isResponseError(e)) throw e;
|
||||
const { response, rawBody } = e;
|
||||
if (response.status !== 400) {
|
||||
throw e;
|
||||
}
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
// TODO: This needs to be fixed as is, this won't actually do anything.
|
||||
const { errors = [] } = JSON.parse(rawBody) as ValidateEntity400Response;
|
||||
|
||||
return await response.json();
|
||||
return {
|
||||
valid: false,
|
||||
errors,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private getParams(filter: EntityFilterQuery = []) {
|
||||
const params: 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
|
||||
// the "inner array" defined within a `filter` param corresponds to "allOf" filters
|
||||
private getFilterValue(filter: EntityFilterQuery = []) {
|
||||
const values: string[] = [];
|
||||
for (const filterItem of [filter].flat()) {
|
||||
const filterParts: string[] = [];
|
||||
for (const [key, value] of Object.entries(filterItem)) {
|
||||
@@ -544,9 +460,9 @@ export class CatalogClient implements CatalogApi {
|
||||
}
|
||||
|
||||
if (filterParts.length) {
|
||||
params.push(`filter=${filterParts.join(',')}`);
|
||||
values.push(filterParts.join(','));
|
||||
}
|
||||
}
|
||||
return params;
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,611 @@
|
||||
/*
|
||||
* 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 { ResponseError } from '@backstage/errors';
|
||||
|
||||
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<T> = Omit<Response, 'json'> & {
|
||||
json: () => Promise<T>;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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<string> };
|
||||
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<TypedResponse<AnalyzeLocationResponse>> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
|
||||
|
||||
const uriTemplate = `/analyze-location`;
|
||||
|
||||
const uri = parser.parse(uriTemplate).expand({});
|
||||
|
||||
const url = `${baseUrl}${uri}`;
|
||||
const response = await this.fetchApi.fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify(request.body),
|
||||
});
|
||||
if (response.ok) {
|
||||
return response;
|
||||
}
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<TypedResponse<CreateLocation201Response>> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
|
||||
|
||||
const uriTemplate = `/locations{?dryRun}`;
|
||||
|
||||
const uri = parser.parse(uriTemplate).expand({
|
||||
...request.query,
|
||||
});
|
||||
|
||||
const url = `${baseUrl}${uri}`;
|
||||
const response = await this.fetchApi.fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify(request.body),
|
||||
});
|
||||
if (response.ok) {
|
||||
return response;
|
||||
}
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a single entity by UID.
|
||||
* @param uid
|
||||
*/
|
||||
public async deleteEntityByUid(
|
||||
// @ts-ignore
|
||||
request: {
|
||||
path: {
|
||||
uid: string;
|
||||
};
|
||||
},
|
||||
options?: RequestOptions,
|
||||
): Promise<TypedResponse<void>> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
|
||||
|
||||
const uriTemplate = `/entities/by-uid/{uid}`;
|
||||
|
||||
const uri = parser.parse(uriTemplate).expand({
|
||||
uid: request.path.uid,
|
||||
});
|
||||
|
||||
const url = `${baseUrl}${uri}`;
|
||||
const response = await this.fetchApi.fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (response.ok) {
|
||||
return response;
|
||||
}
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a location by id.
|
||||
* @param id
|
||||
*/
|
||||
public async deleteLocation(
|
||||
// @ts-ignore
|
||||
request: {
|
||||
path: {
|
||||
id: string;
|
||||
};
|
||||
},
|
||||
options?: RequestOptions,
|
||||
): Promise<TypedResponse<void>> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
|
||||
|
||||
const uriTemplate = `/locations/{id}`;
|
||||
|
||||
const uri = parser.parse(uriTemplate).expand({
|
||||
id: request.path.id,
|
||||
});
|
||||
|
||||
const url = `${baseUrl}${uri}`;
|
||||
const response = await this.fetchApi.fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (response.ok) {
|
||||
return response;
|
||||
}
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<string>;
|
||||
limit?: number;
|
||||
filter?: Array<string>;
|
||||
offset?: number;
|
||||
after?: string;
|
||||
order?: Array<string>;
|
||||
};
|
||||
},
|
||||
options?: RequestOptions,
|
||||
): Promise<TypedResponse<Array<Entity>>> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
|
||||
|
||||
const uriTemplate = `/entities{?fields,limit,filter*,offset,after,order*}`;
|
||||
|
||||
const uri = parser.parse(uriTemplate).expand({
|
||||
...request.query,
|
||||
});
|
||||
|
||||
const url = `${baseUrl}${uri}`;
|
||||
const response = await this.fetchApi.fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'GET',
|
||||
});
|
||||
if (response.ok) {
|
||||
return response;
|
||||
}
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<string>;
|
||||
limit?: number;
|
||||
orderField?: Array<string>;
|
||||
cursor?: string;
|
||||
filter?: Array<string>;
|
||||
fullTextFilterTerm?: string;
|
||||
fullTextFilterFields?: Array<string>;
|
||||
};
|
||||
},
|
||||
options?: RequestOptions,
|
||||
): Promise<TypedResponse<EntitiesQueryResponse>> {
|
||||
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,
|
||||
});
|
||||
|
||||
const url = `${baseUrl}${uri}`;
|
||||
const response = await this.fetchApi.fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'GET',
|
||||
});
|
||||
if (response.ok) {
|
||||
return response;
|
||||
}
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a batch set of entities given an array of entityRefs.
|
||||
* @param getEntitiesByRefsRequest
|
||||
*/
|
||||
public async getEntitiesByRefs(
|
||||
// @ts-ignore
|
||||
request: {
|
||||
body: GetEntitiesByRefsRequest;
|
||||
},
|
||||
options?: RequestOptions,
|
||||
): Promise<TypedResponse<EntitiesBatchResponse>> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
|
||||
|
||||
const uriTemplate = `/entities/by-refs`;
|
||||
|
||||
const uri = parser.parse(uriTemplate).expand({});
|
||||
|
||||
const url = `${baseUrl}${uri}`;
|
||||
const response = await this.fetchApi.fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify(request.body),
|
||||
});
|
||||
if (response.ok) {
|
||||
return response;
|
||||
}
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<TypedResponse<EntityAncestryResponse>> {
|
||||
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,
|
||||
});
|
||||
|
||||
const url = `${baseUrl}${uri}`;
|
||||
const response = await this.fetchApi.fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'GET',
|
||||
});
|
||||
if (response.ok) {
|
||||
return response;
|
||||
}
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<TypedResponse<Entity>> {
|
||||
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,
|
||||
});
|
||||
|
||||
const url = `${baseUrl}${uri}`;
|
||||
const response = await this.fetchApi.fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'GET',
|
||||
});
|
||||
if (response.ok) {
|
||||
return response;
|
||||
}
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single entity by the UID.
|
||||
* @param uid
|
||||
*/
|
||||
public async getEntityByUid(
|
||||
// @ts-ignore
|
||||
request: {
|
||||
path: {
|
||||
uid: string;
|
||||
};
|
||||
},
|
||||
options?: RequestOptions,
|
||||
): Promise<TypedResponse<Entity>> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
|
||||
|
||||
const uriTemplate = `/entities/by-uid/{uid}`;
|
||||
|
||||
const uri = parser.parse(uriTemplate).expand({
|
||||
uid: request.path.uid,
|
||||
});
|
||||
|
||||
const url = `${baseUrl}${uri}`;
|
||||
const response = await this.fetchApi.fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'GET',
|
||||
});
|
||||
if (response.ok) {
|
||||
return response;
|
||||
}
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<string>;
|
||||
filter?: Array<string>;
|
||||
};
|
||||
},
|
||||
options?: RequestOptions,
|
||||
): Promise<TypedResponse<EntityFacetsResponse>> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
|
||||
|
||||
const uriTemplate = `/entity-facets{?facet*,filter*}`;
|
||||
|
||||
const uri = parser.parse(uriTemplate).expand({
|
||||
...request.query,
|
||||
});
|
||||
|
||||
const url = `${baseUrl}${uri}`;
|
||||
const response = await this.fetchApi.fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'GET',
|
||||
});
|
||||
if (response.ok) {
|
||||
return response;
|
||||
}
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a location by id.
|
||||
* @param id
|
||||
*/
|
||||
public async getLocation(
|
||||
// @ts-ignore
|
||||
request: {
|
||||
path: {
|
||||
id: string;
|
||||
};
|
||||
},
|
||||
options?: RequestOptions,
|
||||
): Promise<TypedResponse<Location>> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
|
||||
|
||||
const uriTemplate = `/locations/{id}`;
|
||||
|
||||
const uri = parser.parse(uriTemplate).expand({
|
||||
id: request.path.id,
|
||||
});
|
||||
|
||||
const url = `${baseUrl}${uri}`;
|
||||
const response = await this.fetchApi.fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'GET',
|
||||
});
|
||||
if (response.ok) {
|
||||
return response;
|
||||
}
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all locations
|
||||
*/
|
||||
public async getLocations(
|
||||
// @ts-ignore
|
||||
request: {},
|
||||
options?: RequestOptions,
|
||||
): Promise<TypedResponse<Array<GetLocations200ResponseInner>>> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
|
||||
|
||||
const uriTemplate = `/locations`;
|
||||
|
||||
const uri = parser.parse(uriTemplate).expand({});
|
||||
|
||||
const url = `${baseUrl}${uri}`;
|
||||
const response = await this.fetchApi.fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'GET',
|
||||
});
|
||||
if (response.ok) {
|
||||
return response;
|
||||
}
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the entity related to entityRef.
|
||||
* @param refreshEntityRequest
|
||||
*/
|
||||
public async refreshEntity(
|
||||
// @ts-ignore
|
||||
request: {
|
||||
body: RefreshEntityRequest;
|
||||
},
|
||||
options?: RequestOptions,
|
||||
): Promise<TypedResponse<void>> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
|
||||
|
||||
const uriTemplate = `/refresh`;
|
||||
|
||||
const uri = parser.parse(uriTemplate).expand({});
|
||||
|
||||
const url = `${baseUrl}${uri}`;
|
||||
const response = await this.fetchApi.fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify(request.body),
|
||||
});
|
||||
if (response.ok) {
|
||||
return response;
|
||||
}
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a passed in entity has no errors in schema.
|
||||
* @param validateEntityRequest
|
||||
*/
|
||||
public async validateEntity(
|
||||
// @ts-ignore
|
||||
request: {
|
||||
body: ValidateEntityRequest;
|
||||
},
|
||||
options?: RequestOptions,
|
||||
): Promise<TypedResponse<void>> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
|
||||
|
||||
const uriTemplate = `/validate-entity`;
|
||||
|
||||
const uri = parser.parse(uriTemplate).expand({});
|
||||
|
||||
const url = `${baseUrl}${uri}`;
|
||||
const response = await this.fetchApi.fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify(request.body),
|
||||
});
|
||||
if (response.ok) {
|
||||
return response;
|
||||
}
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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';
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<AnalyzeLocationEntityField>;
|
||||
entity: RecursivePartialEntity;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<AnalyzeLocationGenerateEntity>;
|
||||
existingEntityFiles: Array<AnalyzeLocationExistingEntity>;
|
||||
}
|
||||
@@ -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<Entity>;
|
||||
location: Location;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<NullableEntity>;
|
||||
}
|
||||
@@ -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<Entity>;
|
||||
totalItems: number;
|
||||
pageInfo: EntitiesQueryResponsePageInfo;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<EntityRelation>;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -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<EntityAncestryResponseItemsInner>;
|
||||
rootEntityRef: string;
|
||||
}
|
||||
+22
@@ -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<string>;
|
||||
entity: Entity;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<EntityFacet> };
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<EntityLink>;
|
||||
/**
|
||||
* A list of single-valued strings, to for example classify catalog entities in various ways.
|
||||
*/
|
||||
tags?: Array<string>;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<string>;
|
||||
fields?: Array<string>;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<EntityRelation>;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -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<RecursivePartialEntityRelation>;
|
||||
}
|
||||
@@ -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<EntityLink>;
|
||||
/**
|
||||
* A list of single-valued strings, to for example classify catalog entities in various ways.
|
||||
*/
|
||||
tags?: Array<string>;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -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<EntityLink>;
|
||||
/**
|
||||
* A list of single-valued strings, to for example classify catalog entities in various ways.
|
||||
*/
|
||||
tags?: Array<string>;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<ValidateEntity400ResponseErrorsInner>;
|
||||
}
|
||||
+21
@@ -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;
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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<string>;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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 ?? []),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -42,6 +42,11 @@ export class ResponseError extends Error {
|
||||
*/
|
||||
readonly body: ErrorResponseBody;
|
||||
|
||||
/**
|
||||
* The unparsed possibly JSON error body. Will be set even if the returned error doesn't match {@link ErrorResponseBody}.
|
||||
*/
|
||||
readonly rawBody: string;
|
||||
|
||||
/**
|
||||
* The Error cause, as seen by the remote server. This is parsed out of the
|
||||
* JSON error body.
|
||||
@@ -61,9 +66,22 @@ export class ResponseError extends Error {
|
||||
* been consumed before.
|
||||
*/
|
||||
static async fromResponse(
|
||||
response: ConsumedResponse & { text(): Promise<string> },
|
||||
response: ConsumedResponse & { text(): Promise<string>; bodyUsed: boolean },
|
||||
): Promise<ResponseError> {
|
||||
const data = await parseErrorResponseBody(response);
|
||||
let rawBody = '';
|
||||
try {
|
||||
rawBody = await response.text();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const data = await parseErrorResponseBody(
|
||||
// TS isn't smart enough to know that this is done under the hood,
|
||||
response as ConsumedResponse & {
|
||||
bodyUsed: true;
|
||||
},
|
||||
rawBody,
|
||||
);
|
||||
|
||||
const status = data.response.statusCode || response.status;
|
||||
const statusText = data.error.name || response.statusText;
|
||||
@@ -75,6 +93,7 @@ export class ResponseError extends Error {
|
||||
response,
|
||||
data,
|
||||
cause,
|
||||
rawBody,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -82,6 +101,7 @@ export class ResponseError extends Error {
|
||||
message: string;
|
||||
response: ConsumedResponse;
|
||||
data: ErrorResponseBody;
|
||||
rawBody: string;
|
||||
cause: Error;
|
||||
}) {
|
||||
super(props.message);
|
||||
@@ -89,5 +109,6 @@ export class ResponseError extends Error {
|
||||
this.response = props.response;
|
||||
this.body = props.data;
|
||||
this.cause = props.cause;
|
||||
this.rawBody = props.rawBody;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,10 +54,20 @@ export type ErrorResponseBody = {
|
||||
* @param response - The response of a failed request
|
||||
*/
|
||||
export async function parseErrorResponseBody(
|
||||
response: ConsumedResponse & { text(): Promise<string> },
|
||||
response: ConsumedResponse & { bodyUsed: true },
|
||||
rawBody: string,
|
||||
): Promise<ErrorResponseBody>;
|
||||
export async function parseErrorResponseBody(
|
||||
response: ConsumedResponse & { text(): Promise<string>; bodyUsed: false },
|
||||
): Promise<ErrorResponseBody>;
|
||||
export async function parseErrorResponseBody(
|
||||
response:
|
||||
| (ConsumedResponse & { bodyUsed: true })
|
||||
| (ConsumedResponse & { text(): Promise<string>; bodyUsed: false }),
|
||||
rawBody?: string,
|
||||
): Promise<ErrorResponseBody> {
|
||||
try {
|
||||
const text = await response.text();
|
||||
const text = !response.bodyUsed ? await response.text() : rawBody;
|
||||
if (text) {
|
||||
if (
|
||||
response.headers.get('content-type')?.startsWith('application/json')
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "../../node_modules/@openapitools/openapi-generator-cli/config.schema.json",
|
||||
"spaces": 2,
|
||||
"generator-cli": {
|
||||
"version": "6.5.0"
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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 <paths...>')
|
||||
.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 <file>')
|
||||
.requiredOption('--output-directory <directory>')
|
||||
.action(
|
||||
lazy(() =>
|
||||
import('./openapi/client/generate').then(m => m.singleCommand),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function registerCommands(program: Command) {
|
||||
|
||||
@@ -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<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
];
|
||||
|
||||
+3
-3
@@ -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';
|
||||
|
||||
+3
-3
@@ -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);
|
||||
@@ -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
|
||||
@@ -0,0 +1,120 @@
|
||||
{{>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';
|
||||
import {ResponseError} from '@backstage/errors';
|
||||
|
||||
{{#imports}}
|
||||
import { {{classname}} } from '{{filename}}.model{{importFileExtension}}';
|
||||
{{/imports}}
|
||||
|
||||
type TypedResponse<T> = Omit<Response, 'json'> & {
|
||||
json: () => Promise<T>;
|
||||
};
|
||||
|
||||
{{#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<string> };
|
||||
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<TypedResponse<{{{returnType}}} {{^returnType}}void{{/returnType}}>> {
|
||||
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}}
|
||||
})
|
||||
|
||||
const url = `${baseUrl}${uri}`;
|
||||
const response = await this.fetchApi.fetch(url, {
|
||||
headers: {
|
||||
{{#hasHeaderParams}}
|
||||
...request.header,
|
||||
{{/hasHeaderParams}}
|
||||
'Content-Type': 'application/json',
|
||||
...(options?.token && { Authorization: `Bearer ${options?.token}` }),
|
||||
},
|
||||
method: '{{httpMethod}}',
|
||||
{{#hasBodyParam}} body: JSON.stringify(request.body), {{/hasBodyParam}}
|
||||
});
|
||||
if(response.ok){
|
||||
return response;
|
||||
};
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
{{/operation}}
|
||||
}
|
||||
{{/operations}}
|
||||
@@ -0,0 +1,3 @@
|
||||
//
|
||||
|
||||
export * from './DefaultApi.client';
|
||||
@@ -0,0 +1,3 @@
|
||||
//
|
||||
|
||||
export * from './apis'
|
||||
@@ -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}}
|
||||
@@ -0,0 +1,7 @@
|
||||
//
|
||||
|
||||
{{#models}}
|
||||
{{#model}}
|
||||
export * from '{{{ importPath }}}.model{{importFileExtension}}'
|
||||
{{/model}}
|
||||
{{/models}}
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
{{#servers}}
|
||||
{{#-last}}
|
||||
export const pluginId = "{{url}}";
|
||||
{{/-last}}
|
||||
{{/servers}}
|
||||
@@ -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<string>;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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'],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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' } },
|
||||
|
||||
@@ -3454,6 +3454,7 @@ __metadata:
|
||||
"@backstage/errors": "workspace:^"
|
||||
cross-fetch: ^4.0.0
|
||||
msw: ^1.0.0
|
||||
uri-template: ^2.0.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -3588,6 +3589,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
|
||||
@@ -9733,6 +9735,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
|
||||
@@ -13773,7 +13776,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:
|
||||
@@ -25759,6 +25762,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"
|
||||
@@ -36282,6 +36307,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"
|
||||
@@ -43177,6 +43209,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"
|
||||
@@ -44086,9 +44127,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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user