From 8f1cc99d3b6020e42d04d5523a3466a9834f2eb5 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 23 Feb 2022 11:46:33 +0100 Subject: [PATCH 1/9] chore: entityName -> entityRef Signed-off-by: blam --- plugins/scaffolder/src/api.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index 62fc06a6db..594ca74aa4 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { EntityName } from '@backstage/catalog-model'; +import { + EntityName, + EntityRef, + parseEntityRef, +} from '@backstage/catalog-model'; import { createApiRef, DiscoveryApi, @@ -70,7 +74,7 @@ export type CustomField = { */ export interface ScaffolderApi { getTemplateParameterSchema( - templateName: EntityName, + templateRef: EntityRef, ): Promise; /** @@ -136,14 +140,15 @@ export class ScaffolderClient implements ScaffolderApi { } async getTemplateParameterSchema( - templateName: EntityName, + templateRef: EntityRef, ): Promise { - const { namespace, kind, name } = templateName; + const { namespace, kind, name } = parseEntityRef(templateRef); const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); const templatePath = [namespace, kind, name] .map(s => encodeURIComponent(s)) .join('/'); + const url = `${baseUrl}/v2/templates/${templatePath}/parameter-schema`; const response = await this.fetchApi.fetch(url); From 6bf5faa3f2127471b15a0c03ef1d5995d3861813 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 23 Feb 2022 15:25:52 +0100 Subject: [PATCH 2/9] chore: reworking where the types are stored and where the client is created Signed-off-by: blam --- plugins/scaffolder/src/api.ts | 117 ++++++++----------------------- plugins/scaffolder/src/index.ts | 2 +- plugins/scaffolder/src/plugin.ts | 9 +-- plugins/scaffolder/src/types.ts | 84 +++++++++++++++++++++- 4 files changed, 117 insertions(+), 95 deletions(-) diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index 594ca74aa4..02d09a7a07 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - EntityName, - EntityRef, - parseEntityRef, -} from '@backstage/catalog-model'; +import { parseEntityRef } from '@backstage/catalog-model'; import { createApiRef, DiscoveryApi, @@ -26,11 +22,20 @@ import { } from '@backstage/core-plugin-api'; import { ResponseError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { JsonObject, JsonValue, Observable } from '@backstage/types'; -import { Field, FieldValidation } from '@rjsf/core'; +import { Observable } from '@backstage/types'; import qs from 'qs'; import ObservableImpl from 'zen-observable'; -import { ListActionsResponse, ScaffolderTask, Status } from './types'; +import { + ListActionsResponse, + LogEvent, + ScaffolderApi, + TemplateParameterSchema, + ScaffolderScaffoldOptions, + ScaffolderScaffoldResponse, + ScaffolderStreamLogsOptions, + ScaffolderGetIntegrationsListOptions, + ScaffolderGetIntegrationsListResponse, +} from './types'; /** * Utility API reference for the {@link ScaffolderApi}. @@ -41,70 +46,6 @@ export const scaffolderApiRef = createApiRef({ id: 'plugin.scaffolder.service', }); -type TemplateParameterSchema = { - title: string; - steps: Array<{ - title: string; - schema: JsonObject; - }>; -}; - -export type LogEvent = { - type: 'log' | 'completion'; - body: { - message: string; - stepId?: string; - status?: Status; - }; - createdAt: string; - id: string; - taskId: string; -}; - -export type CustomField = { - name: string; - component: Field; - validation: (data: JsonValue, field: FieldValidation) => void; -}; - -/** - * An API to interact with the scaffolder backend. - * - * @public - */ -export interface ScaffolderApi { - getTemplateParameterSchema( - templateRef: EntityRef, - ): Promise; - - /** - * Executes the scaffolding of a component, given a template and its - * parameter values. - * - * @param templateName - Name of the Template entity for the scaffolder to use. New project is going to be created out of this template. - * @param values - Parameters for the template, e.g. name, description - * @param secrets - Optional secrets to pass to as the secrets parameter to the template. - */ - scaffold( - templateName: string, - values: Record, - secrets?: Record, - ): Promise; - - getTask(taskId: string): Promise; - - getIntegrationsList(options: { - allowedHosts: string[]; - }): Promise<{ type: string; title: string; host: string }[]>; - - /** - * Returns a list of all installed actions. - */ - listActions(): Promise; - - streamLogs(options: { taskId: string; after?: number }): Observable; -} - /** * An API to interact with the scaffolder backend. * @@ -128,8 +69,10 @@ export class ScaffolderClient implements ScaffolderApi { this.useLongPollingLogs = options.useLongPollingLogs ?? false; } - async getIntegrationsList(options: { allowedHosts: string[] }) { - return [ + async getIntegrationsList( + options: ScaffolderGetIntegrationsListOptions, + ): Promise { + const integrations = [ ...this.scmIntegrationsApi.azure.list(), ...this.scmIntegrationsApi.bitbucket.list(), ...this.scmIntegrationsApi.github.list(), @@ -137,10 +80,14 @@ export class ScaffolderClient implements ScaffolderApi { ] .map(c => ({ type: c.type, title: c.title, host: c.config.host })) .filter(c => options.allowedHosts.includes(c.host)); + + return { + integrations, + }; } async getTemplateParameterSchema( - templateRef: EntityRef, + templateRef: string, ): Promise { const { namespace, kind, name } = parseEntityRef(templateRef); @@ -164,15 +111,14 @@ export class ScaffolderClient implements ScaffolderApi { * Executes the scaffolding of a component, given a template and its * parameter values. * - * @param templateName - Template name for the scaffolder to use. New project is going to be created out of this template. - * @param values - Parameters for the template, e.g. name, description - * @param secrets - Optional secrets to pass to as the secrets parameter to the template. + * @param options.templateName - Template name for the scaffolder to use. New project is going to be created out of this template. + * @param options.values - Parameters for the template, e.g. name, description + * @param options.secrets - Optional secrets to pass to as the secrets parameter to the template. */ async scaffold( - templateName: string, - values: Record, - secrets: Record = {}, - ): Promise { + options: ScaffolderScaffoldOptions, + ): Promise { + const { templateName, values, secrets = {} } = options; const url = `${await this.discoveryApi.getBaseUrl('scaffolder')}/v2/tasks`; const response = await this.fetchApi.fetch(url, { method: 'POST', @@ -193,7 +139,7 @@ export class ScaffolderClient implements ScaffolderApi { } const { id } = (await response.json()) as { id: string }; - return id; + return { jobId: id }; } async getTask(taskId: string) { @@ -208,10 +154,7 @@ export class ScaffolderClient implements ScaffolderApi { return await response.json(); } - streamLogs(options: { - taskId: string; - after?: number; - }): Observable { + streamLogs(options: ScaffolderStreamLogsOptions): Observable { if (this.useLongPollingLogs) { return this.streamLogsPolling(options); } diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index 34091b3fea..17c48e55ac 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -21,7 +21,7 @@ */ export { scaffolderApiRef, ScaffolderClient } from './api'; -export type { ScaffolderApi } from './api'; +export * from './types'; export { createScaffolderFieldExtension, ScaffolderFieldExtensions, diff --git a/plugins/scaffolder/src/plugin.ts b/plugins/scaffolder/src/plugin.ts index ccf8d2ee34..c84d2bb73c 100644 --- a/plugins/scaffolder/src/plugin.ts +++ b/plugins/scaffolder/src/plugin.ts @@ -64,11 +64,12 @@ export const scaffolderPlugin = createPlugin({ }, }); +export const EntityPickerExtension = createScaffolderFieldExtension({ + component: EntityPicker, + name: 'EntityPicker', +}); export const EntityPickerFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - component: EntityPicker, - name: 'EntityPicker', - }), + EntityPickerExtension, ); export const EntityNamePickerFieldExtension = scaffolderPlugin.provide( diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index 291f680710..9b65bc5e5e 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { JSONSchema } from '@backstage/catalog-model'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; +import { JsonObject, Observable } from '@backstage/types'; +import { JSONSchema7 } from 'json-schema'; export type Status = 'open' | 'processing' | 'failed' | 'completed' | 'skipped'; export type JobStatus = 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED'; @@ -51,8 +52,8 @@ export type ListActionsResponse = Array<{ id: string; description?: string; schema?: { - input?: JSONSchema; - output?: JSONSchema; + input?: JSONSchema7; + output?: JSONSchema7; }; }>; @@ -72,3 +73,80 @@ export type TaskOutput = { } & { [key: string]: unknown; }; + +export type TemplateParameterSchema = { + title: string; + steps: Array<{ + title: string; + schema: JsonObject; + }>; +}; + +export type LogEvent = { + type: 'log' | 'completion'; + body: { + message: string; + stepId?: string; + status?: Status; + }; + createdAt: string; + id: string; + taskId: string; +}; +export interface ScaffolderScaffoldOptions { + templateName: string; + values: Record; + secrets?: Record; +} + +export interface ScaffolderScaffoldResponse { + jobId: string; +} + +export interface ScaffolderGetIntegrationsListOptions { + allowedHosts: string[]; +} + +export interface ScaffolderGetIntegrationsListResponse { + integrations: { type: string; title: string; host: string }[]; +} + +export interface ScaffolderStreamLogsOptions { + taskId: string; + after?: number; +} +/** + * An API to interact with the scaffolder backend. + * + * @public + */ +export interface ScaffolderApi { + getTemplateParameterSchema( + templateRef: string, + ): Promise; + + /** + * Executes the scaffolding of a component, given a template and its + * parameter values. + * + * @param options.templateName - Name of the Template entity for the scaffolder to use. New project is going to be created out of this template. + * @param options.values - Parameters for the template, e.g. name, description + * @param options.secrets - Optional secrets to pass to as the secrets parameter to the template. + */ + scaffold( + options: ScaffolderScaffoldOptions, + ): Promise; + + getTask(taskId: string): Promise; + + getIntegrationsList( + options: ScaffolderGetIntegrationsListOptions, + ): Promise; + + /** + * Returns a list of all installed actions. + */ + listActions(): Promise; + + streamLogs(options: ScaffolderStreamLogsOptions): Observable; +} From 12c5e791f0487ed3de05d104ed401f2b56b67791 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 23 Feb 2022 15:56:38 +0100 Subject: [PATCH 3/9] chore: some more migrations and deprecations Signed-off-by: blam --- plugins/scaffolder/src/api.test.ts | 4 +++- plugins/scaffolder/src/api.ts | 3 ++- .../components/ActionsPage/ActionsPage.test.tsx | 3 ++- .../src/components/ActionsPage/ActionsPage.tsx | 11 +++++------ .../fields/RepoUrlPicker/RepoUrlPicker.test.tsx | 14 +++++++++----- .../fields/RepoUrlPicker/RepoUrlPickerHost.tsx | 12 +++++++----- 6 files changed, 28 insertions(+), 19 deletions(-) diff --git a/plugins/scaffolder/src/api.test.ts b/plugins/scaffolder/src/api.test.ts index 369722ec8f..4e47c7f239 100644 --- a/plugins/scaffolder/src/api.test.ts +++ b/plugins/scaffolder/src/api.test.ts @@ -62,7 +62,9 @@ describe('api', () => { 'dev.azure.com', 'bitbucket.org', ]; - const integrations = await apiClient.getIntegrationsList({ allowedHosts }); + const { integrations } = await apiClient.getIntegrationsList({ + allowedHosts, + }); integrations.forEach(integration => expect(allowedHosts).toContain(integration.host), ); diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index 02d09a7a07..8a29daa95d 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -35,6 +35,7 @@ import { ScaffolderStreamLogsOptions, ScaffolderGetIntegrationsListOptions, ScaffolderGetIntegrationsListResponse, + ScaffolderTask, } from './types'; /** @@ -142,7 +143,7 @@ export class ScaffolderClient implements ScaffolderApi { return { jobId: id }; } - async getTask(taskId: string) { + async getTask(taskId: string): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); const url = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}`; diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx index ce0354b817..aff6681037 100644 --- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx +++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx @@ -14,11 +14,12 @@ * limitations under the License. */ import React from 'react'; -import { ScaffolderApi, scaffolderApiRef } from '../../api'; +import { scaffolderApiRef } from '../../api'; import { ActionsPage } from './ActionsPage'; import { rootRouteRef } from '../../routes'; import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; import { ApiProvider } from '@backstage/core-app-api'; +import { ScaffolderApi } from '../../types'; const scaffolderApiMock: jest.Mocked = { scaffold: jest.fn(), diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx index 4542979e73..78efaa43ff 100644 --- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx +++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx @@ -28,8 +28,7 @@ import { TableRow, makeStyles, } from '@material-ui/core'; -import { JSONSchema } from '@backstage/catalog-model'; -import { JSONSchema7Definition } from 'json-schema'; +import { JSONSchema7, JSONSchema7Definition } from 'json-schema'; import classNames from 'classnames'; import { useApi } from '@backstage/core-plugin-api'; @@ -87,7 +86,7 @@ export const ActionsPage = () => { ); } - const formatRows = (input: JSONSchema) => { + const formatRows = (input: JSONSchema7) => { const properties = input.properties; if (!properties) { return undefined; @@ -95,7 +94,7 @@ export const ActionsPage = () => { return Object.entries(properties).map(entry => { const [key] = entry; - const props = entry[1] as unknown as JSONSchema; + const props = entry[1] as unknown as JSONSchema7; const codeClassname = classNames(classes.code, { [classes.codeRequired]: input.required?.includes(key), }); @@ -115,7 +114,7 @@ export const ActionsPage = () => { }); }; - const renderTable = (input: JSONSchema) => { + const renderTable = (input: JSONSchema7) => { if (!input.properties) { return undefined; } @@ -145,7 +144,7 @@ export const ActionsPage = () => { <> {name} {input.map((i, index) => ( -
{renderTable(i as unknown as JSONSchema)}
+
{renderTable(i as unknown as JSONSchema7)}
))} ); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx index 829495cdda..22043d5761 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx @@ -23,7 +23,9 @@ import { scmAuthApiRef, ScmAuthApi, } from '@backstage/integration-react'; -import { scaffolderApiRef, ScaffolderApi } from '../../../api'; +import { scaffolderApiRef } from '../../../api'; +import { ScaffolderApi } from '../../../types'; + import { SecretsContextProvider, SecretsContext, @@ -32,10 +34,12 @@ import { act, fireEvent } from '@testing-library/react'; describe('RepoUrlPicker', () => { const mockScaffolderApi: Partial = { - getIntegrationsList: async () => [ - { host: 'github.com', type: 'github', title: 'github.com' }, - { host: 'dev.azure.com', type: 'azure', title: 'dev.azure.com' }, - ], + getIntegrationsList: async () => ({ + integrations: [ + { host: 'github.com', type: 'github', title: 'github.com' }, + { host: 'dev.azure.com', type: 'azure', title: 'dev.azure.com' }, + ], + }), }; const mockIntegrationsApi: Partial = { diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx index ec09494a1b..b9762cd69d 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx @@ -30,11 +30,13 @@ export const RepoUrlPickerHost = (props: { const { host, hosts, onChange, rawErrors } = props; const scaffolderApi = useApi(scaffolderApiRef); - const { value: integrations, loading } = useAsync(async () => { - return await scaffolderApi.getIntegrationsList({ - allowedHosts: hosts ?? [], - }); - }); + const { value: { integrations } = { integrations: [] }, loading } = useAsync( + async () => { + return await scaffolderApi.getIntegrationsList({ + allowedHosts: hosts ?? [], + }); + }, + ); useEffect(() => { // If there is no host chosen currently From 974a7917f1bcba866c798dc6a16a338a81560ef5 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 23 Feb 2022 16:46:47 +0100 Subject: [PATCH 4/9] chore: reworking some types Signed-off-by: blam --- plugins/scaffolder/api-report.md | 187 ++++++++++++++---- plugins/scaffolder/src/api.ts | 2 +- .../src/components/TaskPage/TaskPage.tsx | 10 +- .../src/components/TaskPage/TaskPageLinks.tsx | 4 +- .../TemplatePage/TemplatePage.test.tsx | 3 +- .../components/TemplatePage/TemplatePage.tsx | 21 +- .../src/components/hooks/useEventStream.ts | 17 +- plugins/scaffolder/src/types.ts | 38 ++-- 8 files changed, 194 insertions(+), 88 deletions(-) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index fb3fd830f7..95fc6f9aa2 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -12,7 +12,6 @@ import { ComponentProps } from 'react'; import { ComponentType } from 'react'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; -import { EntityName } from '@backstage/catalog-model'; import { Extension } from '@backstage/core-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { FetchApi } from '@backstage/core-plugin-api'; @@ -20,7 +19,7 @@ import { FieldProps } from '@rjsf/core'; import { FieldValidation } from '@rjsf/core'; import { IconButton } from '@material-ui/core'; import { JsonObject } from '@backstage/types'; -import { JSONSchema } from '@backstage/catalog-model'; +import { JSONSchema7 } from 'json-schema'; import { Observable } from '@backstage/types'; import { default as React_2 } from 'react'; import { ReactNode } from 'react'; @@ -122,6 +121,48 @@ export type FieldExtensionOptions< validation?: CustomFieldValidator; }; +// Warning: (ae-missing-release-tag) "JobStatus" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type JobStatus = 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED'; + +// Warning: (ae-missing-release-tag) "ListActionsResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ListActionsResponse = Array<{ + id: string; + description?: string; + schema?: { + input?: JSONSchema7; + output?: JSONSchema7; + }; +}>; + +// Warning: (ae-missing-release-tag) "LogEvent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type LogEvent = { + type: 'log' | 'completion'; + body: { + message: string; + stepId?: string; + status?: LogEventStatus; + }; + createdAt: string; + id: string; + taskId: string; +}; + +// Warning: (ae-missing-release-tag) "LogEventStatus" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type LogEventStatus = + | 'open' + | 'processing' + | 'failed' + | 'completed' + | 'skipped'; + // Warning: (ae-missing-release-tag) "OwnedEntityPicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -210,34 +251,24 @@ export interface RepoUrlPickerUiOptions { // @public export interface ScaffolderApi { // (undocumented) - getIntegrationsList(options: { allowedHosts: string[] }): Promise< - { - type: string; - title: string; - host: string; - }[] - >; - // Warning: (ae-forgotten-export) The symbol "ScaffolderTask" needs to be exported by the entry point index.d.ts - // + getIntegrationsList( + options: ScaffolderGetIntegrationsListOptions, + ): Promise; // (undocumented) getTask(taskId: string): Promise; - // Warning: (ae-forgotten-export) The symbol "TemplateParameterSchema" needs to be exported by the entry point index.d.ts - // // (undocumented) getTemplateParameterSchema( - templateName: EntityName, + templateRef: string, ): Promise; - // Warning: (ae-forgotten-export) The symbol "ListActionsResponse" needs to be exported by the entry point index.d.ts listActions(): Promise; + // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters + // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters + // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters scaffold( - templateName: string, - values: Record, - secrets?: Record, - ): Promise; - // Warning: (ae-forgotten-export) The symbol "LogEvent" needs to be exported by the entry point index.d.ts - // + options: ScaffolderScaffoldOptions, + ): Promise; // (undocumented) - streamLogs(options: { taskId: string; after?: number }): Observable; + streamLogs(options: ScaffolderStreamLogsOptions): Observable; } // @public @@ -252,28 +283,25 @@ export class ScaffolderClient implements ScaffolderApi { useLongPollingLogs?: boolean; }); // (undocumented) - getIntegrationsList(options: { allowedHosts: string[] }): Promise< - { - type: string; - title: string; - host: string; - }[] - >; + getIntegrationsList( + options: ScaffolderGetIntegrationsListOptions, + ): Promise; // (undocumented) - getTask(taskId: string): Promise; + getTask(taskId: string): Promise; // (undocumented) getTemplateParameterSchema( - templateName: EntityName, + templateRef: string, ): Promise; // (undocumented) listActions(): Promise; + // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters + // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters + // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters scaffold( - templateName: string, - values: Record, - secrets?: Record, - ): Promise; + options: ScaffolderScaffoldOptions, + ): Promise; // (undocumented) - streamLogs(options: { taskId: string; after?: number }): Observable; + streamLogs(options: ScaffolderStreamLogsOptions): Observable; } // Warning: (ae-missing-release-tag) "ScaffolderFieldExtensions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -281,6 +309,26 @@ export class ScaffolderClient implements ScaffolderApi { // @public (undocumented) export const ScaffolderFieldExtensions: React_2.ComponentType; +// Warning: (ae-missing-release-tag) "ScaffolderGetIntegrationsListOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ScaffolderGetIntegrationsListOptions { + // (undocumented) + allowedHosts: string[]; +} + +// Warning: (ae-missing-release-tag) "ScaffolderGetIntegrationsListResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ScaffolderGetIntegrationsListResponse { + // (undocumented) + integrations: { + type: string; + title: string; + host: string; + }[]; +} + // Warning: (ae-missing-release-tag) "ScaffolderPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -316,6 +364,58 @@ export const scaffolderPlugin: BackstagePlugin< } >; +// Warning: (ae-missing-release-tag) "ScaffolderScaffoldOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ScaffolderScaffoldOptions { + // (undocumented) + secrets?: Record; + // (undocumented) + templateName: string; + // (undocumented) + values: Record; +} + +// Warning: (ae-missing-release-tag) "ScaffolderScaffoldResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ScaffolderScaffoldResponse { + // (undocumented) + taskId: string; +} + +// Warning: (ae-missing-release-tag) "ScaffolderStreamLogsOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ScaffolderStreamLogsOptions { + // (undocumented) + after?: number; + // (undocumented) + taskId: string; +} + +// Warning: (ae-missing-release-tag) "ScaffolderTask" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ScaffolderTask = { + id: string; + spec: TaskSpec; + status: 'failed' | 'completed' | 'processing' | 'open' | 'cancelled'; + lastHeartbeatAt: string; + createdAt: string; +}; + +// Warning: (ae-missing-release-tag) "ScaffolderTaskOutput" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ScaffolderTaskOutput = { + entityRef?: string; + remoteUrl?: string; + links?: ScaffolderOutputLink[]; +} & { + [key: string]: unknown; +}; + // @public export const TaskPage: ({ loadingText }: TaskPageProps) => JSX.Element; @@ -348,6 +448,17 @@ export type TemplateListProps = { }; }; +// Warning: (ae-missing-release-tag) "TemplateParameterSchema" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type TemplateParameterSchema = { + title: string; + steps: Array<{ + title: string; + schema: JsonObject; + }>; +}; + // Warning: (ae-missing-release-tag) "TemplateTypePicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -357,4 +468,8 @@ export const TemplateTypePicker: () => JSX.Element | null; export const useTemplateSecrets: () => { setSecret: (input: Record) => void; }; + +// Warnings were encountered during analysis: +// +// src/types.d.ts:32:5 - (ae-forgotten-export) The symbol "ScaffolderOutputLink" needs to be exported by the entry point index.d.ts ``` diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index 8a29daa95d..d12ce68555 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -140,7 +140,7 @@ export class ScaffolderClient implements ScaffolderApi { } const { id } = (await response.json()) as { id: string }; - return { jobId: id }; + return { taskId: id }; } async getTask(taskId: string): Promise { diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index a642660bac..5387391e99 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -49,7 +49,7 @@ import React, { memo, useEffect, useMemo, useState } from 'react'; import { generatePath, useNavigate, useParams } from 'react-router'; import useInterval from 'react-use/lib/useInterval'; import { rootRouteRef } from '../../routes'; -import { Status, TaskOutput } from '../../types'; +import { LogEventStatus, ScaffolderTaskOutput } from '../../types'; import { useTaskEventStream } from '../hooks/useEventStream'; import { TaskPageLinks } from './TaskPageLinks'; @@ -86,7 +86,7 @@ const useStyles = makeStyles((theme: Theme) => type TaskStep = { id: string; name: string; - status: Status; + status: LogEventStatus; startedAt?: string; endedAt?: string; }; @@ -217,7 +217,11 @@ export const TaskStatusStepper = memo( }, ); -const hasLinks = ({ entityRef, remoteUrl, links = [] }: TaskOutput): boolean => +const hasLinks = ({ + entityRef, + remoteUrl, + links = [], +}: ScaffolderTaskOutput): boolean => !!(entityRef || remoteUrl || links.length > 0); /** diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx index d53b5d705c..5451b4e725 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx @@ -19,12 +19,12 @@ import { entityRouteRef } from '@backstage/plugin-catalog-react'; import { Box } from '@material-ui/core'; import LanguageIcon from '@material-ui/icons/Language'; import React from 'react'; -import { TaskOutput } from '../../types'; +import { ScaffolderTaskOutput } from '../../types'; import { IconLink } from './IconLink'; import { IconComponent, useApp, useRouteRef } from '@backstage/core-plugin-api'; type TaskPageLinksProps = { - output: TaskOutput; + output: ScaffolderTaskOutput; }; export const TaskPageLinks = ({ output }: TaskPageLinksProps) => { diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx index 48710b7086..d0eca58b9d 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx @@ -23,7 +23,8 @@ import { ThemeProvider } from '@material-ui/core'; import { act, fireEvent, within } from '@testing-library/react'; import React from 'react'; import { MemoryRouter, Route } from 'react-router'; -import { ScaffolderApi, scaffolderApiRef } from '../../api'; +import { scaffolderApiRef } from '../../api'; +import { ScaffolderApi } from '../../types'; import { rootRouteRef } from '../../routes'; import { TemplatePage } from './TemplatePage'; import { diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index d30c292053..fade247a24 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -42,16 +42,11 @@ import { useRouteRef, } from '@backstage/core-plugin-api'; -const useTemplateParameterSchema = (templateName: string) => { +const useTemplateParameterSchema = (templateRef: string) => { const scaffolderApi = useApi(scaffolderApiRef); const { value, loading, error } = useAsync( - () => - scaffolderApi.getTemplateParameterSchema({ - name: templateName, - kind: 'template', - namespace: 'default', - }), - [scaffolderApi, templateName], + () => scaffolderApi.getTemplateParameterSchema(templateRef), + [scaffolderApi, templateRef], ); return { schema: value, loading, error }; }; @@ -141,11 +136,11 @@ export const TemplatePage = ({ ); const handleCreate = async () => { - const id = await scaffolderApi.scaffold( + const { taskId } = await scaffolderApi.scaffold({ templateName, - formState, - secretsContext?.secrets, - ); + values: formState, + secrets: secretsContext?.secrets, + }); const formParams = qs.stringify( { formData: formState }, @@ -158,7 +153,7 @@ export const TemplatePage = ({ // extra back/forward slots. window.history?.replaceState(null, document.title, newUrl); - navigate(generatePath(`${rootLink()}/tasks/:taskId`, { taskId: id })); + navigate(generatePath(`${rootLink()}/tasks/:taskId`, { taskId })); }; if (error) { diff --git a/plugins/scaffolder/src/components/hooks/useEventStream.ts b/plugins/scaffolder/src/components/hooks/useEventStream.ts index 7414a35189..b5ce7241a1 100644 --- a/plugins/scaffolder/src/components/hooks/useEventStream.ts +++ b/plugins/scaffolder/src/components/hooks/useEventStream.ts @@ -15,14 +15,19 @@ */ import { useImmerReducer } from 'use-immer'; import { useEffect } from 'react'; -import { scaffolderApiRef, LogEvent } from '../../api'; -import { ScaffolderTask, Status, TaskOutput } from '../../types'; +import { scaffolderApiRef } from '../../api'; +import { + ScaffolderTask, + LogEventStatus, + ScaffolderTaskOutput, + LogEvent, +} from '../../types'; import { useApi } from '@backstage/core-plugin-api'; import { Subscription } from '@backstage/types'; type Step = { id: string; - status: Status; + status: LogEventStatus; endedAt?: string; startedAt?: string; }; @@ -34,16 +39,16 @@ export type TaskStream = { completed: boolean; task?: ScaffolderTask; steps: { [stepId in string]: Step }; - output?: TaskOutput; + output?: ScaffolderTaskOutput; }; type ReducerLogEntry = { createdAt: string; body: { stepId?: string; - status?: Status; + status?: LogEventStatus; message: string; - output?: TaskOutput; + output?: ScaffolderTaskOutput; }; }; diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index 9b65bc5e5e..a6304fc2c2 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -17,28 +17,14 @@ import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { JsonObject, Observable } from '@backstage/types'; import { JSONSchema7 } from 'json-schema'; -export type Status = 'open' | 'processing' | 'failed' | 'completed' | 'skipped'; -export type JobStatus = 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED'; -export type Job = { - id: string; - metadata: { - entity: any; - values: any; - remoteUrl?: string; - catalogInfoUrl?: string; - }; - status: JobStatus; - stages: Stage[]; - error?: Error; -}; +export type LogEventStatus = + | 'open' + | 'processing' + | 'failed' + | 'completed' + | 'skipped'; -export type Stage = { - name: string; - log: string[]; - status: JobStatus; - startedAt: string; - endedAt?: string; -}; +export type JobStatus = 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED'; export type ScaffolderTask = { id: string; @@ -57,19 +43,19 @@ export type ListActionsResponse = Array<{ }; }>; -type OutputLink = { +type ScaffolderOutputLink = { title?: string; icon?: string; url?: string; entityRef?: string; }; -export type TaskOutput = { +export type ScaffolderTaskOutput = { /** @deprecated use the `links` property to link out to relevant resources */ entityRef?: string; /** @deprecated use the `links` property to link out to relevant resources */ remoteUrl?: string; - links?: OutputLink[]; + links?: ScaffolderOutputLink[]; } & { [key: string]: unknown; }; @@ -87,7 +73,7 @@ export type LogEvent = { body: { message: string; stepId?: string; - status?: Status; + status?: LogEventStatus; }; createdAt: string; id: string; @@ -100,7 +86,7 @@ export interface ScaffolderScaffoldOptions { } export interface ScaffolderScaffoldResponse { - jobId: string; + taskId: string; } export interface ScaffolderGetIntegrationsListOptions { From 50e0242ac2329941b1236905397c900340d88e30 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 23 Feb 2022 16:56:39 +0100 Subject: [PATCH 5/9] chore: added some changeset Signed-off-by: blam --- .changeset/seven-rings-smoke.md | 6 ++++++ .changeset/slimy-drinks-tell.md | 7 +++++++ plugins/scaffolder/src/plugin.ts | 9 ++++----- 3 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 .changeset/seven-rings-smoke.md create mode 100644 .changeset/slimy-drinks-tell.md diff --git a/.changeset/seven-rings-smoke.md b/.changeset/seven-rings-smoke.md new file mode 100644 index 0000000000..5183ccb3ac --- /dev/null +++ b/.changeset/seven-rings-smoke.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +- Moved the `JSONSchema` type from `@backstage/catalog-model` to `JSONSchema7`. +- Renamed and prefixed some types ready for exporting. diff --git a/.changeset/slimy-drinks-tell.md b/.changeset/slimy-drinks-tell.md new file mode 100644 index 0000000000..f83953ccf6 --- /dev/null +++ b/.changeset/slimy-drinks-tell.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +- **BREAKING** - `scaffolderApi.scaffold()` now takes one `options` argument instead of 3, the existing arguments should just be wrapped up in one object instead. +- **BREAKING** - `scaffolderApi.scaffold()` now returns an object instead of a single string for the job ID. It's now `{ taskId: string }` +- **BREAKING** - `scaffolderApi.getIntegrationsList` now returns an object `{ integrations: { type: string, title: string, host: string }[] }` instead of just an array. diff --git a/plugins/scaffolder/src/plugin.ts b/plugins/scaffolder/src/plugin.ts index c84d2bb73c..ccf8d2ee34 100644 --- a/plugins/scaffolder/src/plugin.ts +++ b/plugins/scaffolder/src/plugin.ts @@ -64,12 +64,11 @@ export const scaffolderPlugin = createPlugin({ }, }); -export const EntityPickerExtension = createScaffolderFieldExtension({ - component: EntityPicker, - name: 'EntityPicker', -}); export const EntityPickerFieldExtension = scaffolderPlugin.provide( - EntityPickerExtension, + createScaffolderFieldExtension({ + component: EntityPicker, + name: 'EntityPicker', + }), ); export const EntityNamePickerFieldExtension = scaffolderPlugin.provide( From cb19ac7a32a2c1589fbdf83c4e6669fc8dbe3bba Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 23 Feb 2022 17:14:17 +0100 Subject: [PATCH 6/9] chore: remove some warnings from the types and fix the buiold Signed-off-by: blam --- plugins/scaffolder/api-report.md | 6 ------ plugins/scaffolder/package.json | 1 + plugins/scaffolder/src/api.ts | 4 +--- plugins/scaffolder/src/types.ts | 5 ++--- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 95fc6f9aa2..d8b433484d 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -261,9 +261,6 @@ export interface ScaffolderApi { templateRef: string, ): Promise; listActions(): Promise; - // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters - // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters - // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters scaffold( options: ScaffolderScaffoldOptions, ): Promise; @@ -294,9 +291,6 @@ export class ScaffolderClient implements ScaffolderApi { ): Promise; // (undocumented) listActions(): Promise; - // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters - // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters - // Warning: (tsdoc-param-tag-with-invalid-name) The @param block should be followed by a valid parameter name: The identifier cannot non-word characters scaffold( options: ScaffolderScaffoldOptions, ): Promise; diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 827e8126f7..60b32881c9 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -34,6 +34,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { + "@types/json-schema": "^7.0.9", "@backstage/catalog-client": "^0.7.0", "@backstage/catalog-model": "^0.10.0", "@backstage/config": "^0.1.14", diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index d12ce68555..39c366a500 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -112,9 +112,7 @@ export class ScaffolderClient implements ScaffolderApi { * Executes the scaffolding of a component, given a template and its * parameter values. * - * @param options.templateName - Template name for the scaffolder to use. New project is going to be created out of this template. - * @param options.values - Parameters for the template, e.g. name, description - * @param options.secrets - Optional secrets to pass to as the secrets parameter to the template. + * @param options - The {@link ScaffolderScaffoldOptions} the scaffolding. */ async scaffold( options: ScaffolderScaffoldOptions, diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index a6304fc2c2..9b77b9a9b0 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -79,6 +79,7 @@ export type LogEvent = { id: string; taskId: string; }; + export interface ScaffolderScaffoldOptions { templateName: string; values: Record; @@ -115,9 +116,7 @@ export interface ScaffolderApi { * Executes the scaffolding of a component, given a template and its * parameter values. * - * @param options.templateName - Name of the Template entity for the scaffolder to use. New project is going to be created out of this template. - * @param options.values - Parameters for the template, e.g. name, description - * @param options.secrets - Optional secrets to pass to as the secrets parameter to the template. + * @param options - The {@link ScaffolderScaffoldOptions} the scaffolding. */ scaffold( options: ScaffolderScaffoldOptions, From f46c712a96b5e1aa736feaa8b5dd52f8d877329b Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 24 Feb 2022 12:45:57 +0100 Subject: [PATCH 7/9] chore: fixing tests for RepoUrlPickerHost Signed-off-by: blam --- .../RepoUrlPicker/RepoUrlPickerHost.test.tsx | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.test.tsx index e151ddbbc7..bd71fb160f 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.test.tsx @@ -23,11 +23,11 @@ describe('RepoUrlPickerHostField', () => { it('renders the default host properly', async () => { const mockOnChange = jest.fn(); const mockScaffolderApi = { - getIntegrationsList: jest - .fn() - .mockResolvedValue([ + getIntegrationsList: jest.fn().mockResolvedValue({ + integrations: [ { host: 'github.com', title: 'github.com', type: 'github' }, - ]), + ], + }), }; const { getByText } = await renderInTestApp( @@ -46,10 +46,12 @@ describe('RepoUrlPickerHostField', () => { it('should provide a dropdown when multiple hosts are returned that can be selected', async () => { const mockOnChange = jest.fn(); const mockScaffolderApi = { - getIntegrationsList: jest.fn().mockResolvedValue([ - { host: 'github.com', title: 'github.com', type: 'github' }, - { host: 'gitlab.com', title: 'gitlab.com', type: 'gitlab' }, - ]), + getIntegrationsList: jest.fn().mockResolvedValue({ + integrations: [ + { host: 'github.com', title: 'github.com', type: 'github' }, + { host: 'gitlab.com', title: 'gitlab.com', type: 'gitlab' }, + ], + }), }; const { getByRole, getByText, getByTestId } = await renderInTestApp( @@ -73,10 +75,12 @@ describe('RepoUrlPickerHostField', () => { it('should not display hosts that dont have integration config set correctly', async () => { const mockOnChange = jest.fn(); const mockScaffolderApi = { - getIntegrationsList: jest.fn().mockResolvedValue([ - { host: 'github.com', title: 'github.com', type: 'github' }, - { host: 'gitlab.com', title: 'gitlab.com', type: 'gitlab' }, - ]), + getIntegrationsList: jest.fn().mockResolvedValue({ + integrations: [ + { host: 'github.com', title: 'github.com', type: 'github' }, + { host: 'gitlab.com', title: 'gitlab.com', type: 'gitlab' }, + ], + }), }; const { getByRole, getByText, getByTestId } = await renderInTestApp( From 4031764fca86600a12a716a0ffd2f77df18f8b1f Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 24 Feb 2022 14:01:51 +0100 Subject: [PATCH 8/9] chore: updating to use templateRef Signed-off-by: blam --- plugins/scaffolder-backend/src/service/router.ts | 10 ++++++---- plugins/scaffolder/src/api.ts | 8 +++++--- .../src/components/TemplatePage/TemplatePage.tsx | 7 ++++++- plugins/scaffolder/src/types.ts | 2 +- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index e1cd75dcc2..eb7c0ebd78 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -22,6 +22,7 @@ import { import { CatalogApi } from '@backstage/catalog-client'; import { DEFAULT_NAMESPACE, + parseEntityRef, stringifyEntityRef, } from '@backstage/catalog-model'; import { Entity } from '@backstage/catalog-model'; @@ -178,14 +179,15 @@ export async function createRouter( res.json(actionsList); }) .post('/v2/tasks', async (req, res) => { - const templateName: string = req.body.templateName; - const kind = 'template'; - const namespace = DEFAULT_NAMESPACE; + const templateRef: string = req.body.templateRef; + const { kind, namespace, name } = parseEntityRef(templateRef, { + defaultKind: 'template', + }); const values = req.body.values; const token = getBearerToken(req.headers.authorization); const template = await findTemplate({ catalogApi: catalogClient, - entityRef: { kind, namespace, name: templateName }, + entityRef: { kind, namespace, name }, token: getBearerToken(req.headers.authorization), }); diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index 39c366a500..818ee7971b 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -90,7 +90,9 @@ export class ScaffolderClient implements ScaffolderApi { async getTemplateParameterSchema( templateRef: string, ): Promise { - const { namespace, kind, name } = parseEntityRef(templateRef); + const { namespace, kind, name } = parseEntityRef(templateRef, { + defaultKind: 'template', + }); const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); const templatePath = [namespace, kind, name] @@ -117,7 +119,7 @@ export class ScaffolderClient implements ScaffolderApi { async scaffold( options: ScaffolderScaffoldOptions, ): Promise { - const { templateName, values, secrets = {} } = options; + const { templateRef, values, secrets = {} } = options; const url = `${await this.discoveryApi.getBaseUrl('scaffolder')}/v2/tasks`; const response = await this.fetchApi.fetch(url, { method: 'POST', @@ -125,7 +127,7 @@ export class ScaffolderClient implements ScaffolderApi { 'Content-Type': 'application/json', }, body: JSON.stringify({ - templateName, + templateRef, values: { ...values }, secrets, }), diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index fade247a24..798ce31539 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -41,6 +41,7 @@ import { useApiHolder, useRouteRef, } from '@backstage/core-plugin-api'; +import { stringifyEntityRef } from '@backstage/catalog-model'; const useTemplateParameterSchema = (templateRef: string) => { const scaffolderApi = useApi(scaffolderApiRef); @@ -137,7 +138,11 @@ export const TemplatePage = ({ const handleCreate = async () => { const { taskId } = await scaffolderApi.scaffold({ - templateName, + templateRef: stringifyEntityRef({ + name: templateName, + kind: 'template', + namespace: 'default', + }), values: formState, secrets: secretsContext?.secrets, }); diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index 9b77b9a9b0..47829885da 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -81,7 +81,7 @@ export type LogEvent = { }; export interface ScaffolderScaffoldOptions { - templateName: string; + templateRef: string; values: Record; secrets?: Record; } From 91c6faeb7ba4eb8710ee10c9667e92b19e0a3424 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 24 Feb 2022 14:14:58 +0100 Subject: [PATCH 9/9] chore: updated with review comments Signed-off-by: blam --- .changeset/fair-carrots-provide.md | 5 ++++ .changeset/slimy-drinks-tell.md | 1 + .../src/service/router.test.ts | 11 +++++++-- .../scaffolder-backend/src/service/router.ts | 6 +---- plugins/scaffolder/api-report.md | 24 +++++++++---------- .../src/components/TaskPage/TaskPage.tsx | 4 ++-- .../src/components/hooks/useEventStream.ts | 6 ++--- plugins/scaffolder/src/index.ts | 16 ++++++++++++- plugins/scaffolder/src/types.ts | 4 ++-- 9 files changed, 50 insertions(+), 27 deletions(-) create mode 100644 .changeset/fair-carrots-provide.md diff --git a/.changeset/fair-carrots-provide.md b/.changeset/fair-carrots-provide.md new file mode 100644 index 0000000000..01bc9e18bf --- /dev/null +++ b/.changeset/fair-carrots-provide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +- **BREAKING** - the `/v2/tasks` endpoint now takes `templateRef` instead of `templateName` in the POST body. This should be a valid stringified `entityRef`. diff --git a/.changeset/slimy-drinks-tell.md b/.changeset/slimy-drinks-tell.md index f83953ccf6..87b9644d29 100644 --- a/.changeset/slimy-drinks-tell.md +++ b/.changeset/slimy-drinks-tell.md @@ -4,4 +4,5 @@ - **BREAKING** - `scaffolderApi.scaffold()` now takes one `options` argument instead of 3, the existing arguments should just be wrapped up in one object instead. - **BREAKING** - `scaffolderApi.scaffold()` now returns an object instead of a single string for the job ID. It's now `{ taskId: string }` +- **BREAKING** - `scaffolderApi.scaffold()` now takes a `templateRef` instead of `templateName` as an argument in the options. This should be a valid stringified `entityRef`. - **BREAKING** - `scaffolderApi.getIntegrationsList` now returns an object `{ integrations: { type: string, title: string, host: string }[] }` instead of just an array. diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 6a66d6df47..fa1b649231 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -49,6 +49,7 @@ import request from 'supertest'; */ import { createRouter, DatabaseTaskStore, TaskBroker } from '../index'; import { StorageTaskBroker } from '../scaffolder/tasks/StorageTaskBroker'; +import { stringifyEntityRef } from '@backstage/catalog-model'; const createCatalogClient = (template: any) => ({ @@ -146,7 +147,10 @@ describe('createRouter', () => { const response = await request(app) .post('/v2/tasks') .send({ - templateName: 'create-react-app-template', + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), values: { storePath: 'https://github.com/backstage/backstage', }, @@ -165,7 +169,10 @@ describe('createRouter', () => { const response = await request(app) .post('/v2/tasks') .send({ - templateName: 'create-react-app-template', + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), values: { required: 'required-value', }, diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index eb7c0ebd78..84febe4225 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -20,11 +20,7 @@ import { UrlReader, } from '@backstage/backend-common'; import { CatalogApi } from '@backstage/catalog-client'; -import { - DEFAULT_NAMESPACE, - parseEntityRef, - stringifyEntityRef, -} from '@backstage/catalog-model'; +import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model'; import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { InputError, NotFoundError } from '@backstage/errors'; diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index d8b433484d..df8f1ca9e2 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -146,23 +146,13 @@ export type LogEvent = { body: { message: string; stepId?: string; - status?: LogEventStatus; + status?: ScaffolderTaskStatus; }; createdAt: string; id: string; taskId: string; }; -// Warning: (ae-missing-release-tag) "LogEventStatus" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type LogEventStatus = - | 'open' - | 'processing' - | 'failed' - | 'completed' - | 'skipped'; - // Warning: (ae-missing-release-tag) "OwnedEntityPicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -365,7 +355,7 @@ export interface ScaffolderScaffoldOptions { // (undocumented) secrets?: Record; // (undocumented) - templateName: string; + templateRef: string; // (undocumented) values: Record; } @@ -410,6 +400,16 @@ export type ScaffolderTaskOutput = { [key: string]: unknown; }; +// Warning: (ae-missing-release-tag) "ScaffolderTaskStatus" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ScaffolderTaskStatus = + | 'open' + | 'processing' + | 'failed' + | 'completed' + | 'skipped'; + // @public export const TaskPage: ({ loadingText }: TaskPageProps) => JSX.Element; diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index 5387391e99..0882d49ff4 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -49,7 +49,7 @@ import React, { memo, useEffect, useMemo, useState } from 'react'; import { generatePath, useNavigate, useParams } from 'react-router'; import useInterval from 'react-use/lib/useInterval'; import { rootRouteRef } from '../../routes'; -import { LogEventStatus, ScaffolderTaskOutput } from '../../types'; +import { ScaffolderTaskStatus, ScaffolderTaskOutput } from '../../types'; import { useTaskEventStream } from '../hooks/useEventStream'; import { TaskPageLinks } from './TaskPageLinks'; @@ -86,7 +86,7 @@ const useStyles = makeStyles((theme: Theme) => type TaskStep = { id: string; name: string; - status: LogEventStatus; + status: ScaffolderTaskStatus; startedAt?: string; endedAt?: string; }; diff --git a/plugins/scaffolder/src/components/hooks/useEventStream.ts b/plugins/scaffolder/src/components/hooks/useEventStream.ts index b5ce7241a1..8ff9a30d0b 100644 --- a/plugins/scaffolder/src/components/hooks/useEventStream.ts +++ b/plugins/scaffolder/src/components/hooks/useEventStream.ts @@ -18,7 +18,7 @@ import { useEffect } from 'react'; import { scaffolderApiRef } from '../../api'; import { ScaffolderTask, - LogEventStatus, + ScaffolderTaskStatus, ScaffolderTaskOutput, LogEvent, } from '../../types'; @@ -27,7 +27,7 @@ import { Subscription } from '@backstage/types'; type Step = { id: string; - status: LogEventStatus; + status: ScaffolderTaskStatus; endedAt?: string; startedAt?: string; }; @@ -46,7 +46,7 @@ type ReducerLogEntry = { createdAt: string; body: { stepId?: string; - status?: LogEventStatus; + status?: ScaffolderTaskStatus; message: string; output?: ScaffolderTaskOutput; }; diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index 17c48e55ac..90d5445c8c 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -21,7 +21,21 @@ */ export { scaffolderApiRef, ScaffolderClient } from './api'; -export * from './types'; +export type { + JobStatus, + ListActionsResponse, + LogEvent, + ScaffolderApi, + ScaffolderGetIntegrationsListOptions, + ScaffolderGetIntegrationsListResponse, + ScaffolderScaffoldOptions, + ScaffolderScaffoldResponse, + ScaffolderStreamLogsOptions, + ScaffolderTask, + ScaffolderTaskOutput, + ScaffolderTaskStatus, + TemplateParameterSchema, +} from './types'; export { createScaffolderFieldExtension, ScaffolderFieldExtensions, diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index 47829885da..5d89f10291 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -17,7 +17,7 @@ import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { JsonObject, Observable } from '@backstage/types'; import { JSONSchema7 } from 'json-schema'; -export type LogEventStatus = +export type ScaffolderTaskStatus = | 'open' | 'processing' | 'failed' @@ -73,7 +73,7 @@ export type LogEvent = { body: { message: string; stepId?: string; - status?: LogEventStatus; + status?: ScaffolderTaskStatus; }; createdAt: string; id: string;