From 9ef2e68b3399f3f331f4a66decd0e726480f0820 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 8 Feb 2021 14:08:13 +0100 Subject: [PATCH 001/270] Add log metadata and getTask method --- .../src/scaffolder/tasks/DatabaseTaskStore.ts | 2 +- .../tasks/StorageTaskBroker.test.ts | 10 +-- .../src/scaffolder/tasks/StorageTaskBroker.ts | 10 ++- .../src/scaffolder/tasks/TaskWorker.ts | 89 ++++++++++--------- .../src/scaffolder/tasks/types.ts | 3 +- 5 files changed, 61 insertions(+), 53 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index 5717f3c8c1..4190a5d58f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -64,7 +64,7 @@ export class DatabaseTaskStore implements TaskStore { constructor(private readonly db: Knex) {} - async get(taskId: string): Promise { + async getTask(taskId: string): Promise { const [result] = await this.db('tasks') .where({ id: taskId }) .select(); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts index c45fa46335..ffb8de6e3d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts @@ -83,7 +83,7 @@ describe('StorageTaskBroker', () => { const dispatchResult = await broker.dispatch({ steps: [] }); const task = await broker.claim(); await task.complete('completed'); - const taskRow = await storage.get(dispatchResult.taskId); + const taskRow = await storage.getTask(dispatchResult.taskId); expect(taskRow.status).toBe('completed'); }, 10000); @@ -92,7 +92,7 @@ describe('StorageTaskBroker', () => { const dispatchResult = await broker.dispatch({ steps: [] }); const task = await broker.claim(); await task.complete('failed'); - const taskRow = await storage.get(dispatchResult.taskId); + const taskRow = await storage.getTask(dispatchResult.taskId); expect(taskRow.status).toBe('failed'); }); @@ -142,10 +142,10 @@ describe('StorageTaskBroker', () => { const { taskId } = await broker.dispatch({ steps: [] }); const task = await broker.claim(); - const initialTask = await storage.get(taskId); + const initialTask = await storage.getTask(taskId); for (;;) { - const maybeTask = await storage.get(taskId); + const maybeTask = await storage.getTask(taskId); if (maybeTask.lastHeartbeatAt !== initialTask.lastHeartbeatAt) { break; } @@ -169,7 +169,7 @@ describe('StorageTaskBroker', () => { }, 500); for (;;) { - const maybeTask = await storage.get(taskId); + const maybeTask = await storage.getTask(taskId); if (maybeTask.status === 'failed') { break; } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 48c0d5e5ba..d3b2c098c7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { JsonObject } from '@backstage/config'; import { Logger } from 'winston'; import { CompletedTaskState, @@ -22,6 +23,7 @@ import { TaskBroker, DispatchResult, DbTaskEventRow, + DbTaskRow, } from './types'; export class TaskAgent implements Task { @@ -54,10 +56,10 @@ export class TaskAgent implements Task { return this.isDone; } - async emitLog(message: string): Promise { + async emitLog(message: string, metadata?: JsonObject): Promise { await this.storage.emitLogEvent({ taskId: this.state.taskId, - body: { message }, + body: { message, ...metadata }, }); } @@ -136,6 +138,10 @@ export class StorageTaskBroker implements TaskBroker { }; } + async get(taskId: string): Promise { + return this.storage.getTask(taskId); + } + observe( options: { taskId: string; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index c62495ca4c..d2f3b88e65 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -51,59 +51,60 @@ export class TaskWorker { await task.getWorkspaceName(), ); await fs.ensureDir(workspacePath); - - const taskLogger = winston.createLogger({ - level: process.env.LOG_LEVEL || 'info', - format: winston.format.combine( - winston.format.colorize(), - winston.format.timestamp(), - winston.format.simple(), - ), - defaultMeta: {}, - }); - - const stream = new PassThrough(); - stream.on('data', data => { - const message = data.toString().trim(); - if (message?.length > 1) task.emitLog(message); - }); - - taskLogger.add(new winston.transports.Stream({ stream })); - - // Give us some time to curl observe - task.emitLog('Task claimed, waiting ...'); - await new Promise(resolve => setTimeout(resolve, 5000)); - task.emitLog(`Starting up work with ${task.spec.steps.length} steps`); + await task.emitLog( + `Starting up work with ${task.spec.steps.length} steps`, + ); const outputs: { [name: string]: JsonValue } = {}; for (const step of task.spec.steps) { - task.emitLog(`Beginning step ${step.name}`); + const metadata = { stepId: step.id }; + try { + const taskLogger = winston.createLogger({ + level: process.env.LOG_LEVEL || 'info', + format: winston.format.combine( + winston.format.colorize(), + winston.format.timestamp(), + winston.format.simple(), + ), + defaultMeta: {}, + }); - const action = actionRegistry.get(step.action); - if (!action) { - throw new Error(`Action '${step.action}' does not exist`); + const stream = new PassThrough(); + stream.on('data', data => { + const message = data.toString().trim(); + if (message?.length > 1) task.emitLog(message, metadata); + }); + + taskLogger.add(new winston.transports.Stream({ stream })); + await task.emitLog(`Beginning step ${step.name}`, metadata); + + const action = actionRegistry.get(step.action); + if (!action) { + throw new Error(`Action '${step.action}' does not exist`); + } + + // TODO: substitute any placeholders with output from previous steps + const parameters = step.parameters!; + + await action.handler({ + logger, + logStream: stream, + parameters, + workspacePath, + output(name: string, value: JsonValue) { + outputs[name] = value; + }, + }); + + await task.emitLog(`Finished step ${step.name}`, metadata); + } catch (error) { + await task.emitLog(String(error.stack), metadata); + throw error; } - - // TODO: substitute any placeholders with output from previous steps - const parameters = step.parameters!; - - await action.handler({ - logger, - logStream: stream, - parameters, - workspacePath, - output(name: string, value: JsonValue) { - outputs[name] = value; - }, - }); - - task.emitLog(`Finished step ${step.name}`); } - await task.complete('completed'); } catch (error) { - task.emitLog(String(error.stack)); await task.complete('failed'); } } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 0c2592109b..783528dbdf 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -58,7 +58,7 @@ export type DispatchResult = { export interface Task { spec: TaskSpec; done: boolean; - emitLog(message: string): Promise; + emitLog(message: string, metadata?: JsonValue): Promise; complete(result: CompletedTaskState): Promise; getWorkspaceName(): Promise; } @@ -90,6 +90,7 @@ export type TaskStoreGetEventsOptions = { }; export interface TaskStore { createTask(task: TaskSpec): Promise<{ taskId: string }>; + getTask(taskId: string): Promise; claimTask(): Promise; completeTask(options: { taskId: string; From 10aa1f42267e42ccdb68482237ed4118c9f7dad0 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 8 Feb 2021 14:09:00 +0100 Subject: [PATCH 002/270] Add get task by taskID endpoint --- plugins/scaffolder-backend/src/service/router.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 5821edfe8c..e9239b8ea2 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -44,7 +44,10 @@ import { } from '../scaffolder/tasks/TemplateConverter'; import { registerLegacyActions } from '../scaffolder/stages/legacy'; import { getWorkingDirectory } from './helpers'; -import { PluginDatabaseManager } from '@backstage/backend-common'; +import { + NotFoundError, + PluginDatabaseManager, +} from '@backstage/backend-common'; export interface RouterOptions { preparers: PreparerBuilder; @@ -246,6 +249,16 @@ export async function createRouter( res.status(201).json({ id: result.taskId }); }) + .get('/v2/tasks/:taskId', async (req, res) => { + const { taskId } = req.params; + console.warn('getting task'); + const task = await taskBroker.get(taskId); + console.warn('got task', task); + if (!task) { + throw new NotFoundError(`task with id ${taskId} does not exist`); + } + res.status(200).json(task); + }) .get('/v2/tasks/:taskId/eventstream', async (req, res) => { const { taskId } = req.params; const after = Number(req.query.after) || undefined; From cb1d826cddabad4cf7f5514313b070d44a40a1e3 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 8 Feb 2021 17:03:17 +0100 Subject: [PATCH 003/270] wip --- plugins/scaffolder/package.json | 5 +- plugins/scaffolder/src/api.ts | 79 +++++++- .../JobStatusModal/JobStatusModal.tsx | 186 +++++++++++++++--- .../components/TemplatePage/TemplatePage.tsx | 76 +++---- .../{useJobPolling.ts => useTaskPolling.ts} | 54 ++--- plugins/scaffolder/src/types.ts | 18 ++ 6 files changed, 313 insertions(+), 105 deletions(-) rename plugins/scaffolder/src/components/hooks/{useJobPolling.ts => useTaskPolling.ts} (52%) diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 56beb7b10b..10cc36809a 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -32,6 +32,7 @@ "dependencies": { "@backstage/catalog-model": "^0.7.1", "@backstage/core": "^0.6.0", + "@backstage/config": "^0.1.2", "@backstage/plugin-catalog-react": "^0.0.2", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", @@ -48,13 +49,13 @@ "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", "react-use": "^15.3.3", - "swr": "^0.3.0" + "swr": "^0.3.0", + "zen-observable": "^0.8.15" }, "devDependencies": { "@backstage/cli": "^0.6.0", "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", - "@backstage/catalog-client": "^0.3.5", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index ca52418e92..6b7f1b6181 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -14,13 +14,21 @@ * limitations under the License. */ -import { createApiRef, DiscoveryApi } from '@backstage/core'; +import { JsonObject } from '@backstage/config'; +import { createApiRef, DiscoveryApi, Observable } from '@backstage/core'; +import ObservableImpl from 'zen-observable'; +import { ScaffolderV2Task } from './types'; export const scaffolderApiRef = createApiRef({ id: 'plugin.scaffolder.service', description: 'Used to make requests towards the scaffolder backend', }); +type LogEvent = { + type: 'log' | 'completion'; + body: JsonObject; +}; + export class ScaffolderApi { private readonly discoveryApi: DiscoveryApi; @@ -36,7 +44,7 @@ export class ScaffolderApi { * @param values Parameters for the template, e.g. name, description */ async scaffold(templateName: string, values: Record) { - const url = `${await this.discoveryApi.getBaseUrl('scaffolder')}/v1/jobs`; + const url = `${await this.discoveryApi.getBaseUrl('scaffolder')}/v2/tasks`; const response = await fetch(url, { method: 'POST', headers: { @@ -52,12 +60,75 @@ export class ScaffolderApi { } const { id } = await response.json(); + + // 859d6b78-7c19-4d1a-abe0-9396ab3bd686 + // this.streamLogs({ + // taskId: id, + // }).then(observable => { + // console.log('DEBUG: observable =', observable); + // observable.subscribe({ + // next: thing => console.warn('next', thing), + // error: thing => console.warn('error', thing), + // complete: () => console.warn('complete'), + // }); + // }); + return id; } - async getJob(jobId: string) { + async getTask(taskId: string): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); - const url = `${baseUrl}/v1/job/${encodeURIComponent(jobId)}`; + const url = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}`; return fetch(url).then(x => x.json()); } + + streamLogs({ + taskId, + after, + }: { + taskId: string; + after?: number; + }): Observable { + return new ObservableImpl(subscriber => { + const params = new URLSearchParams(); + if (after !== undefined) { + params.set('after', String(Number(after))); + } + + this.discoveryApi.getBaseUrl('scaffolder').then( + baseUrl => { + const url = `${baseUrl}/v2/tasks/${encodeURIComponent( + taskId, + )}/eventstream`; + const eventSource = new EventSource(url); + eventSource.addEventListener('log', event => { + if (event.data) { + try { + subscriber.next(JSON.parse(event.data)); + } catch (ex) { + subscriber.error(ex); + } + } + }); + eventSource.addEventListener('completion', event => { + eventSource.close(); + if (event.data) { + try { + subscriber.next(JSON.parse(event.data)); + } catch (ex) { + subscriber.error(ex); + } + } + subscriber.complete(); + }); + eventSource.addEventListener('error', event => { + subscriber.error(event); + }); + }, + error => { + subscriber.error(error); + }, + ); + }); + } } diff --git a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx index d35385ec3b..597e6a243d 100644 --- a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx +++ b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Button } from '@backstage/core'; +import { Button, Observable, Subscription, useApi } from '@backstage/core'; import { Button as Action, Dialog, @@ -23,64 +23,192 @@ import { LinearProgress, } from '@material-ui/core'; -import React, { useCallback } from 'react'; -import { Job } from '../../types'; -import { JobStage } from '../JobStage/JobStage'; +import React, { useCallback, useEffect, useReducer } from 'react'; +import { scaffolderApiRef } from '../../api'; +import { ScaffolderV2Task } from '../../types'; type Props = { - job: Job | null; + task: ScaffolderV2Task | null; toCatalogLink?: string; open: boolean; onModalClose: () => void; }; +type Step = { + id: string; + status: 'open' | 'processing' | 'failed' | 'completed'; +}; + +type ReducerState = { + loading: boolean; + error?: Error; + completed: boolean; + + task: ScaffolderV2Task; + + step: [{ [stepId in string]: Step }]; +}; + +type ReducerAction = + | { + type: 'INIT'; + data: ScaffolderV2Task; + } + | { type: 'EVENT'; data: { body: { stepId: string } } } + | { type: 'COMPLETED' } + | { type: 'ERROR'; data: Error }; + +function reducer(state: ReducerState, action: ReducerAction) { + const stepId = action.type === 'EVENT' ? action.data.body.stepId : 'global'; + const currentStep = state[stepId] ?? { log: [], status: 'open' }; + + switch (action.type) { + case 'INIT': + return { + ...state, + ...action.data, + }; + case 'EVENT': + return { + ...state, + [stepId]: { + ...currentStep, + log: [...currentStep.log, event], + }, + }; + case 'COMPLETED': + return { + ...state, + progress: 'done', + }; + case 'ERROR': + return { + error: action.error, + loading: false, + completed: false, + }; + default: + return state; + } +} + +/* + +{} + +{ + id: as'das + spec: { + steps: [ + { id, log: []} + ] + } +} + +*/ +const useTaskEventStream = (taskId: string) => { + // fetch task + + const scaffolderApi = useApi(scaffolderApiRef); + const [state, dispatch] = useReducer(reducer, { + loading: true, + }); + useEffect(() => { + let didCancel = false; + let subscription: Subscription | undefined; + + scaffolderApi.getTask(taskId).then( + task => { + if (didCancel) { + return; + } + dispatch({ type: 'INIT', data: task }); + const observable = scaffolderApi.streamLogs({ taskId }); + subscription = observable.subscribe({ + next: event => dispatch({ type: 'EVENT', data: event }), + error: error => dispatch({ type: 'ERROR', data: error }), + complete: () => dispatch({ type: 'COMPLETED' }), + }); + }, + error => { + if (!didCancel) { + dispatch({ type: 'ERROR', data: error }); + } + }, + ); + + return () => { + didCancel = true; + if (subscription) { + subscription.unsubscribe(); + } + }; + }); + + return state; + + // subscribe to observable, + + // on observer change update the step logs etc + status + + // return { + // steps: { + // stepId: { + // log: [], + // status: + // } + // } + // } +}; + export const JobStatusModal = ({ - job, + task, toCatalogLink, open, onModalClose, }: Props) => { + const model = useTaskEventStream(task?.id!); + console.warn(model); const renderTitle = () => { - switch (job?.status) { - case 'COMPLETED': + switch (task?.status) { + case 'completed': return 'Successfully created component'; - case 'FAILED': + case 'failed': return 'Failed to create component'; default: return 'Create component'; } }; - const onClose = useCallback(() => { - if (!job) { + if (!task) { return; } // Disallow closing modal if the job is in progress. - if (job.status === 'COMPLETED' || job.status === 'FAILED') { + if (task.status !== 'processing') { onModalClose(); } - }, [job, onModalClose]); + }, [task, onModalClose]); return ( {renderTitle()} - {!job ? ( - - ) : ( - (job?.stages ?? []).map(step => ( - - )) - )} + {/* {!task ? ( */} + + {/* ) : ( */} + {/* (task?.spec.steps ?? []).map(step => ( */} + {/* */} + {/* )) */} + {/* )} */} - {job?.status && toCatalogLink && ( + {/* {job?.status && toCatalogLink && ( @@ -89,7 +217,7 @@ export const JobStatusModal = ({ Close - )} + )} */} ); }; diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index e1422d4ec1..a3c7ceee12 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -20,6 +20,7 @@ import { Header, InfoCard, Lifecycle, + Observable, Page, useApi, } from '@backstage/core'; @@ -37,7 +38,7 @@ import { useParams } from 'react-router-dom'; import { useAsync } from 'react-use'; import { scaffolderApiRef } from '../../api'; import { rootRoute } from '../../routes'; -import { useJobPolling } from '../hooks/useJobPolling'; +import { useTaskPolling } from '../hooks/useTaskPolling'; import { JobStatusModal } from '../JobStatusModal'; import { MultistepJsonForm } from '../MultistepJsonForm'; @@ -91,41 +92,39 @@ export const TemplatePage = () => { [setFormState, formState], ); - const [jobId, setJobId] = useState(null); - const job = useJobPolling(jobId, async jobItem => { - if (!jobItem.metadata.catalogInfoUrl) { - errorApi.post( - new Error(`No catalogInfoUrl returned from the scaffolder`), - ); - return; - } - - try { - const { - entities: [createdEntity], - } = await catalogApi.addLocation({ - target: jobItem.metadata.catalogInfoUrl, - }); - - const resolvedPath = generatePath( - `/catalog/${entityRoute.path}`, - entityRouteParams(createdEntity), - ); - - setCatalogLink(resolvedPath); - } catch (ex) { - errorApi.post( - new Error( - `Something went wrong trying to add the new 'catalog-info.yaml' to the catalog`, - ), - ); - } + const [taskId, setTaskId] = useState(null); + const task = useTaskPolling(taskId, async task => { + console.warn('onFinish is called'); + // if (!jobItem.metadata.catalogInfoUrl) { + // errorApi.post( + // new Error(`No catalogInfoUrl returned from the scaffolder`), + // ); + // return; + // } + // try { + // const { + // entities: [createdEntity], + // } = await catalogApi.addLocation({ + // target: jobItem.metadata.catalogInfoUrl, + // }); + // const resolvedPath = generatePath( + // `/catalog/${entityRoute.path}`, + // entityRouteParams(createdEntity), + // ); + // setCatalogLink(resolvedPath); + // } catch (ex) { + // errorApi.post( + // new Error( + // `Something went wrong trying to add the new 'catalog-info.yaml' to the catalog`, + // ), + // ); + // } }); const handleCreate = async () => { try { const id = await scaffolderApi.scaffold(templateName, formState); - setJobId(id); + setTaskId(id); setModalOpen(true); } catch (e) { errorApi.post(e); @@ -159,12 +158,15 @@ export const TemplatePage = () => { /> {loading && } - setModalOpen(false)} - /> + {task && ( + setModalOpen(false)} + /> + )} + ) {template && ( void, +export const useTaskPolling = ( + taskId: string | null, + onFinish?: (t: ScaffolderV2Task) => void, pollingInterval = DEFAULT_POLLING_INTERVAL, ) => { const scaffolderApi = useApi(scaffolderApiRef); - const [currentJob, setCurrentJob] = useState(null); + const [currentTask, setCurrentTask] = useState(null); + + useInterval(async () => { + if (taskId) { + setCurrentTask(await scaffolderApi.getTask(taskId)); + } + }, pollingInterval); useEffect(() => { - const resetCurrentJob = async () => { - if (jobId) { - const job = await scaffolderApi.getJob(jobId); - setCurrentJob(job); - } - }; + switch (currentTask?.status) { + case 'failed': + case 'cancelled': + case 'completed': + return onFinish?.(currentTask); + default: + return undefined; + } + }, [currentTask, onFinish]); - resetCurrentJob(); - }, [jobId, scaffolderApi]); - - const shouldBeRunningInterval = - jobId && - currentJob?.status !== 'COMPLETED' && - currentJob?.status !== 'FAILED'; - - useInterval( - async () => { - if (jobId) { - const job = await scaffolderApi.getJob(jobId); - if (job?.status === 'COMPLETED' || job?.status === 'FAILED') { - onFinish?.(job); - } - setCurrentJob(job); - } - }, - shouldBeRunningInterval ? pollingInterval : null, - ); - - return currentJob; + return currentTask; }; diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index 45672c603d..8cdf22c18b 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { JsonValue } from '@backstage/config'; export type JobStatus = 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED'; export type Job = { @@ -35,3 +36,20 @@ export type Stage = { startedAt: string; endedAt?: string; }; + +export type ScaffolderV2Step = { + id: string; + name: string; + action: string; + parameters?: { [name: string]: JsonValue }; +}; + +export type ScaffolderV2Task = { + id: string; + spec: { + steps: ScaffolderV2Step[]; + }; + status: 'failed' | 'completed' | 'processing' | 'open' | 'cancelled'; + lastHeartbeatAt: string; + createdAt: string; +}; From 554209a0632d2bfa2f84c8c84f5014ddc93da500 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 9 Feb 2021 16:48:31 +0100 Subject: [PATCH 004/270] emot step status --- .../src/scaffolder/tasks/TaskWorker.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index d2f3b88e65..66d1331c5c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -77,7 +77,10 @@ export class TaskWorker { }); taskLogger.add(new winston.transports.Stream({ stream })); - await task.emitLog(`Beginning step ${step.name}`, metadata); + await task.emitLog(`Beginning step ${step.name}`, { + ...metadata, + status: 'processing', + }); const action = actionRegistry.get(step.action); if (!action) { @@ -97,9 +100,15 @@ export class TaskWorker { }, }); - await task.emitLog(`Finished step ${step.name}`, metadata); + await task.emitLog(`Finished step ${step.name}`, { + ...metadata, + status: 'completed', + }); } catch (error) { - await task.emitLog(String(error.stack), metadata); + await task.emitLog(String(error.stack), { + ...metadata, + status: 'failed', + }); throw error; } } From 593251b69dd7e2794a5d7d9f95264fdf1517a746 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 9 Feb 2021 16:53:24 +0100 Subject: [PATCH 005/270] add TaskPage component --- plugins/scaffolder/dev/index.tsx | 5 + plugins/scaffolder/package.json | 3 +- plugins/scaffolder/src/api.ts | 25 +-- .../src/components/JobStage/JobStage.tsx | 14 +- .../JobStatusModal/JobStatusModal.tsx | 171 ++---------------- .../src/components/TaskPage/TaskPage.tsx | 121 +++++++++++++ .../src/components/TaskPage/index.ts | 16 ++ .../components/TemplatePage/TemplatePage.tsx | 42 +---- .../src/components/hooks/useEventStream.ts | 170 +++++++++++++++++ .../src/components/hooks/useTaskPolling.ts | 50 ----- plugins/scaffolder/src/index.ts | 3 +- plugins/scaffolder/src/plugin.ts | 12 +- plugins/scaffolder/src/routes.ts | 5 + plugins/scaffolder/src/types.ts | 6 +- 14 files changed, 373 insertions(+), 270 deletions(-) create mode 100644 plugins/scaffolder/src/components/TaskPage/TaskPage.tsx create mode 100644 plugins/scaffolder/src/components/TaskPage/index.ts create mode 100644 plugins/scaffolder/src/components/hooks/useEventStream.ts delete mode 100644 plugins/scaffolder/src/components/hooks/useTaskPolling.ts diff --git a/plugins/scaffolder/dev/index.tsx b/plugins/scaffolder/dev/index.tsx index eff16ab0f5..5bdeec3e10 100644 --- a/plugins/scaffolder/dev/index.tsx +++ b/plugins/scaffolder/dev/index.tsx @@ -21,6 +21,7 @@ import { CatalogClient } from '@backstage/catalog-client'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { TemplateIndexPage, TemplatePage } from '../src/plugin'; import { ScaffolderApi, scaffolderApiRef } from '../src'; +import { TaskPage } from '../src/components/TaskPage'; createDevApp() .registerApi({ @@ -42,4 +43,8 @@ createDevApp() path: '/create/:templateName', element: , }) + .addPage({ + path: '/scaffolder/tasks/:taskId', + element: , + }) .render(); diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 10cc36809a..79ea075e3d 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -50,7 +50,8 @@ "react-router-dom": "6.0.0-beta.0", "react-use": "^15.3.3", "swr": "^0.3.0", - "zen-observable": "^0.8.15" + "zen-observable": "^0.8.15", + "use-immer": "^0.4.2" }, "devDependencies": { "@backstage/cli": "^0.6.0", diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index 6b7f1b6181..1d6bb2007b 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -17,7 +17,7 @@ import { JsonObject } from '@backstage/config'; import { createApiRef, DiscoveryApi, Observable } from '@backstage/core'; import ObservableImpl from 'zen-observable'; -import { ScaffolderV2Task } from './types'; +import { ScaffolderTask } from './types'; export const scaffolderApiRef = createApiRef({ id: 'plugin.scaffolder.service', @@ -27,6 +27,9 @@ export const scaffolderApiRef = createApiRef({ type LogEvent = { type: 'log' | 'completion'; body: JsonObject; + createdAt: string; + id: string; + taskId: string; }; export class ScaffolderApi { @@ -60,23 +63,10 @@ export class ScaffolderApi { } const { id } = await response.json(); - - // 859d6b78-7c19-4d1a-abe0-9396ab3bd686 - // this.streamLogs({ - // taskId: id, - // }).then(observable => { - // console.log('DEBUG: observable =', observable); - // observable.subscribe({ - // next: thing => console.warn('next', thing), - // error: thing => console.warn('error', thing), - // complete: () => console.warn('complete'), - // }); - // }); - return id; } - async getTask(taskId: string): Promise { + async getTask(taskId: string): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); const url = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}`; return fetch(url).then(x => x.json()); @@ -101,7 +91,7 @@ export class ScaffolderApi { taskId, )}/eventstream`; const eventSource = new EventSource(url); - eventSource.addEventListener('log', event => { + eventSource.addEventListener('log', (event: any) => { if (event.data) { try { subscriber.next(JSON.parse(event.data)); @@ -110,8 +100,7 @@ export class ScaffolderApi { } } }); - eventSource.addEventListener('completion', event => { - eventSource.close(); + eventSource.addEventListener('completion', (event: any) => { if (event.data) { try { subscriber.next(JSON.parse(event.data)); diff --git a/plugins/scaffolder/src/components/JobStage/JobStage.tsx b/plugins/scaffolder/src/components/JobStage/JobStage.tsx index f285cb98c0..1448fedf5c 100644 --- a/plugins/scaffolder/src/components/JobStage/JobStage.tsx +++ b/plugins/scaffolder/src/components/JobStage/JobStage.tsx @@ -104,11 +104,11 @@ export const JobStage = ({ endedAt, startedAt, name, log, status }: Props) => { const [expanded, setExpanded] = useState(false); useEffect(() => { - if (status === 'FAILED') setExpanded(true); + if (status === 'failed') setExpanded(true); }, [status, setExpanded]); const timeElapsed = - status !== 'PENDING' + status === 'processing' ? moment .duration(moment(endedAt ?? moment()).diff(moment(startedAt))) .humanize() @@ -143,9 +143,13 @@ export const JobStage = ({ endedAt, startedAt, name, log, status }: Props) => { {log.length === 0 ? ( - - No logs available for this step - +
+ +
) : ( }> void; -}; - -type Step = { - id: string; - status: 'open' | 'processing' | 'failed' | 'completed'; -}; - -type ReducerState = { - loading: boolean; - error?: Error; - completed: boolean; - - task: ScaffolderV2Task; - - step: [{ [stepId in string]: Step }]; -}; - -type ReducerAction = - | { - type: 'INIT'; - data: ScaffolderV2Task; - } - | { type: 'EVENT'; data: { body: { stepId: string } } } - | { type: 'COMPLETED' } - | { type: 'ERROR'; data: Error }; - -function reducer(state: ReducerState, action: ReducerAction) { - const stepId = action.type === 'EVENT' ? action.data.body.stepId : 'global'; - const currentStep = state[stepId] ?? { log: [], status: 'open' }; - - switch (action.type) { - case 'INIT': - return { - ...state, - ...action.data, - }; - case 'EVENT': - return { - ...state, - [stepId]: { - ...currentStep, - log: [...currentStep.log, event], - }, - }; - case 'COMPLETED': - return { - ...state, - progress: 'done', - }; - case 'ERROR': - return { - error: action.error, - loading: false, - completed: false, - }; - default: - return state; - } -} - -/* - -{} - -{ - id: as'das - spec: { - steps: [ - { id, log: []} - ] - } -} - -*/ -const useTaskEventStream = (taskId: string) => { - // fetch task - - const scaffolderApi = useApi(scaffolderApiRef); - const [state, dispatch] = useReducer(reducer, { - loading: true, - }); - useEffect(() => { - let didCancel = false; - let subscription: Subscription | undefined; - - scaffolderApi.getTask(taskId).then( - task => { - if (didCancel) { - return; - } - dispatch({ type: 'INIT', data: task }); - const observable = scaffolderApi.streamLogs({ taskId }); - subscription = observable.subscribe({ - next: event => dispatch({ type: 'EVENT', data: event }), - error: error => dispatch({ type: 'ERROR', data: error }), - complete: () => dispatch({ type: 'COMPLETED' }), - }); - }, - error => { - if (!didCancel) { - dispatch({ type: 'ERROR', data: error }); - } - }, - ); - - return () => { - didCancel = true; - if (subscription) { - subscription.unsubscribe(); - } - }; - }); - - return state; - - // subscribe to observable, - - // on observer change update the step logs etc + status - - // return { - // steps: { - // stepId: { - // log: [], - // status: - // } - // } - // } -}; - export const JobStatusModal = ({ task, toCatalogLink, open, onModalClose, }: Props) => { - const model = useTaskEventStream(task?.id!); - console.warn(model); + const eventStream = useTaskEventStream(task?.id!); + const renderTitle = () => { switch (task?.status) { case 'completed': @@ -189,24 +50,24 @@ export const JobStatusModal = ({ } }, [task, onModalClose]); + console.log(eventStream); + return ( {renderTitle()} - {/* {!task ? ( */} - - {/* ) : ( */} - {/* (task?.spec.steps ?? []).map(step => ( */} - {/* */} - {/* )) */} - {/* )} */} + {task?.spec.steps + .filter(step => !!eventStream?.steps?.[step.id]) + .map(step => ( + + ))} {/* {job?.status && toCatalogLink && ( diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx new file mode 100644 index 0000000000..6debff5b0e --- /dev/null +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -0,0 +1,121 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { Page, Header, Lifecycle, Content } from '@backstage/core'; +import React from 'react'; +import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'; +import Stepper from '@material-ui/core/Stepper'; +import Step from '@material-ui/core/Step'; +import StepLabel from '@material-ui/core/StepLabel'; +import StepContent from '@material-ui/core/StepContent'; +import Button from '@material-ui/core/Button'; +import Paper from '@material-ui/core/Paper'; +import Typography from '@material-ui/core/Typography'; +import { useParams } from 'react-router'; +import { useTaskEventStream, TaskStream } from '../hooks/useEventStream'; +import LazyLog from 'react-lazylog/build/LazyLog'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + width: '100%', + }, + button: { + marginTop: theme.spacing(1), + marginRight: theme.spacing(1), + }, + actionsContainer: { + marginBottom: theme.spacing(2), + }, + resetContainer: { + padding: theme.spacing(3), + }, + }), +); + +export const TaskStepper = ({ taskStream }: { taskStream: TaskStream }) => { + const classes = useStyles(); + const [activeStep, setActiveStep] = React.useState(0); + const steps = taskStream?.task?.spec.steps ?? []; + + const handleNext = () => { + setActiveStep(prevActiveStep => prevActiveStep + 1); + }; + + const handleBack = () => { + setActiveStep(prevActiveStep => prevActiveStep - 1); + }; + + const handleReset = () => { + setActiveStep(0); + }; + + return ( +
+ + {steps.map((step, index) => ( + + + {step.name} + + +
+ +
+
+
+ ))} +
+ {activeStep === steps.length && ( + + All steps completed - you're finished + + + )} +
+ ); +}; + +export const TaskPage = () => { + const { taskId } = useParams(); + const taskStream = useTaskEventStream(taskId); + + return ( + +
+ Task Activity + + } + subtitle={`Activity for task: ${taskId}`} + /> + + + + + ); +}; diff --git a/plugins/scaffolder/src/components/TaskPage/index.ts b/plugins/scaffolder/src/components/TaskPage/index.ts new file mode 100644 index 0000000000..3695c2792e --- /dev/null +++ b/plugins/scaffolder/src/components/TaskPage/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { TaskPage } from './TaskPage'; diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index a3c7ceee12..d9a00d0dda 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -20,15 +20,10 @@ import { Header, InfoCard, Lifecycle, - Observable, Page, useApi, } from '@backstage/core'; -import { - catalogApiRef, - entityRoute, - entityRouteParams, -} from '@backstage/plugin-catalog-react'; +import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { LinearProgress } from '@material-ui/core'; import { IChangeEvent } from '@rjsf/core'; import parseGitUrl from 'git-url-parse'; @@ -38,6 +33,7 @@ import { useParams } from 'react-router-dom'; import { useAsync } from 'react-use'; import { scaffolderApiRef } from '../../api'; import { rootRoute } from '../../routes'; +import { ScaffolderTask } from '../../types'; import { useTaskPolling } from '../hooks/useTaskPolling'; import { JobStatusModal } from '../JobStatusModal'; import { MultistepJsonForm } from '../MultistepJsonForm'; @@ -77,6 +73,7 @@ const OWNER_REPO_SCHEMA = { }, }, }; + export const TemplatePage = () => { const errorApi = useApi(errorApiRef); const catalogApi = useApi(catalogApiRef); @@ -92,39 +89,13 @@ export const TemplatePage = () => { [setFormState, formState], ); - const [taskId, setTaskId] = useState(null); - const task = useTaskPolling(taskId, async task => { - console.warn('onFinish is called'); - // if (!jobItem.metadata.catalogInfoUrl) { - // errorApi.post( - // new Error(`No catalogInfoUrl returned from the scaffolder`), - // ); - // return; - // } - // try { - // const { - // entities: [createdEntity], - // } = await catalogApi.addLocation({ - // target: jobItem.metadata.catalogInfoUrl, - // }); - // const resolvedPath = generatePath( - // `/catalog/${entityRoute.path}`, - // entityRouteParams(createdEntity), - // ); - // setCatalogLink(resolvedPath); - // } catch (ex) { - // errorApi.post( - // new Error( - // `Something went wrong trying to add the new 'catalog-info.yaml' to the catalog`, - // ), - // ); - // } - }); + const [task, setTask] = useState(undefined); const handleCreate = async () => { try { const id = await scaffolderApi.scaffold(templateName, formState); - setTaskId(id); + const returned = await scaffolderApi.getTask(id); + setTask(returned); setModalOpen(true); } catch (e) { errorApi.post(e); @@ -166,7 +137,6 @@ export const TemplatePage = () => { onModalClose={() => setModalOpen(false)} /> )} - ) {template && ( { + current[next.id] = { log: [], status: 'open', id: next.id }; + return current; + }, {} as { [stepId in string]: Step }); + draft.loading = false; + draft.error = undefined; + draft.completed = false; + draft.task = action.data; + draft.log = []; + return; + } + + case 'LOG': { + const stepId = action.data.body.stepId ?? 'global'; + const currentStep = draft.steps?.[stepId]; + const logLine = `${action.data.createdAt} ${action.data.body.message}`; + + if (!currentStep) { + draft.log.push(logLine); + return; + } + + currentStep.log.push(logLine); + + if ( + action.data.body.status && + action.data.body.status !== currentStep.status + ) { + currentStep.status = action.data.body.status; + + if (currentStep.status === 'processing') { + currentStep.startedAt = action.data.createdAt; + } + + if ( + ['zcancelled', 'failed', 'completed'].includes(currentStep.status) + ) { + currentStep.endedAt = action.data.createdAt; + } + } + + return; + } + + case 'COMPLETED': { + draft.completed = true; + return; + } + + case 'ERROR': { + draft.error = action.data; + draft.loading = false; + draft.completed = true; + return; + } + + default: + return; + } +} + +export const useTaskEventStream = (taskId: string): TaskStream => { + const scaffolderApi = useApi(scaffolderApiRef); + const [state, dispatch] = useImmerReducer(reducer, { + loading: true, + completed: false, + log: [], + steps: {} as { [stepId in string]: Step }, + }); + + useEffect(() => { + let didCancel = false; + let subscription: Subscription | undefined; + + scaffolderApi.getTask(taskId).then( + task => { + if (didCancel) { + return; + } + dispatch({ type: 'INIT', data: task }); + const observable = scaffolderApi.streamLogs({ taskId }); + subscription = observable.subscribe({ + next: event => { + switch (event.type) { + case 'log': + return dispatch({ type: 'LOG', data: event }); + default: + throw new Error( + `Unhandled event type ${event.type} in observer`, + ); + } + }, + error: error => dispatch({ type: 'ERROR', data: error }), + complete: () => dispatch({ type: 'COMPLETED' }), + }); + }, + error => { + if (!didCancel) { + dispatch({ type: 'ERROR', data: error }); + } + }, + ); + + return () => { + didCancel = true; + if (subscription) { + subscription.unsubscribe(); + } + }; + }, [scaffolderApi, dispatch, taskId]); + + return state; +}; diff --git a/plugins/scaffolder/src/components/hooks/useTaskPolling.ts b/plugins/scaffolder/src/components/hooks/useTaskPolling.ts deleted file mode 100644 index dbe6c67b89..0000000000 --- a/plugins/scaffolder/src/components/hooks/useTaskPolling.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 { useEffect, useState } from 'react'; -import { ScaffolderV2Task } from '../../types'; -import { useApi } from '@backstage/core'; -import { scaffolderApiRef } from '../../api'; -import { useInterval } from 'react-use'; - -const DEFAULT_POLLING_INTERVAL = 1000; - -export const useTaskPolling = ( - taskId: string | null, - onFinish?: (t: ScaffolderV2Task) => void, - pollingInterval = DEFAULT_POLLING_INTERVAL, -) => { - const scaffolderApi = useApi(scaffolderApiRef); - const [currentTask, setCurrentTask] = useState(null); - - useInterval(async () => { - if (taskId) { - setCurrentTask(await scaffolderApi.getTask(taskId)); - } - }, pollingInterval); - - useEffect(() => { - switch (currentTask?.status) { - case 'failed': - case 'cancelled': - case 'completed': - return onFinish?.(currentTask); - default: - return undefined; - } - }, [currentTask, onFinish]); - - return currentTask; -}; diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index 5f102e7853..e87e8d0107 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -19,6 +19,7 @@ export { scaffolderPlugin as plugin, TemplateIndexPage, TemplatePage, + TaskPage, } from './plugin'; export { ScaffolderApi, scaffolderApiRef } from './api'; -export { rootRoute, templateRoute } from './routes'; +export { rootRoute, templateRoute, taskRoute } from './routes'; diff --git a/plugins/scaffolder/src/plugin.ts b/plugins/scaffolder/src/plugin.ts index ed20705185..d8c20618f3 100644 --- a/plugins/scaffolder/src/plugin.ts +++ b/plugins/scaffolder/src/plugin.ts @@ -22,7 +22,8 @@ import { } from '@backstage/core'; import { ScaffolderPage as ScaffolderPageComponent } from './components/ScaffolderPage'; import { TemplatePage as TemplatePageComponent } from './components/TemplatePage'; -import { rootRoute, templateRoute } from './routes'; +import { TaskPage as TaskPageComponent } from './components/TaskPage'; +import { rootRoute, templateRoute, taskRoute } from './routes'; import { scaffolderApiRef, ScaffolderApi } from './api'; export const scaffolderPlugin = createPlugin({ @@ -37,10 +38,12 @@ export const scaffolderPlugin = createPlugin({ register({ router }) { router.addRoute(rootRoute, ScaffolderPageComponent); router.addRoute(templateRoute, TemplatePageComponent); + router.addRoute(taskRoute, TaskPageComponent); }, routes: { templateIndex: rootRoute, template: templateRoute, + task: taskRoute, }, }); @@ -59,3 +62,10 @@ export const TemplatePage = scaffolderPlugin.provide( mountPoint: templateRoute, }), ); + +export const TaskPage = scaffolderPlugin.provide( + createRoutableExtension({ + component: () => import('./components/TaskPage').then(m => m.TaskPage), + mountPoint: taskRoute, + }), +); diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts index 8efd1d3aaf..09af4717aa 100644 --- a/plugins/scaffolder/src/routes.ts +++ b/plugins/scaffolder/src/routes.ts @@ -24,3 +24,8 @@ export const templateRoute = createRouteRef({ path: '/create/:templateName', title: 'Entity creation', }); + +export const taskRoute = createRouteRef({ + path: '/scaffolder/task/:taskId', + title: 'Task information', +}); diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index 8cdf22c18b..fe1aec5286 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -37,17 +37,17 @@ export type Stage = { endedAt?: string; }; -export type ScaffolderV2Step = { +export type ScaffolderStep = { id: string; name: string; action: string; parameters?: { [name: string]: JsonValue }; }; -export type ScaffolderV2Task = { +export type ScaffolderTask = { id: string; spec: { - steps: ScaffolderV2Step[]; + steps: ScaffolderStep[]; }; status: 'failed' | 'completed' | 'processing' | 'open' | 'cancelled'; lastHeartbeatAt: string; From 36d0954886fe0dde7503253a62b33bca47b57b66 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 10 Feb 2021 11:41:22 +0100 Subject: [PATCH 006/270] refactor scaffolder --- .../software-templates/installation.md | 19 --- plugins/scaffolder/dev/index.tsx | 7 +- plugins/scaffolder/package.json | 7 +- plugins/scaffolder/src/api.ts | 29 +++- .../JobStatusModal/JobStatusModal.tsx | 84 --------- .../src/components/JobStatusModal/index.ts | 16 -- .../src/components/TaskPage/TaskPage.tsx | 160 +++++++++++++++--- .../components/TemplatePage/TemplatePage.tsx | 29 ++-- plugins/scaffolder/src/index.ts | 3 +- plugins/scaffolder/src/plugin.ts | 4 +- plugins/scaffolder/src/routes.ts | 1 + 11 files changed, 185 insertions(+), 174 deletions(-) delete mode 100644 plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx delete mode 100644 plugins/scaffolder/src/components/JobStatusModal/index.ts diff --git a/docs/features/software-templates/installation.md b/docs/features/software-templates/installation.md index 8d2fa7727d..a02755c9c7 100644 --- a/docs/features/software-templates/installation.md +++ b/docs/features/software-templates/installation.md @@ -36,25 +36,6 @@ Add the following entry to the head of your `packages/app/src/plugins.ts`: export { plugin as ScaffolderPlugin } from '@backstage/plugin-scaffolder'; ``` -Add the following to your `packages/app/src/apis.ts`: - -```ts -import { scaffolderApiRef, ScaffolderApi } from '@backstage/plugin-scaffolder'; - -// Inside the ApiRegistry builder function ... - -builder.add( - scaffolderApiRef, - new ScaffolderApi({ - apiOrigin: backendUrl, - basePath: '/scaffolder/v1', - }), -); -``` - -Where `backendUrl` is the `backend.baseUrl` from config, i.e. -`const backendUrl = config.getString('backend.baseUrl')`. - This is all that is needed for the frontend part of the Scaffolder plugin to work! diff --git a/plugins/scaffolder/dev/index.tsx b/plugins/scaffolder/dev/index.tsx index 5bdeec3e10..250a59e775 100644 --- a/plugins/scaffolder/dev/index.tsx +++ b/plugins/scaffolder/dev/index.tsx @@ -19,9 +19,8 @@ import { createDevApp } from '@backstage/dev-utils'; import { discoveryApiRef } from '@backstage/core'; import { CatalogClient } from '@backstage/catalog-client'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import { TemplateIndexPage, TemplatePage } from '../src/plugin'; -import { ScaffolderApi, scaffolderApiRef } from '../src'; -import { TaskPage } from '../src/components/TaskPage'; +import { TemplateIndexPage, TemplatePage, TaskPage } from '../src/plugin'; +import { ScaffolderClient, scaffolderApiRef } from '../src'; createDevApp() .registerApi({ @@ -32,7 +31,7 @@ createDevApp() .registerApi({ api: scaffolderApiRef, deps: { discoveryApi: discoveryApiRef }, - factory: ({ discoveryApi }) => new ScaffolderApi({ discoveryApi }), + factory: ({ discoveryApi }) => new ScaffolderClient({ discoveryApi }), }) .addPage({ path: '/create', diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 79ea075e3d..b332c37c28 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -31,8 +31,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.0", "@backstage/config": "^0.1.2", + "@backstage/core": "^0.6.0", "@backstage/plugin-catalog-react": "^0.0.2", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", @@ -41,6 +41,7 @@ "@rjsf/core": "^2.4.0", "@rjsf/material-ui": "^2.4.0", "classnames": "^2.2.6", + "clsx": "^1.1.1", "git-url-parse": "^11.4.4", "moment": "^2.26.0", "react": "^16.13.1", @@ -50,8 +51,8 @@ "react-router-dom": "6.0.0-beta.0", "react-use": "^15.3.3", "swr": "^0.3.0", - "zen-observable": "^0.8.15", - "use-immer": "^0.4.2" + "use-immer": "^0.4.2", + "zen-observable": "^0.8.15" }, "devDependencies": { "@backstage/cli": "^0.6.0", diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index 1d6bb2007b..c51bd4a8be 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -32,7 +32,27 @@ type LogEvent = { taskId: string; }; -export class ScaffolderApi { +export interface 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 + */ + scaffold(templateName: string, values: Record): Promise; + + getTask(taskId: string): Promise; + + streamLogs({ + taskId, + after, + }: { + taskId: string; + after?: number; + }): Observable; +} +export class ScaffolderClient implements ScaffolderApi { private readonly discoveryApi: DiscoveryApi; constructor(options: { discoveryApi: DiscoveryApi }) { @@ -46,7 +66,10 @@ export class ScaffolderApi { * @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 */ - async scaffold(templateName: string, values: Record) { + async scaffold( + templateName: string, + values: Record, + ): Promise { const url = `${await this.discoveryApi.getBaseUrl('scaffolder')}/v2/tasks`; const response = await fetch(url, { method: 'POST', @@ -62,7 +85,7 @@ export class ScaffolderApi { throw new Error(`Backend request failed, ${status} ${body.trim()}`); } - const { id } = await response.json(); + const { id } = (await response.json()) as { id: string }; return id; } diff --git a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx deleted file mode 100644 index 7ef6454588..0000000000 --- a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 { Button, Observable, Subscription, useApi } from '@backstage/core'; -import { - Button as Action, - Dialog, - DialogActions, - DialogContent, - DialogTitle, -} from '@material-ui/core'; - -export const JobStatusModal = ({ - task, - toCatalogLink, - open, - onModalClose, -}: Props) => { - const eventStream = useTaskEventStream(task?.id!); - - const renderTitle = () => { - switch (task?.status) { - case 'completed': - return 'Successfully created component'; - case 'failed': - return 'Failed to create component'; - default: - return 'Create component'; - } - }; - const onClose = useCallback(() => { - if (!task) { - return; - } - // Disallow closing modal if the job is in progress. - if (task.status !== 'processing') { - onModalClose(); - } - }, [task, onModalClose]); - - console.log(eventStream); - - return ( - - {renderTitle()} - - {task?.spec.steps - .filter(step => !!eventStream?.steps?.[step.id]) - .map(step => ( - - ))} - - {/* {job?.status && toCatalogLink && ( - - - - )} - {job?.status === 'FAILED' && ( - - Close - - )} */} - - ); -}; diff --git a/plugins/scaffolder/src/components/JobStatusModal/index.ts b/plugins/scaffolder/src/components/JobStatusModal/index.ts deleted file mode 100644 index 5598999fe3..0000000000 --- a/plugins/scaffolder/src/components/JobStatusModal/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 { JobStatusModal } from './JobStatusModal'; diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index 6debff5b0e..2f22cb9849 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -15,18 +15,102 @@ */ import { Page, Header, Lifecycle, Content } from '@backstage/core'; -import React from 'react'; -import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'; +import React, { useState, useEffect } from 'react'; +import { + makeStyles, + Theme, + createStyles, + withStyles, +} from '@material-ui/core/styles'; import Stepper from '@material-ui/core/Stepper'; import Step from '@material-ui/core/Step'; import StepLabel from '@material-ui/core/StepLabel'; import StepContent from '@material-ui/core/StepContent'; +import StepConnector from '@material-ui/core/StepConnector'; import Button from '@material-ui/core/Button'; import Paper from '@material-ui/core/Paper'; +import clsx from 'clsx'; +import Check from '@material-ui/icons/Check'; +import Cancel from '@material-ui/icons/Cancel'; import Typography from '@material-ui/core/Typography'; import { useParams } from 'react-router'; import { useTaskEventStream, TaskStream } from '../hooks/useEventStream'; import LazyLog from 'react-lazylog/build/LazyLog'; +import { StepButton, StepIconProps } from '@material-ui/core'; + +const QontoConnector = withStyles({ + active: { + '& $line': { + borderColor: '#784af4', + }, + }, + completed: { + '& $line': { + borderColor: '#784af4', + }, + }, + line: { + borderColor: '#eaeaf0', + borderTopWidth: 3, + borderRadius: 1, + }, +})(StepConnector); + +const useQontoStepIconStyles = makeStyles({ + root: { + color: '#eaeaf0', + display: 'flex', + height: 22, + alignItems: 'center', + }, + active: { + color: 'grey', + }, + error: { + color: 'red', + }, + circle: { + width: 8, + height: 8, + borderRadius: '50%', + backgroundColor: 'currentColor', + }, + completed: { + color: 'green', + zIndex: 1, + fontSize: 18, + }, +}); + +function QontoStepIcon(props: StepIconProps) { + const classes = useQontoStepIconStyles(); + const { active, completed, error } = props; + + const getComponent = () => { + if (error) { + return ; + } + + if (completed) { + return ; + } + + if (active) { + return
; + } + return undefined; + }; + return ( +
+ {getComponent()} +
+ ); +} const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -48,7 +132,9 @@ const useStyles = makeStyles((theme: Theme) => export const TaskStepper = ({ taskStream }: { taskStream: TaskStream }) => { const classes = useStyles(); - const [activeStep, setActiveStep] = React.useState(0); + const [activeStep, setActiveStep] = useState(0); + const [expandAll, setExpandAll] = useState(false); + const steps = taskStream?.task?.spec.steps ?? []; const handleNext = () => { @@ -59,32 +145,60 @@ export const TaskStepper = ({ taskStream }: { taskStream: TaskStream }) => { setActiveStep(prevActiveStep => prevActiveStep - 1); }; + const handleStep = (step: number) => { + setExpandAll(false); + setActiveStep(step); + }; + const handleReset = () => { setActiveStep(0); }; + useEffect(() => { + const activeIndex = Object.values(taskStream?.steps ?? {}).findIndex(step => + ['failed', 'processing'].includes(step.status), + ); + setActiveStep(activeIndex); + }, [taskStream]); + return (
- - {steps.map((step, index) => ( - - - {step.name} - - -
- -
-
-
- ))} + + + + + {steps.map((step, index) => { + const isCompleted = + taskStream.steps?.[step.id].status === 'completed'; + const isFailed = taskStream.steps?.[step.id].status === 'failed'; + return ( + + handleStep(index)}> + + {step.name} + + + + +
+ +
+
+
+ ); + })}
{activeStep === steps.length && ( diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index d9a00d0dda..7239781b5c 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -22,20 +22,19 @@ import { Lifecycle, Page, useApi, + useRouteRef, } from '@backstage/core'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { LinearProgress } from '@material-ui/core'; import { IChangeEvent } from '@rjsf/core'; import parseGitUrl from 'git-url-parse'; import React, { useCallback, useState } from 'react'; -import { generatePath, Navigate } from 'react-router'; +import { Navigate } from 'react-router'; import { useParams } from 'react-router-dom'; import { useAsync } from 'react-use'; import { scaffolderApiRef } from '../../api'; -import { rootRoute } from '../../routes'; -import { ScaffolderTask } from '../../types'; -import { useTaskPolling } from '../hooks/useTaskPolling'; -import { JobStatusModal } from '../JobStatusModal'; +import { rootRoute, taskRoute } from '../../routes'; +import { useNavigate } from 'react-router'; import { MultistepJsonForm } from '../MultistepJsonForm'; const useTemplate = ( @@ -79,24 +78,24 @@ export const TemplatePage = () => { const catalogApi = useApi(catalogApiRef); const scaffolderApi = useApi(scaffolderApiRef); const { templateName } = useParams(); - const [catalogLink, setCatalogLink] = useState(); + const navigate = useNavigate(); + const tasks = useRouteRef(taskRoute); const { template, loading } = useTemplate(templateName, catalogApi); const [formState, setFormState] = useState({}); - const [modalOpen, setModalOpen] = useState(false); const handleFormReset = () => setFormState({}); + const handleChange = useCallback( (e: IChangeEvent) => setFormState({ ...formState, ...e.formData }), [setFormState, formState], ); - const [task, setTask] = useState(undefined); + const [taskId, setTaskId] = useState(undefined); const handleCreate = async () => { try { const id = await scaffolderApi.scaffold(templateName, formState); - const returned = await scaffolderApi.getTask(id); - setTask(returned); - setModalOpen(true); + setTaskId(id); + navigate(tasks({ taskId: id })); } catch (e) { errorApi.post(e); } @@ -129,14 +128,6 @@ export const TemplatePage = () => { /> {loading && } - {task && ( - setModalOpen(false)} - /> - )} {template && ( new ScaffolderApi({ discoveryApi }), + factory: ({ discoveryApi }) => new ScaffolderClient({ discoveryApi }), }), ], register({ router }) { diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts index 09af4717aa..6b55f89ff1 100644 --- a/plugins/scaffolder/src/routes.ts +++ b/plugins/scaffolder/src/routes.ts @@ -28,4 +28,5 @@ export const templateRoute = createRouteRef({ export const taskRoute = createRouteRef({ path: '/scaffolder/task/:taskId', title: 'Task information', + params: ['taskId'], }); From 3796a666e0d3c4b3e470a3fe1b3391473cba238a Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 10 Feb 2021 11:42:04 +0100 Subject: [PATCH 007/270] scaffolder-backend: convert string to number --- .../src/scaffolder/tasks/DatabaseTaskStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index 4190a5d58f..1dee11ba20 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -255,7 +255,7 @@ export class DatabaseTaskStore implements TaskStore { try { const body = JSON.parse(event.body) as JsonObject; return { - id: event.id, + id: Number(event.id), taskId, body, type: event.event_type, From 888497b55cb6067f7a866db4ecd0cc7edae2e5d7 Mon Sep 17 00:00:00 2001 From: mclarke Date: Wed, 10 Feb 2021 11:16:37 +0000 Subject: [PATCH 008/270] replace kubernetes fanout request function with class --- ...est.ts => KubernetesFanOutHandler.test.ts} | 108 +++++++++-------- .../src/service/KubernetesFanOutHandler.ts | 109 ++++++++++++++++++ .../getKubernetesObjectsForServiceHandler.ts | 107 ----------------- .../src/service/router.test.ts | 41 +++---- .../kubernetes-backend/src/service/router.ts | 21 +--- 5 files changed, 183 insertions(+), 203 deletions(-) rename plugins/kubernetes-backend/src/service/{getKubernetesObjectsByServiceIdHandler.test.ts => KubernetesFanOutHandler.test.ts} (73%) create mode 100644 plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts delete mode 100644 plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts similarity index 73% rename from plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts rename to plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts index 5c6513e57c..95ec4f0aba 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts @@ -14,11 +14,9 @@ * limitations under the License. */ -import { handleGetKubernetesObjectsForService } from './getKubernetesObjectsForServiceHandler'; import { getVoidLogger } from '@backstage/backend-common'; import { ObjectFetchParams } from '..'; - -const TEST_SERVICE_ID = 'my-service'; +import { KubernetesFanOutHandler } from './KubernetesFanOutHandler'; const fetchObjectsForService = jest.fn(); @@ -81,34 +79,34 @@ describe('handleGetKubernetesObjectsForService', () => { mockFetch(fetchObjectsForService); - const result = await handleGetKubernetesObjectsForService( - TEST_SERVICE_ID, + const sut = new KubernetesFanOutHandler( + getVoidLogger(), { - fetchObjectsForService: fetchObjectsForService, + fetchObjectsForService, }, { getClustersByServiceId, }, - getVoidLogger(), - { - entity: { - apiVersion: 'backstage.io/v1beta1', - kind: 'Component', - metadata: { - name: 'test-component', - annotations: { - 'backstage.io/kubernetes-labels-selector': - 'backstage.io/test-label=test-component', - }, - }, - spec: { - type: 'service', - lifecycle: 'production', - owner: 'joe', + ); + + const result = await sut.getKubernetesObjectsByEntity({ + entity: { + apiVersion: 'backstage.io/v1beta1', + kind: 'Component', + metadata: { + name: 'test-component', + annotations: { + 'backstage.io/kubernetes-labels-selector': + 'backstage.io/test-label=test-component', }, }, + spec: { + type: 'service', + lifecycle: 'production', + owner: 'joe', + }, }, - ); + }); expect(getClustersByServiceId.mock.calls.length).toBe(1); expect(fetchObjectsForService.mock.calls.length).toBe(1); @@ -124,7 +122,7 @@ describe('handleGetKubernetesObjectsForService', () => { resources: [ { metadata: { - name: 'my-pods-my-service-test-cluster', + name: 'my-pods-test-component-test-cluster', }, }, ], @@ -134,7 +132,7 @@ describe('handleGetKubernetesObjectsForService', () => { resources: [ { metadata: { - name: 'my-configmaps-my-service-test-cluster', + name: 'my-configmaps-test-component-test-cluster', }, }, ], @@ -144,7 +142,7 @@ describe('handleGetKubernetesObjectsForService', () => { resources: [ { metadata: { - name: 'my-services-my-service-test-cluster', + name: 'my-services-test-component-test-cluster', }, }, ], @@ -172,37 +170,37 @@ describe('handleGetKubernetesObjectsForService', () => { mockFetch(fetchObjectsForService); - const result = await handleGetKubernetesObjectsForService( - TEST_SERVICE_ID, + const sut = new KubernetesFanOutHandler( + getVoidLogger(), { - fetchObjectsForService: fetchObjectsForService, + fetchObjectsForService, }, { getClustersByServiceId, }, - getVoidLogger(), - { - auth: { - google: 'google_token_123', + ); + + const result = await sut.getKubernetesObjectsByEntity({ + auth: { + google: 'google_token_123', + }, + entity: { + apiVersion: 'backstage.io/v1beta1', + kind: 'Component', + metadata: { + name: 'test-component', + annotations: { + 'backstage.io/kubernetes-labels-selector': + 'backstage.io/test-label=test-component', + }, }, - entity: { - apiVersion: 'backstage.io/v1beta1', - kind: 'Component', - metadata: { - name: 'test-component', - annotations: { - 'backstage.io/kubernetes-labels-selector': - 'backstage.io/test-label=test-component', - }, - }, - spec: { - type: 'service', - lifecycle: 'production', - owner: 'joe', - }, + spec: { + type: 'service', + lifecycle: 'production', + owner: 'joe', }, }, - ); + }); expect(getClustersByServiceId.mock.calls.length).toBe(1); expect(fetchObjectsForService.mock.calls.length).toBe(2); @@ -218,7 +216,7 @@ describe('handleGetKubernetesObjectsForService', () => { resources: [ { metadata: { - name: 'my-pods-my-service-test-cluster', + name: 'my-pods-test-component-test-cluster', }, }, ], @@ -228,7 +226,7 @@ describe('handleGetKubernetesObjectsForService', () => { resources: [ { metadata: { - name: 'my-configmaps-my-service-test-cluster', + name: 'my-configmaps-test-component-test-cluster', }, }, ], @@ -238,7 +236,7 @@ describe('handleGetKubernetesObjectsForService', () => { resources: [ { metadata: { - name: 'my-services-my-service-test-cluster', + name: 'my-services-test-component-test-cluster', }, }, ], @@ -256,7 +254,7 @@ describe('handleGetKubernetesObjectsForService', () => { resources: [ { metadata: { - name: 'my-pods-my-service-other-cluster', + name: 'my-pods-test-component-other-cluster', }, }, ], @@ -266,7 +264,7 @@ describe('handleGetKubernetesObjectsForService', () => { resources: [ { metadata: { - name: 'my-configmaps-my-service-other-cluster', + name: 'my-configmaps-test-component-other-cluster', }, }, ], @@ -276,7 +274,7 @@ describe('handleGetKubernetesObjectsForService', () => { resources: [ { metadata: { - name: 'my-services-my-service-other-cluster', + name: 'my-services-test-component-other-cluster', }, }, ], diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts new file mode 100644 index 0000000000..0a2fc1488a --- /dev/null +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -0,0 +1,109 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { Logger } from 'winston'; +import { + ClusterDetails, + KubernetesFetcher, + KubernetesObjectTypes, + KubernetesRequestBody, + KubernetesServiceLocator, +} from '../types/types'; +import { KubernetesAuthTranslator } from '../kubernetes-auth-translator/types'; +import { KubernetesAuthTranslatorGenerator } from '../kubernetes-auth-translator/KubernetesAuthTranslatorGenerator'; + +const DEFAULT_OBJECTS = new Set([ + 'pods', + 'services', + 'configmaps', + 'deployments', + 'replicasets', + 'horizontalpodautoscalers', + 'ingresses', +]); + +export class KubernetesFanOutHandler { + private readonly logger: Logger; + private readonly fetcher: KubernetesFetcher; + private readonly serviceLocator: KubernetesServiceLocator; + + constructor( + logger: Logger, + fetcher: KubernetesFetcher, + serviceLocator: KubernetesServiceLocator, + ) { + this.logger = logger; + this.fetcher = fetcher; + this.serviceLocator = serviceLocator; + } + + async getKubernetesObjectsByEntity( + requestBody: KubernetesRequestBody, + objectTypesToFetch: Set = DEFAULT_OBJECTS, + ) { + const entityName = requestBody.entity.metadata.name; + + const clusterDetails: ClusterDetails[] = await this.serviceLocator.getClustersByServiceId( + entityName, + ); + + // Execute all of these async actions simultaneously/without blocking sequentially as no common object is modified by them + const promises: Promise[] = clusterDetails.map(cd => { + const kubernetesAuthTranslator: KubernetesAuthTranslator = KubernetesAuthTranslatorGenerator.getKubernetesAuthTranslatorInstance( + cd.authProvider, + ); + return kubernetesAuthTranslator.decorateClusterDetailsWithAuth( + cd, + requestBody, + ); + }); + const clusterDetailsDecoratedForAuth: ClusterDetails[] = await Promise.all( + promises, + ); + + this.logger.info( + `entity.metadata.name=${entityName} clusterDetails=[${clusterDetailsDecoratedForAuth + .map(c => c.name) + .join(', ')}]`, + ); + + const labelSelector: string = + requestBody.entity?.metadata?.annotations?.[ + 'backstage.io/kubernetes-label-selector' + ] || `backstage.io/kubernetes-id=${entityName}`; + + return Promise.all( + clusterDetailsDecoratedForAuth.map(clusterDetailsItem => { + return this.fetcher + .fetchObjectsForService({ + serviceId: entityName, + clusterDetails: clusterDetailsItem, + objectTypesToFetch, + labelSelector, + }) + .then(result => { + return { + cluster: { + name: clusterDetailsItem.name, + }, + resources: result.responses, + errors: result.errors, + }; + }); + }), + ).then(r => ({ items: r })); + } +} diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts deleted file mode 100644 index 811a3c8acb..0000000000 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 { Logger } from 'winston'; -import { - KubernetesRequestBody, - ClusterDetails, - KubernetesServiceLocator, - KubernetesFetcher, - KubernetesObjectTypes, - ObjectsByEntityResponse, - ObjectFetchParams, -} from '../types/types'; -import { KubernetesAuthTranslator } from '../kubernetes-auth-translator/types'; -import { KubernetesAuthTranslatorGenerator } from '../kubernetes-auth-translator/KubernetesAuthTranslatorGenerator'; - -export type GetKubernetesObjectsForServiceHandler = ( - serviceId: string, - fetcher: KubernetesFetcher, - serviceLocator: KubernetesServiceLocator, - logger: Logger, - requestBody: KubernetesRequestBody, - objectTypesToFetch?: Set, -) => Promise; - -const DEFAULT_OBJECTS = new Set([ - 'pods', - 'services', - 'configmaps', - 'deployments', - 'replicasets', - 'horizontalpodautoscalers', - 'ingresses', -]); - -// Fans out the request to all clusters that the service lives in, aggregates their responses together -export const handleGetKubernetesObjectsForService: GetKubernetesObjectsForServiceHandler = async ( - serviceId, - fetcher, - serviceLocator, - logger, - requestBody, - objectTypesToFetch = DEFAULT_OBJECTS, -) => { - const clusterDetails: ClusterDetails[] = await serviceLocator.getClustersByServiceId( - serviceId, - ); - - // Execute all of these async actions simultaneously/without blocking sequentially as no common object is modified by them - const promises: Promise[] = clusterDetails.map(cd => { - const kubernetesAuthTranslator: KubernetesAuthTranslator = KubernetesAuthTranslatorGenerator.getKubernetesAuthTranslatorInstance( - cd.authProvider, - ); - return kubernetesAuthTranslator.decorateClusterDetailsWithAuth( - cd, - requestBody, - ); - }); - const clusterDetailsDecoratedForAuth: ClusterDetails[] = await Promise.all( - promises, - ); - - logger.info( - `serviceId=${serviceId} clusterDetails=[${clusterDetailsDecoratedForAuth - .map(c => c.name) - .join(', ')}]`, - ); - - const labelSelector: string = - requestBody.entity?.metadata?.annotations?.[ - 'backstage.io/kubernetes-label-selector' - ] || `backstage.io/kubernetes-id=${requestBody.entity.metadata.name}`; - - return Promise.all( - clusterDetailsDecoratedForAuth.map(clusterDetailsItem => { - return fetcher - .fetchObjectsForService({ - serviceId, - clusterDetails: clusterDetailsItem, - objectTypesToFetch, - labelSelector, - } as ObjectFetchParams) - .then(result => { - return { - cluster: { - name: clusterDetailsItem.name, - }, - resources: result.responses, - errors: result.errors, - }; - }); - }), - ).then(r => ({ items: r })); -}; diff --git a/plugins/kubernetes-backend/src/service/router.test.ts b/plugins/kubernetes-backend/src/service/router.test.ts index a407ce9693..c91cae27df 100644 --- a/plugins/kubernetes-backend/src/service/router.test.ts +++ b/plugins/kubernetes-backend/src/service/router.test.ts @@ -18,35 +18,18 @@ import { getVoidLogger } from '@backstage/backend-common'; import express from 'express'; import request from 'supertest'; import { makeRouter } from './router'; -import { - KubernetesServiceLocator, - KubernetesFetcher, - ObjectsByEntityResponse, -} from '..'; +import { KubernetesFanOutHandler } from './KubernetesFanOutHandler'; describe('router', () => { let app: express.Express; - let kubernetesFetcher: jest.Mocked; - let kubernetesServiceLocator: jest.Mocked; - let handleGetByServiceId: jest.Mock>; + let kubernetesFanOutHandler: jest.Mocked; beforeAll(async () => { - kubernetesFetcher = { - fetchObjectsForService: jest.fn(), - }; + kubernetesFanOutHandler = { + getKubernetesObjectsByEntity: jest.fn(), + } as any; - kubernetesServiceLocator = { - getClustersByServiceId: jest.fn(), - }; - - handleGetByServiceId = jest.fn(); - - const router = makeRouter( - getVoidLogger(), - kubernetesFetcher, - kubernetesServiceLocator, - handleGetByServiceId as any, - ); + const router = makeRouter(getVoidLogger(), kubernetesFanOutHandler); app = express().use(router); }); @@ -67,7 +50,9 @@ describe('router', () => { ], }, } as any; - handleGetByServiceId.mockReturnValueOnce(Promise.resolve(result)); + kubernetesFanOutHandler.getKubernetesObjectsByEntity.mockReturnValueOnce( + Promise.resolve(result), + ); const response = await request(app).post('/services/test-service'); @@ -87,7 +72,9 @@ describe('router', () => { ], }, } as any; - handleGetByServiceId.mockReturnValueOnce(Promise.resolve(result)); + kubernetesFanOutHandler.getKubernetesObjectsByEntity.mockReturnValueOnce( + Promise.resolve(result), + ); const response = await request(app) .post('/services/test-service') @@ -103,7 +90,9 @@ describe('router', () => { }); it('internal error: lists kubernetes objects', async () => { - handleGetByServiceId.mockRejectedValue(Error('some internal error')); + kubernetesFanOutHandler.getKubernetesObjectsByEntity.mockRejectedValue( + Error('some internal error'), + ); const response = await request(app).post('/services/test-service'); diff --git a/plugins/kubernetes-backend/src/service/router.ts b/plugins/kubernetes-backend/src/service/router.ts index 38a02ad970..32b6dfb832 100644 --- a/plugins/kubernetes-backend/src/service/router.ts +++ b/plugins/kubernetes-backend/src/service/router.ts @@ -21,19 +21,15 @@ import { Config } from '@backstage/config'; import { MultiTenantServiceLocator } from '../service-locator/MultiTenantServiceLocator'; import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; import { KubernetesClientProvider } from './KubernetesClientProvider'; -import { - GetKubernetesObjectsForServiceHandler, - handleGetKubernetesObjectsForService, -} from './getKubernetesObjectsForServiceHandler'; import { KubernetesRequestBody, KubernetesServiceLocator, - KubernetesFetcher, ServiceLocatorMethod, ClusterLocatorMethod, ClusterDetails, } from '..'; import { getCombinedClusterDetails } from '../cluster-locator'; +import { KubernetesFanOutHandler } from './KubernetesFanOutHandler'; export interface RouterOptions { logger: Logger; @@ -62,9 +58,7 @@ const getServiceLocator = ( export const makeRouter = ( logger: Logger, - fetcher: KubernetesFetcher, - serviceLocator: KubernetesServiceLocator, - handleGetByEntity: GetKubernetesObjectsForServiceHandler, + kubernetesFanOutHandler: KubernetesFanOutHandler, ): express.Router => { const router = Router(); router.use(express.json()); @@ -73,11 +67,7 @@ export const makeRouter = ( const serviceId = req.params.serviceId; const requestBody: KubernetesRequestBody = req.body; try { - const response = await handleGetByEntity( - serviceId, - fetcher, - serviceLocator, - logger, + const response = await kubernetesFanOutHandler.getKubernetesObjectsByEntity( requestBody, ); res.send(response); @@ -115,10 +105,11 @@ export async function createRouter( const serviceLocator = getServiceLocator(options.config, clusterDetails); - return makeRouter( + const kubernetesFanOutHandler = new KubernetesFanOutHandler( logger, fetcher, serviceLocator, - handleGetKubernetesObjectsForService, ); + + return makeRouter(logger, kubernetesFanOutHandler); } From efbd9c9a9928a68fa18d5bd6fd700cd2b20900ce Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 10 Feb 2021 14:00:51 +0100 Subject: [PATCH 009/270] Add TaskStep component --- .../src/components/TaskPage/TaskPage.tsx | 124 ++++++++++++------ .../src/components/hooks/useEventStream.ts | 31 ++--- 2 files changed, 101 insertions(+), 54 deletions(-) diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index 2f22cb9849..820e65d126 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -15,7 +15,7 @@ */ import { Page, Header, Lifecycle, Content } from '@backstage/core'; -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, memo } from 'react'; import { makeStyles, Theme, @@ -34,7 +34,11 @@ import Check from '@material-ui/icons/Check'; import Cancel from '@material-ui/icons/Cancel'; import Typography from '@material-ui/core/Typography'; import { useParams } from 'react-router'; -import { useTaskEventStream, TaskStream } from '../hooks/useEventStream'; +import { + useTaskEventStream, + TaskStream, + Status, +} from '../hooks/useEventStream'; import LazyLog from 'react-lazylog/build/LazyLog'; import { StepButton, StepIconProps } from '@material-ui/core'; @@ -130,36 +134,29 @@ const useStyles = makeStyles((theme: Theme) => }), ); -export const TaskStepper = ({ taskStream }: { taskStream: TaskStream }) => { +type Steps = { + log: string[]; + id: string; + name: string; + status: Status; +}; + +export const TaskStepper = ({ steps }: { steps: Steps[] }) => { const classes = useStyles(); const [activeStep, setActiveStep] = useState(0); const [expandAll, setExpandAll] = useState(false); - const steps = taskStream?.task?.spec.steps ?? []; - - const handleNext = () => { - setActiveStep(prevActiveStep => prevActiveStep + 1); - }; - - const handleBack = () => { - setActiveStep(prevActiveStep => prevActiveStep - 1); - }; - const handleStep = (step: number) => { setExpandAll(false); setActiveStep(step); }; - const handleReset = () => { - setActiveStep(0); - }; - useEffect(() => { - const activeIndex = Object.values(taskStream?.steps ?? {}).findIndex(step => + const activeIndex = steps.findIndex(step => ['failed', 'processing'].includes(step.status), ); - setActiveStep(activeIndex); - }, [taskStream]); + setActiveStep(2); + }, [steps]); return (
@@ -170,12 +167,24 @@ export const TaskStepper = ({ taskStream }: { taskStream: TaskStream }) => { {steps.map((step, index) => { - const isCompleted = - taskStream.steps?.[step.id].status === 'completed'; - const isFailed = taskStream.steps?.[step.id].status === 'failed'; + const isCompleted = step.status === 'completed'; + const isFailed = step.status === 'failed'; + // return ( + // + // ); + return ( - - handleStep(index)}> + + {}}> {
@@ -200,21 +205,62 @@ export const TaskStepper = ({ taskStream }: { taskStream: TaskStream }) => { ); })}
- {activeStep === steps.length && ( - - All steps completed - you're finished - - - )}
); }; +type TaskStepOptions = { + name: string; + isCompleted: boolean; + isFailed: boolean; + expanded: boolean; + handleStep: () => void; + index: number; + log: string; +}; + +export const TaskStep = memo( + ({ + log, + name, + isCompleted, + isFailed, + handleStep, + index, + expanded, + }: TaskStepOptions) => { + const onClick = React.useCallback(() => handleStep(index), [ + handleStep, + index, + ]); + return ( + + + + {name} + + + + +
+ +
+
+
+ ); + }, +); export const TaskPage = () => { const { taskId } = useParams(); const taskStream = useTaskEventStream(taskId); + const steps = + taskStream.task?.spec.steps.map(step => ({ + ...step, + ...taskStream?.steps?.[step.id], + })) ?? []; return ( @@ -228,7 +274,7 @@ export const TaskPage = () => { subtitle={`Activity for task: ${taskId}`} /> - + ); diff --git a/plugins/scaffolder/src/components/hooks/useEventStream.ts b/plugins/scaffolder/src/components/hooks/useEventStream.ts index 2cbf8b4f83..20338463c3 100644 --- a/plugins/scaffolder/src/components/hooks/useEventStream.ts +++ b/plugins/scaffolder/src/components/hooks/useEventStream.ts @@ -14,16 +14,16 @@ * limitations under the License. */ import { useImmerReducer } from 'use-immer'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { scaffolderApiRef } from '../../api'; import { ScaffolderTask } from '../../types'; +import { useDebounce } from 'react-use'; import { Subscription, useApi } from '@backstage/core'; -type Status = 'open' | 'processing' | 'failed' | 'completed'; +export type Status = 'open' | 'processing' | 'failed' | 'completed'; type Step = { id: string; status: Status; - log: string[]; endedAt?: string; startedAt?: string; }; @@ -34,7 +34,7 @@ export type TaskStream = { log: string[]; completed: boolean; task?: ScaffolderTask; - steps?: { [stepId in string]: Step }; + steps: { [stepId in string]: Step }; }; type ReducerAction = @@ -56,7 +56,7 @@ function reducer(draft: TaskStream, action: ReducerAction) { switch (action.type) { case 'INIT': { draft.steps = action.data.spec.steps.reduce((current, next) => { - current[next.id] = { log: [], status: 'open', id: next.id }; + current[next.id] = { status: 'open', id: next.id }; return current; }, {} as { [stepId in string]: Step }); draft.loading = false; @@ -72,12 +72,7 @@ function reducer(draft: TaskStream, action: ReducerAction) { const currentStep = draft.steps?.[stepId]; const logLine = `${action.data.createdAt} ${action.data.body.message}`; - if (!currentStep) { - draft.log.push(logLine); - return; - } - - currentStep.log.push(logLine); + draft.log.push(logLine); if ( action.data.body.status && @@ -89,9 +84,7 @@ function reducer(draft: TaskStream, action: ReducerAction) { currentStep.startedAt = action.data.createdAt; } - if ( - ['zcancelled', 'failed', 'completed'].includes(currentStep.status) - ) { + if (['cancelled', 'failed', 'completed'].includes(currentStep.status)) { currentStep.endedAt = action.data.createdAt; } } @@ -124,7 +117,14 @@ export const useTaskEventStream = (taskId: string): TaskStream => { log: [], steps: {} as { [stepId in string]: Step }, }); - + // const [debouncedState, setDebouncedState] = useState(state); + // useDebounce( + // () => { + // setDebouncedState(state); + // }, + // 1000, + // [state], + // ); useEffect(() => { let didCancel = false; let subscription: Subscription | undefined; @@ -167,4 +167,5 @@ export const useTaskEventStream = (taskId: string): TaskStream => { }, [scaffolderApi, dispatch, taskId]); return state; + // return debouncedState; }; From 2876700adb883d876183226cc6ca833f64434860 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 10 Feb 2021 15:24:00 +0100 Subject: [PATCH 010/270] Unifify logs --- plugins/scaffolder/src/api.ts | 10 +- .../src/components/TaskPage/TaskPage.tsx | 185 ++++-------------- .../components/TemplatePage/TemplatePage.tsx | 3 - .../src/components/hooks/useEventStream.ts | 106 +++++----- plugins/scaffolder/src/types.ts | 1 + 5 files changed, 104 insertions(+), 201 deletions(-) diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index c51bd4a8be..afb84fe173 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -17,16 +17,20 @@ import { JsonObject } from '@backstage/config'; import { createApiRef, DiscoveryApi, Observable } from '@backstage/core'; import ObservableImpl from 'zen-observable'; -import { ScaffolderTask } from './types'; +import { ScaffolderTask, Status } from './types'; export const scaffolderApiRef = createApiRef({ id: 'plugin.scaffolder.service', description: 'Used to make requests towards the scaffolder backend', }); -type LogEvent = { +export type LogEvent = { type: 'log' | 'completion'; - body: JsonObject; + body: { + message: string; + stepId?: string; + status?: Status; + }; createdAt: string; id: string; taskId: string; diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index 820e65d126..d6065fbd26 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -27,6 +27,7 @@ import Step from '@material-ui/core/Step'; import StepLabel from '@material-ui/core/StepLabel'; import StepContent from '@material-ui/core/StepContent'; import StepConnector from '@material-ui/core/StepConnector'; +import Grid from '@material-ui/core/Grid'; import Button from '@material-ui/core/Button'; import Paper from '@material-ui/core/Paper'; import clsx from 'clsx'; @@ -42,80 +43,6 @@ import { import LazyLog from 'react-lazylog/build/LazyLog'; import { StepButton, StepIconProps } from '@material-ui/core'; -const QontoConnector = withStyles({ - active: { - '& $line': { - borderColor: '#784af4', - }, - }, - completed: { - '& $line': { - borderColor: '#784af4', - }, - }, - line: { - borderColor: '#eaeaf0', - borderTopWidth: 3, - borderRadius: 1, - }, -})(StepConnector); - -const useQontoStepIconStyles = makeStyles({ - root: { - color: '#eaeaf0', - display: 'flex', - height: 22, - alignItems: 'center', - }, - active: { - color: 'grey', - }, - error: { - color: 'red', - }, - circle: { - width: 8, - height: 8, - borderRadius: '50%', - backgroundColor: 'currentColor', - }, - completed: { - color: 'green', - zIndex: 1, - fontSize: 18, - }, -}); - -function QontoStepIcon(props: StepIconProps) { - const classes = useQontoStepIconStyles(); - const { active, completed, error } = props; - - const getComponent = () => { - if (error) { - return ; - } - - if (completed) { - return ; - } - - if (active) { - return
; - } - return undefined; - }; - return ( -
- {getComponent()} -
- ); -} - const useStyles = makeStyles((theme: Theme) => createStyles({ root: { @@ -141,13 +68,11 @@ type Steps = { status: Status; }; -export const TaskStepper = ({ steps }: { steps: Steps[] }) => { +export const TaskStatusStepper = memo(({ steps }: { steps: Steps[] }) => { const classes = useStyles(); const [activeStep, setActiveStep] = useState(0); - const [expandAll, setExpandAll] = useState(false); const handleStep = (step: number) => { - setExpandAll(false); setActiveStep(step); }; @@ -155,104 +80,50 @@ export const TaskStepper = ({ steps }: { steps: Steps[] }) => { const activeIndex = steps.findIndex(step => ['failed', 'processing'].includes(step.status), ); - setActiveStep(2); + setActiveStep(activeIndex); }, [steps]); return (
- - - {steps.map((step, index) => { const isCompleted = step.status === 'completed'; const isFailed = step.status === 'failed'; - // return ( - // - // ); - return ( {}}> {step.name} - - -
- -
-
); })}
); +}); + +const TaskLogger = memo(({ log }: { log: string }) => { + console.log('rendering my log'); + return ( +
+ +
+ ); +}); + +const TaskActionsBar = () => { + return ( + <> + + + + ); }; -type TaskStepOptions = { - name: string; - isCompleted: boolean; - isFailed: boolean; - expanded: boolean; - handleStep: () => void; - index: number; - log: string; -}; - -export const TaskStep = memo( - ({ - log, - name, - isCompleted, - isFailed, - handleStep, - index, - expanded, - }: TaskStepOptions) => { - const onClick = React.useCallback(() => handleStep(index), [ - handleStep, - index, - ]); - return ( - - - - {name} - - - - -
- -
-
-
- ); - }, -); export const TaskPage = () => { const { taskId } = useParams(); const taskStream = useTaskEventStream(taskId); @@ -274,7 +145,19 @@ export const TaskPage = () => { subtitle={`Activity for task: ${taskId}`} /> - + + + + + + + + + ); diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 7239781b5c..85f3d955ac 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -89,12 +89,9 @@ export const TemplatePage = () => { [setFormState, formState], ); - const [taskId, setTaskId] = useState(undefined); - const handleCreate = async () => { try { const id = await scaffolderApi.scaffold(templateName, formState); - setTaskId(id); navigate(tasks({ taskId: id })); } catch (e) { errorApi.post(e); diff --git a/plugins/scaffolder/src/components/hooks/useEventStream.ts b/plugins/scaffolder/src/components/hooks/useEventStream.ts index 20338463c3..f7b366be56 100644 --- a/plugins/scaffolder/src/components/hooks/useEventStream.ts +++ b/plugins/scaffolder/src/components/hooks/useEventStream.ts @@ -14,13 +14,11 @@ * limitations under the License. */ import { useImmerReducer } from 'use-immer'; -import { useEffect, useState } from 'react'; -import { scaffolderApiRef } from '../../api'; -import { ScaffolderTask } from '../../types'; -import { useDebounce } from 'react-use'; +import { useEffect } from 'react'; +import { scaffolderApiRef, LogEvent } from '../../api'; +import { ScaffolderTask, Status } from '../../types'; import { Subscription, useApi } from '@backstage/core'; -export type Status = 'open' | 'processing' | 'failed' | 'completed'; type Step = { id: string; status: Status; @@ -37,18 +35,14 @@ export type TaskStream = { steps: { [stepId in string]: Step }; }; +type ReducerLogEntry = { + createdAt: string; + body: { stepId?: string; status?: Status; message: string }; +}; + type ReducerAction = - | { - type: 'INIT'; - data: ScaffolderTask; - } - | { - type: 'LOG'; - data: { - createdAt: string; - body: { stepId?: string; status?: Status; message: string }; - }; - } + | { type: 'INIT'; data: ScaffolderTask } + | { type: 'LOGS'; data: ReducerLogEntry[] } | { type: 'COMPLETED' } | { type: 'ERROR'; data: Error }; @@ -67,28 +61,35 @@ function reducer(draft: TaskStream, action: ReducerAction) { return; } - case 'LOG': { - const stepId = action.data.body.stepId ?? 'global'; - const currentStep = draft.steps?.[stepId]; - const logLine = `${action.data.createdAt} ${action.data.body.message}`; + case 'LOGS': { + const entries = action.data; + const logLines = []; + for (const entry of entries) { + const logLine = `${entry.createdAt} ${entry.body.message}`; + logLines.push(logLine); - draft.log.push(logLine); - - if ( - action.data.body.status && - action.data.body.status !== currentStep.status - ) { - currentStep.status = action.data.body.status; - - if (currentStep.status === 'processing') { - currentStep.startedAt = action.data.createdAt; + if (!entry.body.stepId || !draft.steps?.[entry.body.stepId]) { + continue; } - if (['cancelled', 'failed', 'completed'].includes(currentStep.status)) { - currentStep.endedAt = action.data.createdAt; + const currentStep = draft.steps?.[entry.body.stepId]; + + if (entry.body.status && entry.body.status !== currentStep.status) { + currentStep.status = entry.body.status; + + if (currentStep.status === 'processing') { + currentStep.startedAt = entry.createdAt; + } + + if ( + ['cancelled', 'failed', 'completed'].includes(currentStep.status) + ) { + currentStep.endedAt = entry.createdAt; + } } } + draft.log.push(...logLines); return; } @@ -117,17 +118,11 @@ export const useTaskEventStream = (taskId: string): TaskStream => { log: [], steps: {} as { [stepId in string]: Step }, }); - // const [debouncedState, setDebouncedState] = useState(state); - // useDebounce( - // () => { - // setDebouncedState(state); - // }, - // 1000, - // [state], - // ); + useEffect(() => { let didCancel = false; let subscription: Subscription | undefined; + let logPusher: NodeJS.Timeout | undefined; scaffolderApi.getTask(taskId).then( task => { @@ -136,19 +131,40 @@ export const useTaskEventStream = (taskId: string): TaskStream => { } dispatch({ type: 'INIT', data: task }); const observable = scaffolderApi.streamLogs({ taskId }); + + const collectedLogEvents = new Array(); + + function emitLogs() { + if (collectedLogEvents.length) { + const logs = collectedLogEvents.splice( + 0, + collectedLogEvents.length, + ); + dispatch({ type: 'LOGS', data: logs }); + } + } + + logPusher = setInterval(emitLogs, 500); + subscription = observable.subscribe({ next: event => { switch (event.type) { case 'log': - return dispatch({ type: 'LOG', data: event }); + return collectedLogEvents.push(event); default: throw new Error( `Unhandled event type ${event.type} in observer`, ); } }, - error: error => dispatch({ type: 'ERROR', data: error }), - complete: () => dispatch({ type: 'COMPLETED' }), + error: error => { + emitLogs(); + dispatch({ type: 'ERROR', data: error }); + }, + complete: () => { + emitLogs(); + dispatch({ type: 'COMPLETED' }); + }, }); }, error => { @@ -163,9 +179,11 @@ export const useTaskEventStream = (taskId: string): TaskStream => { if (subscription) { subscription.unsubscribe(); } + if (logPusher) { + clearInterval(logPusher); + } }; }, [scaffolderApi, dispatch, taskId]); return state; - // return debouncedState; }; diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index fe1aec5286..0499f4aca2 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -15,6 +15,7 @@ */ import { JsonValue } from '@backstage/config'; +export type Status = 'open' | 'processing' | 'failed' | 'completed'; export type JobStatus = 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED'; export type Job = { id: string; From 2b15455d2b1a3f58b265257975cd1f065613a3bb Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 10 Feb 2021 16:16:56 +0100 Subject: [PATCH 011/270] Separate logs --- .../src/components/TaskPage/TaskPage.tsx | 129 +++++++++++------- .../src/components/hooks/useEventStream.ts | 21 ++- 2 files changed, 96 insertions(+), 54 deletions(-) diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index d6065fbd26..d9a6f86703 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -15,7 +15,7 @@ */ import { Page, Header, Lifecycle, Content } from '@backstage/core'; -import React, { useState, useEffect, memo } from 'react'; +import React, { useState, useEffect, memo, useMemo } from 'react'; import { makeStyles, Theme, @@ -62,52 +62,52 @@ const useStyles = makeStyles((theme: Theme) => ); type Steps = { - log: string[]; id: string; name: string; status: Status; }; -export const TaskStatusStepper = memo(({ steps }: { steps: Steps[] }) => { - const classes = useStyles(); - const [activeStep, setActiveStep] = useState(0); +export const TaskStatusStepper = memo( + ({ + steps, + currentStepId, + onUserStepChange, + }: { + steps: Steps[]; + currentStepId: string | undefined; + onUserStepChange: (id: string) => void; + }) => { + const classes = useStyles(); - const handleStep = (step: number) => { - setActiveStep(step); - }; - - useEffect(() => { - const activeIndex = steps.findIndex(step => - ['failed', 'processing'].includes(step.status), + return ( +
+ s.id === currentStepId)} + orientation="vertical" + nonLinear + > + {steps.map((step, index) => { + const isCompleted = step.status === 'completed'; + const isFailed = step.status === 'failed'; + return ( + + onUserStepChange(step.id)}> + + {step.name} + + + + ); + })} + +
); - setActiveStep(activeIndex); - }, [steps]); - - return ( -
- - {steps.map((step, index) => { - const isCompleted = step.status === 'completed'; - const isFailed = step.status === 'failed'; - return ( - - {}}> - - {step.name} - - - - ); - })} - -
- ); -}); + }, +); const TaskLogger = memo(({ log }: { log: string }) => { - console.log('rendering my log'); return (
@@ -125,13 +125,43 @@ const TaskActionsBar = () => { }; export const TaskPage = () => { + const [userSelectedStepId, setUserSelectedStepId] = useState< + string | undefined + >(undefined); + const [lastActiveStepId, setLastActiveStepId] = useState( + undefined, + ); const { taskId } = useParams(); const taskStream = useTaskEventStream(taskId); - const steps = - taskStream.task?.spec.steps.map(step => ({ - ...step, - ...taskStream?.steps?.[step.id], - })) ?? []; + + const steps = useMemo( + () => + taskStream.task?.spec.steps.map(step => ({ + ...step, + ...taskStream?.steps?.[step.id], + })) ?? [], + [taskStream], + ); + useEffect(() => { + const activeStep = steps.find(step => + ['failed', 'processing'].includes(step.status), + ); + setLastActiveStepId(activeStep?.id); + }, [steps]); + + const currentStepId = userSelectedStepId ?? lastActiveStepId; + + const logAsString = useMemo(() => { + if (!currentStepId) { + return 'Loading...'; + } + const log = taskStream.stepLogs[currentStepId]; + + if (!log?.length) { + return 'Waiting for logs...'; + } + return log.join('\n'); + }, [taskStream.stepLogs, currentStepId]); return ( @@ -145,17 +175,16 @@ export const TaskPage = () => { subtitle={`Activity for task: ${taskId}`} /> - - + - + diff --git a/plugins/scaffolder/src/components/hooks/useEventStream.ts b/plugins/scaffolder/src/components/hooks/useEventStream.ts index f7b366be56..8bd8bb4d6c 100644 --- a/plugins/scaffolder/src/components/hooks/useEventStream.ts +++ b/plugins/scaffolder/src/components/hooks/useEventStream.ts @@ -29,7 +29,7 @@ type Step = { export type TaskStream = { loading: boolean; error?: Error; - log: string[]; + stepLogs: { [stepId in string]: string[] }; completed: boolean; task?: ScaffolderTask; steps: { [stepId in string]: Step }; @@ -53,17 +53,21 @@ function reducer(draft: TaskStream, action: ReducerAction) { current[next.id] = { status: 'open', id: next.id }; return current; }, {} as { [stepId in string]: Step }); + draft.stepLogs = action.data.spec.steps.reduce((current, next) => { + current[next.id] = []; + return current; + }, {} as { [stepId in string]: string[] }); draft.loading = false; draft.error = undefined; draft.completed = false; draft.task = action.data; - draft.log = []; return; } case 'LOGS': { const entries = action.data; const logLines = []; + for (const entry of entries) { const logLine = `${entry.createdAt} ${entry.body.message}`; logLines.push(logLine); @@ -72,6 +76,7 @@ function reducer(draft: TaskStream, action: ReducerAction) { continue; } + const currentStepLog = draft.stepLogs?.[entry.body.stepId]; const currentStep = draft.steps?.[entry.body.stepId]; if (entry.body.status && entry.body.status !== currentStep.status) { @@ -87,9 +92,10 @@ function reducer(draft: TaskStream, action: ReducerAction) { currentStep.endedAt = entry.createdAt; } } + + currentStepLog?.push(logLine); } - draft.log.push(...logLines); return; } @@ -115,7 +121,7 @@ export const useTaskEventStream = (taskId: string): TaskStream => { const [state, dispatch] = useImmerReducer(reducer, { loading: true, completed: false, - log: [], + stepLogs: {} as { [stepId in string]: string[] }, steps: {} as { [stepId in string]: Step }, }); @@ -130,6 +136,13 @@ export const useTaskEventStream = (taskId: string): TaskStream => { return; } dispatch({ type: 'INIT', data: task }); + + // TODO(blam): Use a normal fetch to fetch the current log for the event stream + // and use that for an INIT_EVENTs dispatch event, and then + // use the last event ID to subscribe using after option to + // stream logs. Without this, if you have a lot of logs, it can look like the + // task is being rebuilt on load as it progresses through the steps at a slower + // rate whilst it builds the status from the event logs const observable = scaffolderApi.streamLogs({ taskId }); const collectedLogEvents = new Array(); From 915be577b04cd425da2aee46c29f3e301d7d8695 Mon Sep 17 00:00:00 2001 From: Oscar Hernandez Date: Fri, 5 Feb 2021 16:44:38 -0600 Subject: [PATCH 012/270] Copy Catalog page Filter to Scaffolder page --- plugins/scaffolder/package.json | 1 + .../CatalogFilter/AllServicesCount.tsx | 33 +++ .../CatalogFilter/CatalogFilter.test.tsx | 264 ++++++++++++++++++ .../CatalogFilter/CatalogFilter.tsx | 211 ++++++++++++++ .../src/components/CatalogFilter/index.ts | 17 ++ .../ResultsFilter/ResultsFilter.test.tsx | 105 +++++++ .../ResultsFilter/ResultsFilter.tsx | 121 ++++++++ .../ScaffolderPage/ScaffolderPage.tsx | 182 +++++++++--- .../scaffolder/src/components/useOwnUser.ts | 41 +++ .../src/filter/EntityFilterGroupsProvider.tsx | 263 +++++++++++++++++ plugins/scaffolder/src/filter/context.ts | 44 +++ plugins/scaffolder/src/filter/index.ts | 28 ++ plugins/scaffolder/src/filter/types.ts | 53 ++++ .../src/filter/useEntityFilterGroup.test.tsx | 120 ++++++++ .../src/filter/useEntityFilterGroup.ts | 69 +++++ .../src/filter/useFilteredEntities.ts | 37 +++ .../src/hooks/useStarredEntities.test.tsx | 121 ++++++++ .../src/hooks/useStarredEntities.ts | 75 +++++ 18 files changed, 1741 insertions(+), 44 deletions(-) create mode 100644 plugins/scaffolder/src/components/CatalogFilter/AllServicesCount.tsx create mode 100644 plugins/scaffolder/src/components/CatalogFilter/CatalogFilter.test.tsx create mode 100644 plugins/scaffolder/src/components/CatalogFilter/CatalogFilter.tsx create mode 100644 plugins/scaffolder/src/components/CatalogFilter/index.ts create mode 100644 plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.test.tsx create mode 100644 plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.tsx create mode 100644 plugins/scaffolder/src/components/useOwnUser.ts create mode 100644 plugins/scaffolder/src/filter/EntityFilterGroupsProvider.tsx create mode 100644 plugins/scaffolder/src/filter/context.ts create mode 100644 plugins/scaffolder/src/filter/index.ts create mode 100644 plugins/scaffolder/src/filter/types.ts create mode 100644 plugins/scaffolder/src/filter/useEntityFilterGroup.test.tsx create mode 100644 plugins/scaffolder/src/filter/useEntityFilterGroup.ts create mode 100644 plugins/scaffolder/src/filter/useFilteredEntities.ts create mode 100644 plugins/scaffolder/src/hooks/useStarredEntities.test.tsx create mode 100644 plugins/scaffolder/src/hooks/useStarredEntities.ts diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 56beb7b10b..5112aeccf7 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -58,6 +58,7 @@ "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", + "@testing-library/react-hooks": "^3.3.0", "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "cross-fetch": "^3.0.6", diff --git a/plugins/scaffolder/src/components/CatalogFilter/AllServicesCount.tsx b/plugins/scaffolder/src/components/CatalogFilter/AllServicesCount.tsx new file mode 100644 index 0000000000..efacfa4320 --- /dev/null +++ b/plugins/scaffolder/src/components/CatalogFilter/AllServicesCount.tsx @@ -0,0 +1,33 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { useApi } from '@backstage/core'; +import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import { CircularProgress, useTheme } from '@material-ui/core'; +import React from 'react'; +import { useAsync } from 'react-use'; + +export const AllServicesCount = () => { + const theme = useTheme(); + const catalogApi = useApi(catalogApiRef); + const { value, loading } = useAsync(() => catalogApi.getEntities()); + + if (loading) { + return ; + } + + return {value ?? length ?? '-'}; +}; diff --git a/plugins/scaffolder/src/components/CatalogFilter/CatalogFilter.test.tsx b/plugins/scaffolder/src/components/CatalogFilter/CatalogFilter.test.tsx new file mode 100644 index 0000000000..0cc2108985 --- /dev/null +++ b/plugins/scaffolder/src/components/CatalogFilter/CatalogFilter.test.tsx @@ -0,0 +1,264 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { CatalogApi } from '@backstage/catalog-client'; +import { Entity } from '@backstage/catalog-model'; +import { + ApiProvider, + ApiRegistry, + IdentityApi, + identityApiRef, + storageApiRef, +} from '@backstage/core'; +import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import { MockStorageApi, wrapInTestApp } from '@backstage/test-utils'; +import { fireEvent, render, waitFor } from '@testing-library/react'; +import React from 'react'; +import { EntityFilterGroupsProvider } from '../../filter'; +import { ButtonGroup, CatalogFilter } from './CatalogFilter'; + +describe('Catalog Filter', () => { + const catalogApi: Partial = { + getEntities: () => + Promise.resolve({ + items: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'Entity1', + }, + spec: { + owner: 'tools@example.com', + type: 'service', + }, + }, + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'Entity2', + }, + spec: { + owner: 'not-tools@example.com', + type: 'service', + }, + }, + ] as Entity[], + }), + }; + + const identityApi: Partial = { + getUserId: () => 'tools@example.com', + }; + + const renderWrapped = (children: React.ReactNode) => + render( + wrapInTestApp( + + {children}, + , + ), + ); + + it('should render the different groups', async () => { + const mockGroups: ButtonGroup[] = [ + { name: 'Test Group 1', items: [] }, + { name: 'Test Group 2', items: [] }, + ]; + const { findByText } = renderWrapped( + , + ); + for (const group of mockGroups) { + expect(await findByText(group.name)).toBeInTheDocument(); + } + }); + + it('should render the different items and their names', async () => { + const mockGroups: ButtonGroup[] = [ + { + name: 'Test Group 1', + items: [ + { + id: 'all', + label: 'First Label', + filterFn: () => true, + }, + { + id: 'starred', + label: 'Second Label', + filterFn: () => false, + }, + ], + }, + ]; + + const { findByText } = renderWrapped( + , + ); + + for (const item of mockGroups[0].items) { + expect(await findByText(item.label)).toBeInTheDocument(); + } + }); + + it('selects the first item if no desired initial one is set', async () => { + const mockGroups: ButtonGroup[] = [ + { + name: 'Test Group 1', + items: [ + { + id: 'all', + label: 'First Label', + filterFn: () => true, + }, + { + id: 'starred', + label: 'Second Label', + filterFn: () => false, + }, + ], + }, + ]; + + const onChange = jest.fn(); + + renderWrapped( + , + ); + + await waitFor(() => { + expect(onChange).toHaveBeenLastCalledWith({ + id: 'all', + label: 'First Label', + }); + }); + }); + + it('selects the initial item', async () => { + const mockGroups: ButtonGroup[] = [ + { + name: 'Test Group 1', + items: [ + { + id: 'all', + label: 'First Label', + filterFn: () => true, + }, + { + id: 'starred', + label: 'Second Label', + filterFn: () => false, + }, + ], + }, + ]; + + const onChange = jest.fn(); + + renderWrapped( + , + ); + + await waitFor(() => { + expect(onChange).toHaveBeenLastCalledWith({ + id: 'starred', + label: 'Second Label', + }); + }); + }); + + it('can change the selected item', async () => { + const mockGroups: ButtonGroup[] = [ + { + name: 'Test Group 1', + items: [ + { + id: 'all', + label: 'First Label', + filterFn: () => true, + }, + { + id: 'starred', + label: 'Second Label', + filterFn: () => false, + }, + ], + }, + ]; + + const onChange = jest.fn(); + + const { findByText } = renderWrapped( + , + ); + + await waitFor(() => { + expect(onChange).toHaveBeenLastCalledWith({ + id: 'all', + label: 'First Label', + }); + }); + + fireEvent.click(await findByText('Second Label')); + + await waitFor(() => { + expect(onChange).toHaveBeenLastCalledWith({ + id: 'starred', + label: 'Second Label', + }); + }); + }); + + it('displays match counts properly', async () => { + const mockGroups: ButtonGroup[] = [ + { + name: 'Test Group 1', + items: [ + { + id: 'owned', + label: 'First Label', + filterFn: entity => entity.spec?.owner === 'tools@example.com', + }, + ], + }, + ]; + + const { findByText } = renderWrapped( + , + ); + + expect(await findByText('1')).toBeInTheDocument(); + }); +}); diff --git a/plugins/scaffolder/src/components/CatalogFilter/CatalogFilter.tsx b/plugins/scaffolder/src/components/CatalogFilter/CatalogFilter.tsx new file mode 100644 index 0000000000..90a1e15ad0 --- /dev/null +++ b/plugins/scaffolder/src/components/CatalogFilter/CatalogFilter.tsx @@ -0,0 +1,211 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 '@backstage/catalog-model'; +import { IconComponent } from '@backstage/core'; +import { + Card, + List, + ListItemIcon, + ListItemSecondaryAction, + ListItemText, + makeStyles, + MenuItem, + Theme, + Typography, +} from '@material-ui/core'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { FilterGroup, useEntityFilterGroup } from '../../filter'; + +export type ButtonGroup = { + name: string; + items: { + id: string; + label: string; + icon?: IconComponent; + filterFn: (entity: Entity) => boolean; + }[]; +}; + +const useStyles = makeStyles(theme => ({ + root: { + backgroundColor: 'rgba(0, 0, 0, .11)', + boxShadow: 'none', + }, + title: { + margin: theme.spacing(1, 0, 0, 1), + textTransform: 'uppercase', + fontSize: 12, + fontWeight: 'bold', + }, + listIcon: { + minWidth: 30, + color: theme.palette.text.primary, + }, + menuItem: { + minHeight: theme.spacing(6), + }, + groupWrapper: { + margin: theme.spacing(1, 1, 2, 1), + }, + menuTitle: { + fontWeight: 500, + }, +})); + +type OnChangeCallback = (item: { id: string; label: string }) => void; + +type Props = { + buttonGroups: ButtonGroup[]; + initiallySelected: string; + onChange?: OnChangeCallback; +}; + +/** + * The main filter group in the sidebar, toggling owned/starred/all. + */ +export const CatalogFilter = ({ + buttonGroups, + onChange, + initiallySelected, +}: Props) => { + const classes = useStyles(); + const { currentFilter, setCurrentFilter, getFilterCount } = useFilter( + buttonGroups, + initiallySelected, + ); + + const onChangeRef = useRef(); + useEffect(() => { + onChangeRef.current = onChange; + }, [onChange]); + + const setCurrent = useCallback( + (item: { id: string; label: string }) => { + setCurrentFilter(item.id); + onChangeRef.current?.({ id: item.id, label: item.label }); + }, + [setCurrentFilter], + ); + + // Make one initial onChange to inform the surroundings about the selected + // item + useEffect(() => { + const items = buttonGroups.flatMap(g => g.items); + const item = items.find(i => i.id === initiallySelected) || items[0]; + if (item) { + onChangeRef.current?.({ id: item.id, label: item.label }); + } + // intentionally only happens on startup + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + {buttonGroups.map(group => ( + + + {group.name} + + + + {group.items.map(item => ( + setCurrent(item)} + selected={item.id === currentFilter} + className={classes.menuItem} + > + {item.icon && ( + + + + )} + + + {item.label} + + + + {getFilterCount(item.id) ?? '-'} + + + ))} + + + + ))} + + ); +}; + +function useFilter( + buttonGroups: ButtonGroup[], + initiallySelected: string, +): { + currentFilter: string; + setCurrentFilter: (filterId: string) => void; + getFilterCount: (filterId: string) => number | undefined; +} { + const [currentFilter, setCurrentFilter] = useState(initiallySelected); + + const filterGroup = useMemo( + () => ({ + filters: Object.fromEntries( + buttonGroups.flatMap(g => g.items).map(i => [i.id, i.filterFn]), + ), + }), + [buttonGroups], + ); + + const { setSelectedFilters, state } = useEntityFilterGroup( + 'primary-sidebar', + filterGroup, + [initiallySelected], + ); + + const setCurrent = useCallback( + (filterId: string) => { + setCurrentFilter(filterId); + setSelectedFilters([filterId]); + }, + [setCurrentFilter, setSelectedFilters], + ); + + const getFilterCount = useCallback( + (filterId: string) => { + if (state.type !== 'ready') { + return undefined; + } + return state.state.filters[filterId].matchCount; + }, + [state], + ); + + return { + currentFilter, + setCurrentFilter: setCurrent, + getFilterCount, + }; +} diff --git a/plugins/scaffolder/src/components/CatalogFilter/index.ts b/plugins/scaffolder/src/components/CatalogFilter/index.ts new file mode 100644 index 0000000000..5103b16307 --- /dev/null +++ b/plugins/scaffolder/src/components/CatalogFilter/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { CatalogFilter } from './CatalogFilter'; diff --git a/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.test.tsx b/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.test.tsx new file mode 100644 index 0000000000..9be1c995e8 --- /dev/null +++ b/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.test.tsx @@ -0,0 +1,105 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { CatalogApi } from '@backstage/catalog-client'; +import { Entity } from '@backstage/catalog-model'; +import { + ApiProvider, + ApiRegistry, + IdentityApi, + identityApiRef, + storageApiRef, +} from '@backstage/core'; +import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import { MockStorageApi, wrapInTestApp } from '@backstage/test-utils'; +import { render } from '@testing-library/react'; +import React from 'react'; +import { EntityFilterGroupsProvider } from '../../filter'; +import { ResultsFilter } from './ResultsFilter'; + +describe('Results Filter', () => { + const catalogApi: Partial = { + getEntities: () => + Promise.resolve({ + items: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'Entity1', + tags: ['java'], + }, + spec: { + owner: 'tools@example.com', + type: 'service', + }, + }, + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'Entity2', + }, + spec: { + owner: 'not-tools@example.com', + type: 'service', + }, + }, + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'Entity3', + tags: ['java', 'test'], + }, + spec: { + owner: 'tools@example.com', + type: 'service', + }, + }, + ] as Entity[], + }), + }; + + const identityApi: Partial = { + getUserId: () => 'tools@example.com', + }; + + const renderWrapped = (children: React.ReactNode) => + render( + wrapInTestApp( + + {children}, + , + ), + ); + + it('should render all available tags', async () => { + const tags = ['test', 'java']; + const { findByText } = renderWrapped( + , + ); + for (const tag of tags) { + expect(await findByText(tag)).toBeInTheDocument(); + } + }); +}); diff --git a/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.tsx b/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.tsx new file mode 100644 index 0000000000..8c5737b7b6 --- /dev/null +++ b/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.tsx @@ -0,0 +1,121 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { + Button, + Checkbox, + Divider, + List, + ListItem, + ListItemText, + makeStyles, + Theme, + Typography, +} from '@material-ui/core'; +import React, { useCallback, useContext, useState } from 'react'; +import { filterGroupsContext } from '../../filter/context'; + +const useStyles = makeStyles(theme => ({ + filterBox: { + display: 'flex', + margin: theme.spacing(2, 0, 0, 0), + }, + filterBoxTitle: { + margin: theme.spacing(1, 0, 0, 1), + fontWeight: 'bold', + flex: 1, + }, + title: { + margin: theme.spacing(1, 0, 0, 1), + textTransform: 'uppercase', + fontSize: 12, + fontWeight: 'bold', + }, + checkbox: { + padding: theme.spacing(0, 1, 0, 1), + }, +})); + +type Props = { + availableTags: string[]; +}; + +/** + * The additional results filter in the sidebar. + */ +export const ResultsFilter = ({ availableTags }: Props) => { + const classes = useStyles(); + + const [selectedTags, setSelectedTags] = useState([]); + const context = useContext(filterGroupsContext); + if (!context) { + throw new Error(`Must be used inside an EntityFilterGroupsProvider`); + } + const setSelectedTagsFilter = context?.setSelectedTags; + + const updateSelectedTags = useCallback( + (tags: string[]) => { + setSelectedTags(tags); + setSelectedTagsFilter(tags); + }, + [setSelectedTags, setSelectedTagsFilter], + ); + + return ( + <> +
+ + Refine Results + {' '} + +
+ + + Tags + + + {availableTags.map(t => { + const labelId = `checkbox-list-label-${t}`; + return ( + + updateSelectedTags( + selectedTags.includes(t) + ? selectedTags.filter(s => s !== t) + : [...selectedTags, t], + ) + } + > + + + + ); + })} + + + ); +}; diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index 75ee2d07df..3a8701253d 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -14,8 +14,9 @@ * limitations under the License. */ -import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; +import { TemplateEntityV1alpha1, Entity } from '@backstage/catalog-model'; import { + configApiRef, Content, ContentHeader, errorApiRef, @@ -27,45 +28,112 @@ import { useApi, WarningPanel, } from '@backstage/core'; -import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import { Button, Grid, Link, Typography } from '@material-ui/core'; -import React, { useEffect } from 'react'; +import { catalogApiRef, isOwnerOf } from '@backstage/plugin-catalog-react'; +import { Button, Grid, Link, makeStyles, Typography } from '@material-ui/core'; +import React, { useEffect, useMemo, useState } from 'react'; import { Link as RouterLink } from 'react-router-dom'; import useStaleWhileRevalidate from 'swr'; +import { EntityFilterGroupsProvider, useFilteredEntities } from '../../filter'; import { TemplateCard, TemplateCardProps } from '../TemplateCard'; +import { ResultsFilter } from '../ResultsFilter/ResultsFilter'; +import { CatalogFilter } from '../CatalogFilter'; +import { ButtonGroup } from '../CatalogFilter/CatalogFilter'; +import SettingsIcon from '@material-ui/icons/Settings'; +import StarIcon from '@material-ui/icons/Star'; +import { useOwnUser } from '../useOwnUser'; +import { useStarredEntities } from '../../hooks/useStarredEntities'; + +const useStyles = makeStyles(theme => ({ + contentWrapper: { + display: 'grid', + gridTemplateAreas: "'filters' 'grid'", + gridTemplateColumns: '250px 1fr', + gridColumnGap: theme.spacing(2), + }, +})); const getTemplateCardProps = ( - template: TemplateEntityV1alpha1, + template: Entity, ): TemplateCardProps & { key: string } => { return { key: template.metadata.uid!, name: template.metadata.name, title: `${(template.metadata.title || template.metadata.name) ?? ''}`, - type: template.spec.type ?? '', + // TODO: Validate this prop (I changed TemplateEntityV1alpha1 to Entity interface) Remove 'as any' + type: (template as any).spec.type ?? '', description: template.metadata.description ?? '-', tags: (template.metadata?.tags as string[]) ?? [], }; }; -export const ScaffolderPage = () => { +export const ScaffolderPageContents = () => { + const styles = useStyles(); + const { + loading, + error, + reload, + matchingEntities, + availableTags, // TODO: Change tags to Categories + isCatalogEmpty, + } = useFilteredEntities(); + // TODO: use Selected Sidebar Item + const [selectedSidebarItem, setSelectedSidebarItem] = useState(); const catalogApi = useApi(catalogApiRef); const errorApi = useApi(errorApiRef); - - const { data: templates, isValidating, error } = useStaleWhileRevalidate( - 'templates/all', - async () => { - const response = await catalogApi.getEntities({ - filter: { kind: 'Template' }, - }); - return response.items as TemplateEntityV1alpha1[]; - }, - ); - + const { + data: templates /* isValidating*/ /* , error */, + } = useStaleWhileRevalidate('templates/all', async () => { + const response = await catalogApi.getEntities({ + filter: { kind: 'Template' }, + }); + return response.items as TemplateEntityV1alpha1[]; + }); useEffect(() => { if (!error) return; errorApi.post(error); }, [error, errorApi]); + const { value: user } = useOwnUser(); + + const configApi = useApi(configApiRef); + const orgName = configApi.getOptionalString('organization.name') ?? 'Company'; + + const { isStarredEntity } = useStarredEntities(); + + // TODO: ButtonGroup from CatalogFilter? + const filterGroups = useMemo( + () => [ + { + name: 'Personal', // TODO: Do we need owner? + items: [ + { + id: 'owned', + label: 'Owned', + icon: SettingsIcon, + filterFn: entity => user !== undefined && isOwnerOf(user, entity), + }, + { + id: 'starred', + label: 'Starred', + icon: StarIcon, + filterFn: isStarredEntity, + }, + ], + }, + { + name: orgName, + items: [ + { + id: 'all', + label: 'All', + filterFn: () => true, + }, + ], + }, + ], + [isStarredEntity, orgName, user], + ); + return (
{ documentation, ...). - {!templates && isValidating && } - {templates && !templates.length && ( - - Shoot! Looks like you don't have any templates. Check out the - documentation{' '} - - here! - - - )} - {error && ( - - Oops! Something went wrong loading the templates: {error.message} - - )} - - {templates && - templates?.length > 0 && - templates.map(template => { - return ( - - - - ); - })} - + +
+
+ setSelectedSidebarItem(label)} // TODO: setSelecteSidebarItem + initiallySelected="owned" + /> + +
+
+ {!matchingEntities && loading && } + {matchingEntities && !matchingEntities.length && ( + + Shoot! Looks like you don't have any templates. Check out the + documentation{' '} + + here! + + + )} + {error && ( + + Oops! Something went wrong loading the templates:{' '} + {error.message} + + )} + + {matchingEntities && + matchingEntities?.length > 0 && + matchingEntities.map(template => { + return ( + + + + ); + })} + +
+
); }; + +export const ScaffolderPage = () => ( + + + +); diff --git a/plugins/scaffolder/src/components/useOwnUser.ts b/plugins/scaffolder/src/components/useOwnUser.ts new file mode 100644 index 0000000000..29d8a0d11f --- /dev/null +++ b/plugins/scaffolder/src/components/useOwnUser.ts @@ -0,0 +1,41 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { UserEntity } from '@backstage/catalog-model'; +import { identityApiRef, useApi } from '@backstage/core'; +import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import { useAsync } from 'react-use'; +import { AsyncState } from 'react-use/lib/useAsync'; + +/** + * Get the catalog User entity (if any) that matches the logged-in user. + */ +export function useOwnUser(): AsyncState { + const catalogApi = useApi(catalogApiRef); + const identityApi = useApi(identityApiRef); + + // TODO: get the full entity (or at least the full entity name) from the + // identityApi + return useAsync( + () => + catalogApi.getEntityByName({ + kind: 'User', + namespace: 'default', + name: identityApi.getUserId(), + }) as Promise, + [catalogApi, identityApi], + ); +} diff --git a/plugins/scaffolder/src/filter/EntityFilterGroupsProvider.tsx b/plugins/scaffolder/src/filter/EntityFilterGroupsProvider.tsx new file mode 100644 index 0000000000..92b99c74be --- /dev/null +++ b/plugins/scaffolder/src/filter/EntityFilterGroupsProvider.tsx @@ -0,0 +1,263 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 '@backstage/catalog-model'; +import { useApi } from '@backstage/core'; +import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { useAsyncFn } from 'react-use'; +import { filterGroupsContext, FilterGroupsContext } from './context'; +import { + EntityFilterFn, + FilterGroup, + FilterGroupState, + FilterGroupStates, +} from './types'; + +/** + * Implementation of the shared filter groups state. + */ +export const EntityFilterGroupsProvider = ({ + children, +}: { + children?: React.ReactNode; +}) => { + const state = useProvideEntityFilters(); + return ( + + {children} + + ); +}; + +// The hook that implements the actual context building +function useProvideEntityFilters(): FilterGroupsContext { + const catalogApi = useApi(catalogApiRef); + const [{ value: entities, error }, doReload] = useAsyncFn(async () => { + const response = await catalogApi.getEntities({ + filter: { kind: 'Template' }, + }); + return response.items; + }); + + const filterGroups = useRef<{ + [filterGroupId: string]: FilterGroup; + }>({}); + const selectedFilterKeys = useRef<{ + [filterGroupId: string]: Set; + }>({}); + const selectedTags = useRef([]); + const [filterGroupStates, setFilterGroupStates] = useState<{ + [filterGroupId: string]: FilterGroupStates; + }>({}); + const [matchingEntities, setMatchingEntities] = useState([]); + const [availableTags, setAvailableTags] = useState([]); + const [isCatalogEmpty, setCatalogEmpty] = useState(false); + + useEffect(() => { + doReload(); + }, [doReload]); + + const rebuild = useCallback(() => { + setFilterGroupStates( + buildStates( + filterGroups.current, + selectedFilterKeys.current, + selectedTags.current, + entities, + error, + ), + ); + setMatchingEntities( + buildMatchingEntities( + filterGroups.current, + selectedFilterKeys.current, + selectedTags.current, + entities, + ), + ); + setAvailableTags(collectTags(entities)); + setCatalogEmpty(entities !== undefined && entities.length === 0); + }, [entities, error]); + + const register = useCallback( + ( + filterGroupId: string, + filterGroup: FilterGroup, + initialSelectedFilterIds?: string[], + ) => { + filterGroups.current[filterGroupId] = filterGroup; + selectedFilterKeys.current[filterGroupId] = new Set( + initialSelectedFilterIds ?? [], + ); + rebuild(); + }, + [rebuild], + ); + + const unregister = useCallback( + (filterGroupId: string) => { + delete filterGroups.current[filterGroupId]; + delete selectedFilterKeys.current[filterGroupId]; + rebuild(); + }, + [rebuild], + ); + + const setGroupSelectedFilters = useCallback( + (filterGroupId: string, filters: string[]) => { + selectedFilterKeys.current[filterGroupId] = new Set(filters); + rebuild(); + }, + [rebuild], + ); + + const setSelectedTags = useCallback( + (tags: string[]) => { + selectedTags.current = tags; + rebuild(); + }, + [rebuild], + ); + + const reload = useCallback(async () => { + await doReload(); + }, [doReload]); + + return { + register, + unregister, + setGroupSelectedFilters, + setSelectedTags, + reload, + loading: !error && !entities, + error, + filterGroupStates, + matchingEntities, + availableTags, + isCatalogEmpty, + }; +} + +// Given all filter groups and what filters are actually selected, along with +// the loading state for entities, generate the state of each individual filter +function buildStates( + filterGroups: { [filterGroupId: string]: FilterGroup }, + selectedFilterKeys: { [filterGroupId: string]: Set }, + selectedTags: string[], + entities?: Entity[], + error?: Error, +): { [filterGroupId: string]: FilterGroupStates } { + // On error - all entries are an error state + if (error) { + return Object.fromEntries( + Object.keys(filterGroups).map(filterGroupId => [ + filterGroupId, + { type: 'error', error }, + ]), + ); + } + + // On startup - all entries are a loading state + if (!entities) { + return Object.fromEntries( + Object.keys(filterGroups).map(filterGroupId => [ + filterGroupId, + { type: 'loading' }, + ]), + ); + } + + const result: { [filterGroupId: string]: FilterGroupStates } = {}; + for (const [filterGroupId, filterGroup] of Object.entries(filterGroups)) { + const otherMatchingEntities = buildMatchingEntities( + filterGroups, + selectedFilterKeys, + selectedTags, + entities, + filterGroupId, + ); + const groupState: FilterGroupState = { filters: {} }; + for (const [filterId, filterFn] of Object.entries(filterGroup.filters)) { + const isSelected = !!selectedFilterKeys[filterGroupId]?.has(filterId); + const matchCount = otherMatchingEntities.filter(entity => + filterFn(entity), + ).length; + groupState.filters[filterId] = { isSelected, matchCount }; + } + result[filterGroupId] = { type: 'ready', state: groupState }; + } + + return result; +} + +// Given all entites, find all possible tags and provide them in a sorted list. +function collectTags(entities?: Entity[]): string[] { + const tags = new Set(); + (entities || []).forEach(e => { + if (e.metadata.tags) { + e.metadata.tags.forEach(t => tags.add(t)); + } + }); + return Array.from(tags).sort(); +} + +// Given all filter groups and what filters are actually selected, extract all +// entities that match all those filter groups. +function buildMatchingEntities( + filterGroups: { [filterGroupId: string]: FilterGroup }, + selectedFilterKeys: { [filterGroupId: string]: Set }, + selectedTags: string[], + entities?: Entity[], + excludeFilterGroupId?: string, +): Entity[] { + // Build one filter fn per filter group + const allFilters: EntityFilterFn[] = []; + for (const [filterGroupId, filterGroup] of Object.entries(filterGroups)) { + if (excludeFilterGroupId === filterGroupId) { + continue; + } + + // Pick out all of the filter functions in the group that are actually + // selected + const groupFilters: EntityFilterFn[] = []; + for (const [filterId, filterFn] of Object.entries(filterGroup.filters)) { + if (!!selectedFilterKeys[filterGroupId]?.has(filterId)) { + groupFilters.push(filterFn); + } + } + + // Need to match any of the selected filters in the group - if there is + // any at all + if (groupFilters.length) { + allFilters.push(entity => groupFilters.some(fn => fn(entity))); + } + } + + // Filter by tags, if at least one tag is selected. Include all entities + // that have at least one of the selected tags + if (selectedTags.length > 0) { + allFilters.push( + entity => + !!entity.metadata.tags && + entity.metadata.tags.some(t => selectedTags.includes(t)), + ); + } + + // All filter groups that had any checked filters need to match. Note that + // every() always returns true for an empty array. + return entities?.filter(entity => allFilters.every(fn => fn(entity))) ?? []; +} diff --git a/plugins/scaffolder/src/filter/context.ts b/plugins/scaffolder/src/filter/context.ts new file mode 100644 index 0000000000..c025480fa6 --- /dev/null +++ b/plugins/scaffolder/src/filter/context.ts @@ -0,0 +1,44 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 '@backstage/catalog-model'; +import { createContext } from 'react'; +import { FilterGroup, FilterGroupStates } from './types'; + +export type FilterGroupsContext = { + register: ( + filterGroupId: string, + filterGroup: FilterGroup, + initialSelectedFilterIds?: string[], + ) => void; + unregister: (filterGroupId: string) => void; + setGroupSelectedFilters: (filterGroupId: string, filterIds: string[]) => void; + setSelectedTags: (tags: string[]) => void; + reload: () => Promise; + loading: boolean; + error?: Error; + filterGroupStates: { [filterGroupId: string]: FilterGroupStates }; + matchingEntities: Entity[]; + availableTags: string[]; + isCatalogEmpty: boolean; +}; + +/** + * The context that maintains shared state for all visible filter groups. + */ +export const filterGroupsContext = createContext< + FilterGroupsContext | undefined +>(undefined); diff --git a/plugins/scaffolder/src/filter/index.ts b/plugins/scaffolder/src/filter/index.ts new file mode 100644 index 0000000000..da73147ef9 --- /dev/null +++ b/plugins/scaffolder/src/filter/index.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { EntityFilterGroupsProvider } from './EntityFilterGroupsProvider'; +export type { + EntityFilterFn, + FilterGroup, + FilterGroupState, + FilterGroupStates, + FilterGroupStatesError, + FilterGroupStatesLoading, + FilterGroupStatesReady, +} from './types'; +export { useEntityFilterGroup } from './useEntityFilterGroup'; +export { useFilteredEntities } from './useFilteredEntities'; diff --git a/plugins/scaffolder/src/filter/types.ts b/plugins/scaffolder/src/filter/types.ts new file mode 100644 index 0000000000..ed08b131bf --- /dev/null +++ b/plugins/scaffolder/src/filter/types.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 '@backstage/catalog-model'; + +export type EntityFilterFn = (entity: Entity) => boolean; + +export type FilterGroup = { + filters: { + [filterId: string]: EntityFilterFn; + }; +}; + +export type FilterGroupState = { + filters: { + [filterId: string]: { + isSelected: boolean; + matchCount: number; + }; + }; +}; + +export type FilterGroupStatesReady = { + type: 'ready'; + state: FilterGroupState; +}; + +export type FilterGroupStatesError = { + type: 'error'; + error: Error; +}; + +export type FilterGroupStatesLoading = { + type: 'loading'; +}; + +export type FilterGroupStates = + | FilterGroupStatesReady + | FilterGroupStatesError + | FilterGroupStatesLoading; diff --git a/plugins/scaffolder/src/filter/useEntityFilterGroup.test.tsx b/plugins/scaffolder/src/filter/useEntityFilterGroup.test.tsx new file mode 100644 index 0000000000..685fc751d2 --- /dev/null +++ b/plugins/scaffolder/src/filter/useEntityFilterGroup.test.tsx @@ -0,0 +1,120 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { ApiProvider, ApiRegistry, storageApiRef } from '@backstage/core'; +import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import { MockStorageApi } from '@backstage/test-utils'; +import { act, renderHook } from '@testing-library/react-hooks'; +import React from 'react'; +import { EntityFilterGroupsProvider } from './EntityFilterGroupsProvider'; +import { FilterGroup, FilterGroupStatesReady } from './types'; +import { useEntityFilterGroup } from './useEntityFilterGroup'; + +describe('useEntityFilterGroup', () => { + let catalogApi: jest.Mocked; + let wrapper: ({ children }: { children?: React.ReactNode }) => JSX.Element; + + beforeEach(() => { + catalogApi = { + /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + addLocation: jest.fn(_a => new Promise(() => {})), + getEntities: jest.fn(), + getLocationByEntity: jest.fn(), + getLocationById: jest.fn(), + removeEntityByUid: jest.fn(), + getEntityByName: jest.fn(), + }; + const apis = ApiRegistry.with(catalogApiRef, catalogApi).with( + storageApiRef, + MockStorageApi.create(), + ); + wrapper = ({ children }: { children?: React.ReactNode }) => ( + + {children} + + ); + }); + + it('works for an empty set of filters', async () => { + catalogApi.getEntities.mockResolvedValue({ items: [] }); + const group: FilterGroup = { filters: {} }; + const { result, waitFor } = renderHook( + () => useEntityFilterGroup('g1', group), + { wrapper }, + ); + + await waitFor(() => expect(result.current.state.type).toBe('ready')); + }); + + it('works for a single group', async () => { + catalogApi.getEntities.mockResolvedValue({ + items: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { name: 'n' }, + }, + ], + }); + const group: FilterGroup = { + filters: { + f1: e => e.metadata.name === 'n', + f2: e => e.metadata.name !== 'n', + }, + }; + const { result, waitFor } = renderHook( + () => useEntityFilterGroup('g1', group), + { wrapper }, + ); + + await waitFor(() => expect(result.current.state.type).toEqual('ready')); + let state = result.current.state as FilterGroupStatesReady; + expect(state.state.filters.f1).toEqual({ + isSelected: false, + matchCount: 1, + }); + expect(state.state.filters.f2).toEqual({ + isSelected: false, + matchCount: 0, + }); + + act(() => result.current.setSelectedFilters(['f1'])); + + await waitFor(() => expect(result.current.state.type).toEqual('ready')); + state = result.current.state as FilterGroupStatesReady; + expect(state.state.filters.f1).toEqual({ + isSelected: true, + matchCount: 1, + }); + expect(state.state.filters.f2).toEqual({ + isSelected: false, + matchCount: 0, + }); + + act(() => result.current.setSelectedFilters(['f2'])); + + await waitFor(() => expect(result.current.state.type).toEqual('ready')); + state = result.current.state as FilterGroupStatesReady; + expect(state.state.filters.f1).toEqual({ + isSelected: false, + matchCount: 1, + }); + expect(state.state.filters.f2).toEqual({ + isSelected: true, + matchCount: 0, + }); + }); +}); diff --git a/plugins/scaffolder/src/filter/useEntityFilterGroup.ts b/plugins/scaffolder/src/filter/useEntityFilterGroup.ts new file mode 100644 index 0000000000..242238e4f4 --- /dev/null +++ b/plugins/scaffolder/src/filter/useEntityFilterGroup.ts @@ -0,0 +1,69 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { useCallback, useContext, useEffect, useMemo } from 'react'; +import { filterGroupsContext } from './context'; +import { FilterGroup, FilterGroupStates } from './types'; + +export type EntityFilterGroupOutput = { + state: FilterGroupStates; + setSelectedFilters: (filterIds: string[]) => void; +}; + +/** + * Hook that exposes the relevant data and operations for a single filter + * group. + */ +export const useEntityFilterGroup = ( + filterGroupId: string, + filterGroup: FilterGroup, + initialSelectedFilters?: string[], +): EntityFilterGroupOutput => { + const context = useContext(filterGroupsContext); + if (!context) { + throw new Error(`Must be used inside an EntityFilterGroupsProvider`); + } + const { + register, + unregister, + setGroupSelectedFilters, + filterGroupStates, + } = context; + + // Intentionally consider initial set only at mount time + // eslint-disable-next-line react-hooks/exhaustive-deps + const initialMemo = useMemo(() => initialSelectedFilters?.slice(), []); + + // Register the group on mount, and unregister on unmount + useEffect(() => { + register(filterGroupId, filterGroup, initialMemo); + return () => unregister(filterGroupId); + }, [register, unregister, filterGroupId, filterGroup, initialMemo]); + + const setSelectedFilters = useCallback( + (filters: string[]) => { + setGroupSelectedFilters(filterGroupId, filters); + }, + [setGroupSelectedFilters, filterGroupId], + ); + + let state = filterGroupStates[filterGroupId]; + if (!state) { + state = { type: 'loading' }; + } + + return { state, setSelectedFilters }; +}; diff --git a/plugins/scaffolder/src/filter/useFilteredEntities.ts b/plugins/scaffolder/src/filter/useFilteredEntities.ts new file mode 100644 index 0000000000..2d7dcfd89d --- /dev/null +++ b/plugins/scaffolder/src/filter/useFilteredEntities.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { useContext } from 'react'; +import { filterGroupsContext } from './context'; + +/** + * Hook that exposes the result of applying a set of filter groups. + */ +export function useFilteredEntities() { + const context = useContext(filterGroupsContext); + if (!context) { + throw new Error(`Must be used inside an EntityFilterGroupsProvider`); + } + + return { + loading: context.loading, + error: context.error, + matchingEntities: context.matchingEntities, + availableTags: context.availableTags, + isCatalogEmpty: context.isCatalogEmpty, + reload: context.reload, + }; +} diff --git a/plugins/scaffolder/src/hooks/useStarredEntities.test.tsx b/plugins/scaffolder/src/hooks/useStarredEntities.test.tsx new file mode 100644 index 0000000000..78d2c4a58f --- /dev/null +++ b/plugins/scaffolder/src/hooks/useStarredEntities.test.tsx @@ -0,0 +1,121 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 React, { PropsWithChildren } from 'react'; +import { renderHook, act } from '@testing-library/react-hooks'; +import { useStarredEntities } from './useStarredEntities'; +import { + ApiProvider, + ApiRegistry, + storageApiRef, + WebStorage, + StorageApi, +} from '@backstage/core'; +import { MockErrorApi } from '@backstage/test-utils'; +import { Entity } from '@backstage/catalog-model'; + +describe('useStarredEntities', () => { + let mockStorage: StorageApi | undefined; + + const mockEntity: Entity = { + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'mock', + }, + }; + + const secondMockEntity: Entity = { + apiVersion: '1', + kind: 'Component', + metadata: { + namespace: 'test', + name: 'mock2', + }, + }; + + const wrapper = ({ children }: PropsWithChildren<{}>) => { + return ( + + {children} + + ); + }; + + beforeEach(() => { + mockStorage = new WebStorage('@backstage', new MockErrorApi()).forBucket( + Date.now().toString(), // TODO(blam): need something that changes every test run for now until the MockStorage is implemented + ); + }); + it('should return an empty set for when there is no items in storage', async () => { + const { result } = renderHook(() => useStarredEntities(), { wrapper }); + + expect(result.current.starredEntities.size).toBe(0); + }); + it('should return a set with the current items when there is items in storage', async () => { + const expectedIds = ['i', 'am', 'some', 'test', 'ids']; + const store = mockStorage?.forBucket('settings'); + await store?.set('starredEntities', expectedIds); + + const { result } = renderHook(() => useStarredEntities(), { wrapper }); + + for (const item of expectedIds) { + expect(result.current.starredEntities.has(item)).toBeTruthy(); + } + }); + it('should listen to changes when the storage is set elsewhere', async () => { + const { result, waitForNextUpdate } = renderHook( + () => useStarredEntities(), + { wrapper }, + ); + + expect(result.current.starredEntities.size).toBe(0); + expect(result.current.isStarredEntity(mockEntity)).toBeFalsy(); + + // Make this happen after awaiting for the next update so we can + // catch when the hook re-renders with the latest data + setTimeout(() => result.current.toggleStarredEntity(mockEntity), 1); + + await waitForNextUpdate(); + + expect(result.current.starredEntities.size).toBe(1); + expect(result.current.isStarredEntity(mockEntity)).toBeTruthy(); + }); + + it('should write new entries to the local store when adding a togglging entity', async () => { + const { result } = renderHook(() => useStarredEntities(), { wrapper }); + + act(() => { + result.current.toggleStarredEntity(mockEntity); + }); + + expect(result.current.isStarredEntity(mockEntity)).toBeTruthy(); + expect(result.current.isStarredEntity(secondMockEntity)).toBeFalsy(); + }); + + it('should remove an existing entity when toggling entries', async () => { + const { result } = renderHook(() => useStarredEntities(), { wrapper }); + + act(() => { + result.current.toggleStarredEntity(mockEntity); + result.current.toggleStarredEntity(secondMockEntity); + result.current.toggleStarredEntity(mockEntity); + }); + + expect(result.current.isStarredEntity(mockEntity)).toBeFalsy(); + expect(result.current.isStarredEntity(secondMockEntity)).toBeTruthy(); + }); +}); diff --git a/plugins/scaffolder/src/hooks/useStarredEntities.ts b/plugins/scaffolder/src/hooks/useStarredEntities.ts new file mode 100644 index 0000000000..7cbbbb7ce6 --- /dev/null +++ b/plugins/scaffolder/src/hooks/useStarredEntities.ts @@ -0,0 +1,75 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 '@backstage/catalog-model'; +import { storageApiRef, useApi } from '@backstage/core'; +import { useCallback, useEffect, useState } from 'react'; +import { useObservable } from 'react-use'; + +const buildEntityKey = (component: Entity) => + `entity:${component.kind}:${component.metadata.namespace ?? 'default'}:${ + component.metadata.name + }`; + +export const useStarredEntities = () => { + const storageApi = useApi(storageApiRef); + const settingsStore = storageApi.forBucket('settings'); + const rawStarredEntityKeys = + settingsStore.get('starredEntities') ?? []; + + const [starredEntities, setStarredEntities] = useState( + new Set(rawStarredEntityKeys), + ); + + const observedItems = useObservable( + settingsStore.observe$('starredEntities'), + ); + + useEffect(() => { + if (observedItems?.newValue) { + const currentValue = observedItems?.newValue ?? []; + setStarredEntities(new Set(currentValue)); + } + }, [observedItems?.newValue]); + + const toggleStarredEntity = useCallback( + (entity: Entity) => { + const entityKey = buildEntityKey(entity); + if (starredEntities.has(entityKey)) { + starredEntities.delete(entityKey); + } else { + starredEntities.add(entityKey); + } + + settingsStore.set('starredEntities', Array.from(starredEntities)); + }, + [starredEntities, settingsStore], + ); + + const isStarredEntity = useCallback( + (entity: Entity) => { + const entityKey = buildEntityKey(entity); + return starredEntities.has(entityKey); + }, + [starredEntities], + ); + + return { + starredEntities, + toggleStarredEntity, + isStarredEntity, + }; +}; From bff8620448f9e9e6167920cbc57196f44b68a669 Mon Sep 17 00:00:00 2001 From: Oscar Hernandez Date: Mon, 8 Feb 2021 15:44:40 -0600 Subject: [PATCH 013/270] Scaffolder: Implement search --- .../ScaffolderPage/ScaffolderPage.tsx | 109 +++++++++++++++--- 1 file changed, 91 insertions(+), 18 deletions(-) diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index 3a8701253d..59480d1aba 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -29,8 +29,27 @@ import { WarningPanel, } from '@backstage/core'; import { catalogApiRef, isOwnerOf } from '@backstage/plugin-catalog-react'; -import { Button, Grid, Link, makeStyles, Typography } from '@material-ui/core'; -import React, { useEffect, useMemo, useState } from 'react'; +import { + Button, + FormControl, + Grid, + IconButton, + Input, + InputAdornment, + InputLabel, + Link, + makeStyles, + TextField, + Toolbar, + Typography, +} from '@material-ui/core'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { Link as RouterLink } from 'react-router-dom'; import useStaleWhileRevalidate from 'swr'; import { EntityFilterGroupsProvider, useFilteredEntities } from '../../filter'; @@ -42,6 +61,8 @@ import SettingsIcon from '@material-ui/icons/Settings'; import StarIcon from '@material-ui/icons/Star'; import { useOwnUser } from '../useOwnUser'; import { useStarredEntities } from '../../hooks/useStarredEntities'; +import Search from '@material-ui/icons/Search'; +import Clear from '@material-ui/icons/Clear'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -50,6 +71,10 @@ const useStyles = makeStyles(theme => ({ gridTemplateColumns: '250px 1fr', gridColumnGap: theme.spacing(2), }, + searchToolbar: { + paddingLeft: 0, + paddingRight: 0, + }, })); const getTemplateCardProps = ( @@ -78,6 +103,7 @@ export const ScaffolderPageContents = () => { } = useFilteredEntities(); // TODO: use Selected Sidebar Item const [selectedSidebarItem, setSelectedSidebarItem] = useState(); + const catalogApi = useApi(catalogApiRef); const errorApi = useApi(errorApiRef); const { @@ -103,6 +129,16 @@ export const ScaffolderPageContents = () => { // TODO: ButtonGroup from CatalogFilter? const filterGroups = useMemo( () => [ + { + name: orgName, + items: [ + { + id: 'all', + label: 'All', + filterFn: () => true, + }, + ], + }, { name: 'Personal', // TODO: Do we need owner? items: [ @@ -120,20 +156,28 @@ export const ScaffolderPageContents = () => { }, ], }, - { - name: orgName, - items: [ - { - id: 'all', - label: 'All', - filterFn: () => true, - }, - ], - }, ], [isStarredEntity, orgName, user], ); + const [search, setSearch] = useState(''); + const [filteredTemplates, setFilteredTemplates] = useState([] as Entity[]); // TODO: Should I use Entity? + useEffect(() => { + const searchUppercase = search.toUpperCase(); + if (search.length === 0) { + return setFilteredTemplates(matchingEntities); + } + return setFilteredTemplates( + matchingEntities.filter(template => { + const { title, tags } = template.metadata; + return ( + `${title}`.toUpperCase().indexOf(searchUppercase) !== -1 || + `${tags}`.toUpperCase().indexOf(searchUppercase) !== -1 + ); + }), + ); + }, [search, matchingEntities]); + return (
{
+ + + setSearch(event.target.value)} + value={search} + startAdornment={ + + + + } + endAdornment={ + + setSearch('')} + edge="end" + disabled={search.length === 0} + > + + + + } + /> + + + setSelectedSidebarItem(label)} // TODO: setSelecteSidebarItem - initiallySelected="owned" + initiallySelected="all" />
- {!matchingEntities && loading && } - {matchingEntities && !matchingEntities.length && ( + {!filteredTemplates && loading && } + {filteredTemplates && !filteredTemplates.length && ( Shoot! Looks like you don't have any templates. Check out the documentation{' '} @@ -189,9 +262,9 @@ export const ScaffolderPageContents = () => { )} - {matchingEntities && - matchingEntities?.length > 0 && - matchingEntities.map(template => { + {filteredTemplates && + filteredTemplates?.length > 0 && + filteredTemplates.map(template => { return ( Date: Mon, 8 Feb 2021 16:56:11 -0600 Subject: [PATCH 014/270] Scaffolder: Replace Tag Filter with Category Filter --- .../ResultsFilter/ResultsFilter.tsx | 39 +++++++------- .../AllServicesCount.tsx | 0 .../ScaffolderFilter.test.tsx} | 14 ++--- .../ScaffolderFilter.tsx} | 2 +- .../index.ts | 2 +- .../ScaffolderPage/ScaffolderPage.tsx | 41 +++------------ .../src/filter/EntityFilterGroupsProvider.tsx | 51 +++++++++---------- plugins/scaffolder/src/filter/context.ts | 4 +- .../src/filter/useFilteredEntities.ts | 2 +- 9 files changed, 65 insertions(+), 90 deletions(-) rename plugins/scaffolder/src/components/{CatalogFilter => ScaffolderFilter}/AllServicesCount.tsx (100%) rename plugins/scaffolder/src/components/{CatalogFilter/CatalogFilter.test.tsx => ScaffolderFilter/ScaffolderFilter.test.tsx} (94%) rename plugins/scaffolder/src/components/{CatalogFilter/CatalogFilter.tsx => ScaffolderFilter/ScaffolderFilter.tsx} (99%) rename plugins/scaffolder/src/components/{CatalogFilter => ScaffolderFilter}/index.ts (91%) diff --git a/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.tsx b/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.tsx index 8c5737b7b6..5df70bd203 100644 --- a/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.tsx +++ b/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.tsx @@ -50,28 +50,28 @@ const useStyles = makeStyles(theme => ({ })); type Props = { - availableTags: string[]; + availableCategories: string[]; }; /** * The additional results filter in the sidebar. */ -export const ResultsFilter = ({ availableTags }: Props) => { +export const ResultsFilter = ({ availableCategories }: Props) => { const classes = useStyles(); - const [selectedTags, setSelectedTags] = useState([]); + const [selectedCategories, setSelectedCategories] = useState([]); const context = useContext(filterGroupsContext); if (!context) { throw new Error(`Must be used inside an EntityFilterGroupsProvider`); } - const setSelectedTagsFilter = context?.setSelectedTags; + const setSelectedCatgoriesFilter = context?.setSelectedCategories; - const updateSelectedTags = useCallback( - (tags: string[]) => { - setSelectedTags(tags); - setSelectedTagsFilter(tags); + const updateSelectedCategories = useCallback( + (categories: string[]) => { + setSelectedCategories(categories); + setSelectedCatgoriesFilter(categories); }, - [setSelectedTags, setSelectedTagsFilter], + [setSelectedCategories, setSelectedCatgoriesFilter], ); return ( @@ -80,14 +80,14 @@ export const ResultsFilter = ({ availableTags }: Props) => { Refine Results {' '} - +
- Tags + Categories - {availableTags.map(t => { + {availableCategories.map(t => { const labelId = `checkbox-list-label-${t}`; return ( { dense button onClick={() => - updateSelectedTags( - selectedTags.includes(t) - ? selectedTags.filter(s => s !== t) - : [...selectedTags, t], + updateSelectedCategories( + selectedCategories.includes(t) + ? selectedCategories.filter(s => s !== t) + : [...selectedCategories, t], ) } > - + ); })} diff --git a/plugins/scaffolder/src/components/CatalogFilter/AllServicesCount.tsx b/plugins/scaffolder/src/components/ScaffolderFilter/AllServicesCount.tsx similarity index 100% rename from plugins/scaffolder/src/components/CatalogFilter/AllServicesCount.tsx rename to plugins/scaffolder/src/components/ScaffolderFilter/AllServicesCount.tsx diff --git a/plugins/scaffolder/src/components/CatalogFilter/CatalogFilter.test.tsx b/plugins/scaffolder/src/components/ScaffolderFilter/ScaffolderFilter.test.tsx similarity index 94% rename from plugins/scaffolder/src/components/CatalogFilter/CatalogFilter.test.tsx rename to plugins/scaffolder/src/components/ScaffolderFilter/ScaffolderFilter.test.tsx index 0cc2108985..1c9a89c517 100644 --- a/plugins/scaffolder/src/components/CatalogFilter/CatalogFilter.test.tsx +++ b/plugins/scaffolder/src/components/ScaffolderFilter/ScaffolderFilter.test.tsx @@ -28,7 +28,7 @@ import { MockStorageApi, wrapInTestApp } from '@backstage/test-utils'; import { fireEvent, render, waitFor } from '@testing-library/react'; import React from 'react'; import { EntityFilterGroupsProvider } from '../../filter'; -import { ButtonGroup, CatalogFilter } from './CatalogFilter'; +import { ButtonGroup, ScaffolderFilter } from './ScaffolderFilter'; describe('Catalog Filter', () => { const catalogApi: Partial = { @@ -86,7 +86,7 @@ describe('Catalog Filter', () => { { name: 'Test Group 2', items: [] }, ]; const { findByText } = renderWrapped( - , + , ); for (const group of mockGroups) { expect(await findByText(group.name)).toBeInTheDocument(); @@ -113,7 +113,7 @@ describe('Catalog Filter', () => { ]; const { findByText } = renderWrapped( - , + , ); for (const item of mockGroups[0].items) { @@ -143,7 +143,7 @@ describe('Catalog Filter', () => { const onChange = jest.fn(); renderWrapped( - { const onChange = jest.fn(); renderWrapped( - { const onChange = jest.fn(); const { findByText } = renderWrapped( - { ]; const { findByText } = renderWrapped( - , + , ); expect(await findByText('1')).toBeInTheDocument(); diff --git a/plugins/scaffolder/src/components/CatalogFilter/CatalogFilter.tsx b/plugins/scaffolder/src/components/ScaffolderFilter/ScaffolderFilter.tsx similarity index 99% rename from plugins/scaffolder/src/components/CatalogFilter/CatalogFilter.tsx rename to plugins/scaffolder/src/components/ScaffolderFilter/ScaffolderFilter.tsx index 90a1e15ad0..14093462b8 100644 --- a/plugins/scaffolder/src/components/CatalogFilter/CatalogFilter.tsx +++ b/plugins/scaffolder/src/components/ScaffolderFilter/ScaffolderFilter.tsx @@ -83,7 +83,7 @@ type Props = { /** * The main filter group in the sidebar, toggling owned/starred/all. */ -export const CatalogFilter = ({ +export const ScaffolderFilter = ({ buttonGroups, onChange, initiallySelected, diff --git a/plugins/scaffolder/src/components/CatalogFilter/index.ts b/plugins/scaffolder/src/components/ScaffolderFilter/index.ts similarity index 91% rename from plugins/scaffolder/src/components/CatalogFilter/index.ts rename to plugins/scaffolder/src/components/ScaffolderFilter/index.ts index 5103b16307..f53e4be89f 100644 --- a/plugins/scaffolder/src/components/CatalogFilter/index.ts +++ b/plugins/scaffolder/src/components/ScaffolderFilter/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { CatalogFilter } from './CatalogFilter'; +export { ScaffolderFilter } from './ScaffolderFilter'; diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index 59480d1aba..1184c44263 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -19,7 +19,6 @@ import { configApiRef, Content, ContentHeader, - errorApiRef, Header, Lifecycle, Page, @@ -28,7 +27,7 @@ import { useApi, WarningPanel, } from '@backstage/core'; -import { catalogApiRef, isOwnerOf } from '@backstage/plugin-catalog-react'; +import { isOwnerOf } from '@backstage/plugin-catalog-react'; import { Button, FormControl, @@ -36,27 +35,18 @@ import { IconButton, Input, InputAdornment, - InputLabel, Link, makeStyles, - TextField, Toolbar, Typography, } from '@material-ui/core'; -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { Link as RouterLink } from 'react-router-dom'; -import useStaleWhileRevalidate from 'swr'; import { EntityFilterGroupsProvider, useFilteredEntities } from '../../filter'; import { TemplateCard, TemplateCardProps } from '../TemplateCard'; import { ResultsFilter } from '../ResultsFilter/ResultsFilter'; -import { CatalogFilter } from '../CatalogFilter'; -import { ButtonGroup } from '../CatalogFilter/CatalogFilter'; +import { ScaffolderFilter } from '../ScaffolderFilter'; +import { ButtonGroup } from '../ScaffolderFilter/ScaffolderFilter'; import SettingsIcon from '@material-ui/icons/Settings'; import StarIcon from '@material-ui/icons/Star'; import { useOwnUser } from '../useOwnUser'; @@ -96,29 +86,14 @@ export const ScaffolderPageContents = () => { const { loading, error, - reload, + reload, // TODO: Configure reload matchingEntities, - availableTags, // TODO: Change tags to Categories + availableCategories, // TODO: Change tags to Categories isCatalogEmpty, } = useFilteredEntities(); // TODO: use Selected Sidebar Item const [selectedSidebarItem, setSelectedSidebarItem] = useState(); - const catalogApi = useApi(catalogApiRef); - const errorApi = useApi(errorApiRef); - const { - data: templates /* isValidating*/ /* , error */, - } = useStaleWhileRevalidate('templates/all', async () => { - const response = await catalogApi.getEntities({ - filter: { kind: 'Template' }, - }); - return response.items as TemplateEntityV1alpha1[]; - }); - useEffect(() => { - if (!error) return; - errorApi.post(error); - }, [error, errorApi]); - const { value: user } = useOwnUser(); const configApi = useApi(configApiRef); @@ -237,12 +212,12 @@ export const ScaffolderPageContents = () => { - setSelectedSidebarItem(label)} // TODO: setSelecteSidebarItem initiallySelected="all" /> - +
{!filteredTemplates && loading && } diff --git a/plugins/scaffolder/src/filter/EntityFilterGroupsProvider.tsx b/plugins/scaffolder/src/filter/EntityFilterGroupsProvider.tsx index 92b99c74be..1e5474f327 100644 --- a/plugins/scaffolder/src/filter/EntityFilterGroupsProvider.tsx +++ b/plugins/scaffolder/src/filter/EntityFilterGroupsProvider.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { Entity, TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { useApi } from '@backstage/core'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; import React, { useCallback, useEffect, useRef, useState } from 'react'; @@ -59,12 +59,12 @@ function useProvideEntityFilters(): FilterGroupsContext { const selectedFilterKeys = useRef<{ [filterGroupId: string]: Set; }>({}); - const selectedTags = useRef([]); + const selectedCategories = useRef([]); const [filterGroupStates, setFilterGroupStates] = useState<{ [filterGroupId: string]: FilterGroupStates; }>({}); const [matchingEntities, setMatchingEntities] = useState([]); - const [availableTags, setAvailableTags] = useState([]); + const [availableCategories, setAvailableCategories] = useState([]); const [isCatalogEmpty, setCatalogEmpty] = useState(false); useEffect(() => { @@ -76,7 +76,7 @@ function useProvideEntityFilters(): FilterGroupsContext { buildStates( filterGroups.current, selectedFilterKeys.current, - selectedTags.current, + selectedCategories.current, entities, error, ), @@ -85,11 +85,11 @@ function useProvideEntityFilters(): FilterGroupsContext { buildMatchingEntities( filterGroups.current, selectedFilterKeys.current, - selectedTags.current, + selectedCategories.current, entities, ), ); - setAvailableTags(collectTags(entities)); + setAvailableCategories(collectCategories(entities)); setCatalogEmpty(entities !== undefined && entities.length === 0); }, [entities, error]); @@ -125,9 +125,9 @@ function useProvideEntityFilters(): FilterGroupsContext { [rebuild], ); - const setSelectedTags = useCallback( - (tags: string[]) => { - selectedTags.current = tags; + const setSelectedCategories = useCallback( + (categories: string[]) => { + selectedCategories.current = categories; rebuild(); }, [rebuild], @@ -141,13 +141,13 @@ function useProvideEntityFilters(): FilterGroupsContext { register, unregister, setGroupSelectedFilters, - setSelectedTags, + setSelectedCategories, reload, loading: !error && !entities, error, filterGroupStates, matchingEntities, - availableTags, + availableCategories, isCatalogEmpty, }; } @@ -157,7 +157,7 @@ function useProvideEntityFilters(): FilterGroupsContext { function buildStates( filterGroups: { [filterGroupId: string]: FilterGroup }, selectedFilterKeys: { [filterGroupId: string]: Set }, - selectedTags: string[], + selectedCategories: string[], entities?: Entity[], error?: Error, ): { [filterGroupId: string]: FilterGroupStates } { @@ -186,7 +186,7 @@ function buildStates( const otherMatchingEntities = buildMatchingEntities( filterGroups, selectedFilterKeys, - selectedTags, + selectedCategories, entities, filterGroupId, ); @@ -204,15 +204,15 @@ function buildStates( return result; } -// Given all entites, find all possible tags and provide them in a sorted list. -function collectTags(entities?: Entity[]): string[] { - const tags = new Set(); +// Given all entites, find all possible categories and provide them in a sorted list. +function collectCategories(entities?: Entity[]): string[] { + const categories = new Set(); (entities || []).forEach(e => { - if (e.metadata.tags) { - e.metadata.tags.forEach(t => tags.add(t)); + if (e.spec?.type) { + categories.add(e.spec.type as string); } }); - return Array.from(tags).sort(); + return Array.from(categories).sort(); } // Given all filter groups and what filters are actually selected, extract all @@ -220,7 +220,7 @@ function collectTags(entities?: Entity[]): string[] { function buildMatchingEntities( filterGroups: { [filterGroupId: string]: FilterGroup }, selectedFilterKeys: { [filterGroupId: string]: Set }, - selectedTags: string[], + selectedCategories: string[], entities?: Entity[], excludeFilterGroupId?: string, ): Entity[] { @@ -247,13 +247,10 @@ function buildMatchingEntities( } } - // Filter by tags, if at least one tag is selected. Include all entities - // that have at least one of the selected tags - if (selectedTags.length > 0) { - allFilters.push( - entity => - !!entity.metadata.tags && - entity.metadata.tags.some(t => selectedTags.includes(t)), + // Filter by categories, if at least one category is selected. + if (selectedCategories.length > 0) { + allFilters.push(entity => + selectedCategories.some(c => entity.spec?.type === c), ); } diff --git a/plugins/scaffolder/src/filter/context.ts b/plugins/scaffolder/src/filter/context.ts index c025480fa6..e46ebb5507 100644 --- a/plugins/scaffolder/src/filter/context.ts +++ b/plugins/scaffolder/src/filter/context.ts @@ -26,13 +26,13 @@ export type FilterGroupsContext = { ) => void; unregister: (filterGroupId: string) => void; setGroupSelectedFilters: (filterGroupId: string, filterIds: string[]) => void; - setSelectedTags: (tags: string[]) => void; + setSelectedCategories: (categories: string[]) => void; reload: () => Promise; loading: boolean; error?: Error; filterGroupStates: { [filterGroupId: string]: FilterGroupStates }; matchingEntities: Entity[]; - availableTags: string[]; + availableCategories: string[]; isCatalogEmpty: boolean; }; diff --git a/plugins/scaffolder/src/filter/useFilteredEntities.ts b/plugins/scaffolder/src/filter/useFilteredEntities.ts index 2d7dcfd89d..cc2c739546 100644 --- a/plugins/scaffolder/src/filter/useFilteredEntities.ts +++ b/plugins/scaffolder/src/filter/useFilteredEntities.ts @@ -30,7 +30,7 @@ export function useFilteredEntities() { loading: context.loading, error: context.error, matchingEntities: context.matchingEntities, - availableTags: context.availableTags, + availableCategories: context.availableCategories, isCatalogEmpty: context.isCatalogEmpty, reload: context.reload, }; From 8cea525a32e7e3b3a7927a3682a4739a411375c8 Mon Sep 17 00:00:00 2001 From: Oscar Hernandez Date: Tue, 9 Feb 2021 17:21:22 -0600 Subject: [PATCH 015/270] Scaffolder: Filter/Search cleanup --- .../ResultsFilter/ResultsFilter.test.tsx | 12 +- .../ScaffolderPage/ScaffolderPage.tsx | 114 +++++------------- .../SearchToolbar/SearchToolbar.test.tsx | 33 +++++ .../SearchToolbar/SearchToolbar.tsx | 74 ++++++++++++ .../src/filter/EntityFilterGroupsProvider.tsx | 20 +-- plugins/scaffolder/src/filter/context.ts | 4 +- .../src/filter/useFilteredEntities.ts | 2 +- 7 files changed, 155 insertions(+), 104 deletions(-) create mode 100644 plugins/scaffolder/src/components/SearchToolbar/SearchToolbar.test.tsx create mode 100644 plugins/scaffolder/src/components/SearchToolbar/SearchToolbar.tsx diff --git a/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.test.tsx b/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.test.tsx index 9be1c995e8..22de59b6ef 100644 --- a/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.test.tsx +++ b/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.test.tsx @@ -93,13 +93,15 @@ describe('Results Filter', () => { ), ); - it('should render all available tags', async () => { - const tags = ['test', 'java']; + it('should render all available categories', async () => { + const categories = ['test', 'java']; const { findByText } = renderWrapped( - , + , ); - for (const tag of tags) { - expect(await findByText(tag)).toBeInTheDocument(); + for (const category of categories) { + expect( + await findByText(category.charAt(0).toUpperCase() + category.slice(1)), + ).toBeInTheDocument(); } }); }); diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index 1184c44263..eba8a1bd48 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TemplateEntityV1alpha1, Entity } from '@backstage/catalog-model'; +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { configApiRef, Content, @@ -27,19 +27,7 @@ import { useApi, WarningPanel, } from '@backstage/core'; -import { isOwnerOf } from '@backstage/plugin-catalog-react'; -import { - Button, - FormControl, - Grid, - IconButton, - Input, - InputAdornment, - Link, - makeStyles, - Toolbar, - Typography, -} from '@material-ui/core'; +import { Button, Grid, Link, makeStyles, Typography } from '@material-ui/core'; import React, { useEffect, useMemo, useState } from 'react'; import { Link as RouterLink } from 'react-router-dom'; import { EntityFilterGroupsProvider, useFilteredEntities } from '../../filter'; @@ -47,12 +35,10 @@ import { TemplateCard, TemplateCardProps } from '../TemplateCard'; import { ResultsFilter } from '../ResultsFilter/ResultsFilter'; import { ScaffolderFilter } from '../ScaffolderFilter'; import { ButtonGroup } from '../ScaffolderFilter/ScaffolderFilter'; -import SettingsIcon from '@material-ui/icons/Settings'; import StarIcon from '@material-ui/icons/Star'; import { useOwnUser } from '../useOwnUser'; import { useStarredEntities } from '../../hooks/useStarredEntities'; -import Search from '@material-ui/icons/Search'; -import Clear from '@material-ui/icons/Clear'; +import SearchToolbar from '../SearchToolbar/SearchToolbar'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -61,21 +47,16 @@ const useStyles = makeStyles(theme => ({ gridTemplateColumns: '250px 1fr', gridColumnGap: theme.spacing(2), }, - searchToolbar: { - paddingLeft: 0, - paddingRight: 0, - }, })); const getTemplateCardProps = ( - template: Entity, + template: TemplateEntityV1alpha1, ): TemplateCardProps & { key: string } => { return { key: template.metadata.uid!, name: template.metadata.name, title: `${(template.metadata.title || template.metadata.name) ?? ''}`, - // TODO: Validate this prop (I changed TemplateEntityV1alpha1 to Entity interface) Remove 'as any' - type: (template as any).spec.type ?? '', + type: template.spec.type ?? '', description: template.metadata.description ?? '-', tags: (template.metadata?.tags as string[]) ?? [], }; @@ -86,22 +67,12 @@ export const ScaffolderPageContents = () => { const { loading, error, - reload, // TODO: Configure reload - matchingEntities, - availableCategories, // TODO: Change tags to Categories - isCatalogEmpty, + filteredEntities, + availableCategories, } = useFilteredEntities(); - // TODO: use Selected Sidebar Item - const [selectedSidebarItem, setSelectedSidebarItem] = useState(); - - const { value: user } = useOwnUser(); - const configApi = useApi(configApiRef); const orgName = configApi.getOptionalString('organization.name') ?? 'Company'; - const { isStarredEntity } = useStarredEntities(); - - // TODO: ButtonGroup from CatalogFilter? const filterGroups = useMemo( () => [ { @@ -115,14 +86,8 @@ export const ScaffolderPageContents = () => { ], }, { - name: 'Personal', // TODO: Do we need owner? + name: 'Personal', items: [ - { - id: 'owned', - label: 'Owned', - icon: SettingsIcon, - filterFn: entity => user !== undefined && isOwnerOf(user, entity), - }, { id: 'starred', label: 'Starred', @@ -132,26 +97,30 @@ export const ScaffolderPageContents = () => { ], }, ], - [isStarredEntity, orgName, user], + [isStarredEntity, orgName], + ); + const [search, setSearch] = useState(''); + const [matchingEntities, setMatchingEntities] = useState( + [] as TemplateEntityV1alpha1[], ); - const [search, setSearch] = useState(''); - const [filteredTemplates, setFilteredTemplates] = useState([] as Entity[]); // TODO: Should I use Entity? + // Match templates by search input value useEffect(() => { const searchUppercase = search.toUpperCase(); if (search.length === 0) { - return setFilteredTemplates(matchingEntities); + return setMatchingEntities(filteredEntities); } - return setFilteredTemplates( - matchingEntities.filter(template => { + // Match search by title|tags + return setMatchingEntities( + filteredEntities.filter(template => { const { title, tags } = template.metadata; return ( `${title}`.toUpperCase().indexOf(searchUppercase) !== -1 || - `${tags}`.toUpperCase().indexOf(searchUppercase) !== -1 + tags?.join('').toUpperCase().indexOf(searchUppercase) !== -1 ); }), ); - }, [search, matchingEntities]); + }, [search, filteredEntities]); return ( @@ -183,45 +152,16 @@ export const ScaffolderPageContents = () => {
- - - setSearch(event.target.value)} - value={search} - startAdornment={ - - - - } - endAdornment={ - - setSearch('')} - edge="end" - disabled={search.length === 0} - > - - - - } - /> - - - + setSelectedSidebarItem(label)} // TODO: setSelecteSidebarItem + buttonGroups={filterGroups} initiallySelected="all" />
- {!filteredTemplates && loading && } - {filteredTemplates && !filteredTemplates.length && ( + {!matchingEntities && loading && } + {matchingEntities && !matchingEntities.length && ( Shoot! Looks like you don't have any templates. Check out the documentation{' '} @@ -237,9 +177,9 @@ export const ScaffolderPageContents = () => { )} - {filteredTemplates && - filteredTemplates?.length > 0 && - filteredTemplates.map(template => { + {matchingEntities && + matchingEntities?.length > 0 && + matchingEntities.map(template => { return ( { + it('should display search value and execute set callback', async () => { + const setSearchSpy = jest.fn(); + const { getByDisplayValue } = render( + , + ); + + const searchInput = getByDisplayValue('hello'); + expect(searchInput).toBeInTheDocument(); + fireEvent.change(searchInput, { target: { value: 'world' } }); + expect(setSearchSpy).toHaveBeenCalled(); + }); +}); diff --git a/plugins/scaffolder/src/components/SearchToolbar/SearchToolbar.tsx b/plugins/scaffolder/src/components/SearchToolbar/SearchToolbar.tsx new file mode 100644 index 0000000000..4a50fcb257 --- /dev/null +++ b/plugins/scaffolder/src/components/SearchToolbar/SearchToolbar.tsx @@ -0,0 +1,74 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 React from 'react'; +import { + FormControl, + InputAdornment, + makeStyles, + Toolbar, + Input, + IconButton, +} from '@material-ui/core'; +import Search from '@material-ui/icons/Search'; +import Clear from '@material-ui/icons/Clear'; + +interface Props { + search: string; + setSearch: Function; +} + +const useStyles = makeStyles(_theme => ({ + searchToolbar: { + paddingLeft: 0, + paddingRight: 0, + }, +})); + +const SearchToolbar = ({ search, setSearch }: Props) => { + const styles = useStyles(); + return ( + + + setSearch(event.target.value)} + value={search} + startAdornment={ + + + + } + endAdornment={ + + setSearch('')} + edge="end" + disabled={search.length === 0} + > + + + + } + /> + + + ); +}; + +export default SearchToolbar; diff --git a/plugins/scaffolder/src/filter/EntityFilterGroupsProvider.tsx b/plugins/scaffolder/src/filter/EntityFilterGroupsProvider.tsx index 1e5474f327..ba99a0294a 100644 --- a/plugins/scaffolder/src/filter/EntityFilterGroupsProvider.tsx +++ b/plugins/scaffolder/src/filter/EntityFilterGroupsProvider.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Entity, TemplateEntityV1alpha1 } from '@backstage/catalog-model'; +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { useApi } from '@backstage/core'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; import React, { useCallback, useEffect, useRef, useState } from 'react'; @@ -50,7 +50,7 @@ function useProvideEntityFilters(): FilterGroupsContext { const response = await catalogApi.getEntities({ filter: { kind: 'Template' }, }); - return response.items; + return response.items as TemplateEntityV1alpha1[]; }); const filterGroups = useRef<{ @@ -63,7 +63,9 @@ function useProvideEntityFilters(): FilterGroupsContext { const [filterGroupStates, setFilterGroupStates] = useState<{ [filterGroupId: string]: FilterGroupStates; }>({}); - const [matchingEntities, setMatchingEntities] = useState([]); + const [filteredEntities, setFilteredEntities] = useState< + TemplateEntityV1alpha1[] + >([]); const [availableCategories, setAvailableCategories] = useState([]); const [isCatalogEmpty, setCatalogEmpty] = useState(false); @@ -81,7 +83,7 @@ function useProvideEntityFilters(): FilterGroupsContext { error, ), ); - setMatchingEntities( + setFilteredEntities( buildMatchingEntities( filterGroups.current, selectedFilterKeys.current, @@ -146,7 +148,7 @@ function useProvideEntityFilters(): FilterGroupsContext { loading: !error && !entities, error, filterGroupStates, - matchingEntities, + filteredEntities, availableCategories, isCatalogEmpty, }; @@ -158,7 +160,7 @@ function buildStates( filterGroups: { [filterGroupId: string]: FilterGroup }, selectedFilterKeys: { [filterGroupId: string]: Set }, selectedCategories: string[], - entities?: Entity[], + entities?: TemplateEntityV1alpha1[], error?: Error, ): { [filterGroupId: string]: FilterGroupStates } { // On error - all entries are an error state @@ -205,7 +207,7 @@ function buildStates( } // Given all entites, find all possible categories and provide them in a sorted list. -function collectCategories(entities?: Entity[]): string[] { +function collectCategories(entities?: TemplateEntityV1alpha1[]): string[] { const categories = new Set(); (entities || []).forEach(e => { if (e.spec?.type) { @@ -221,9 +223,9 @@ function buildMatchingEntities( filterGroups: { [filterGroupId: string]: FilterGroup }, selectedFilterKeys: { [filterGroupId: string]: Set }, selectedCategories: string[], - entities?: Entity[], + entities?: TemplateEntityV1alpha1[], excludeFilterGroupId?: string, -): Entity[] { +): TemplateEntityV1alpha1[] { // Build one filter fn per filter group const allFilters: EntityFilterFn[] = []; for (const [filterGroupId, filterGroup] of Object.entries(filterGroups)) { diff --git a/plugins/scaffolder/src/filter/context.ts b/plugins/scaffolder/src/filter/context.ts index e46ebb5507..f0a9d3755e 100644 --- a/plugins/scaffolder/src/filter/context.ts +++ b/plugins/scaffolder/src/filter/context.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { createContext } from 'react'; import { FilterGroup, FilterGroupStates } from './types'; @@ -31,7 +31,7 @@ export type FilterGroupsContext = { loading: boolean; error?: Error; filterGroupStates: { [filterGroupId: string]: FilterGroupStates }; - matchingEntities: Entity[]; + filteredEntities: TemplateEntityV1alpha1[]; availableCategories: string[]; isCatalogEmpty: boolean; }; diff --git a/plugins/scaffolder/src/filter/useFilteredEntities.ts b/plugins/scaffolder/src/filter/useFilteredEntities.ts index cc2c739546..d3eb553687 100644 --- a/plugins/scaffolder/src/filter/useFilteredEntities.ts +++ b/plugins/scaffolder/src/filter/useFilteredEntities.ts @@ -29,7 +29,7 @@ export function useFilteredEntities() { return { loading: context.loading, error: context.error, - matchingEntities: context.matchingEntities, + filteredEntities: context.filteredEntities, availableCategories: context.availableCategories, isCatalogEmpty: context.isCatalogEmpty, reload: context.reload, From 67590600645a97dd45fa5768ea52e366297a5269 Mon Sep 17 00:00:00 2001 From: Oscar Hernandez Date: Wed, 10 Feb 2021 10:29:17 -0600 Subject: [PATCH 016/270] Scaffolder: search/filter cleanup --- .../ResultsFilter/ResultsFilter.tsx | 18 +- .../ScaffolderFilter.test.tsx | 339 +++++++++--------- .../ScaffolderPage/ScaffolderPage.tsx | 22 +- .../scaffolder/src/components/useOwnUser.ts | 41 --- 4 files changed, 192 insertions(+), 228 deletions(-) delete mode 100644 plugins/scaffolder/src/components/useOwnUser.ts diff --git a/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.tsx b/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.tsx index 5df70bd203..19c88a87f7 100644 --- a/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.tsx +++ b/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.tsx @@ -87,25 +87,27 @@ export const ResultsFilter = ({ availableCategories }: Props) => { Categories - {availableCategories.map(t => { - const labelId = `checkbox-list-label-${t}`; + {availableCategories.map(category => { + const labelId = `checkbox-list-label-${category}`; return ( updateSelectedCategories( - selectedCategories.includes(t) - ? selectedCategories.filter(s => s !== t) - : [...selectedCategories, t], + selectedCategories.includes(category) + ? selectedCategories.filter( + selectedCategory => selectedCategory !== category, + ) + : [...selectedCategories, category], ) } > { /> ); diff --git a/plugins/scaffolder/src/components/ScaffolderFilter/ScaffolderFilter.test.tsx b/plugins/scaffolder/src/components/ScaffolderFilter/ScaffolderFilter.test.tsx index 1c9a89c517..b5ec45d8bb 100644 --- a/plugins/scaffolder/src/components/ScaffolderFilter/ScaffolderFilter.test.tsx +++ b/plugins/scaffolder/src/components/ScaffolderFilter/ScaffolderFilter.test.tsx @@ -14,8 +14,9 @@ * limitations under the License. */ -import { CatalogApi } from '@backstage/catalog-client'; -import { Entity } from '@backstage/catalog-model'; +import React from 'react'; +import { MockStorageApi, wrapInTestApp } from '@backstage/test-utils'; +import { fireEvent, render, waitFor } from '@testing-library/react'; import { ApiProvider, ApiRegistry, @@ -23,10 +24,9 @@ import { identityApiRef, storageApiRef, } from '@backstage/core'; +import { CatalogApi } from '@backstage/catalog-client'; +import { Entity } from '@backstage/catalog-model'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import { MockStorageApi, wrapInTestApp } from '@backstage/test-utils'; -import { fireEvent, render, waitFor } from '@testing-library/react'; -import React from 'react'; import { EntityFilterGroupsProvider } from '../../filter'; import { ButtonGroup, ScaffolderFilter } from './ScaffolderFilter'; @@ -80,185 +80,192 @@ describe('Catalog Filter', () => { ), ); - it('should render the different groups', async () => { - const mockGroups: ButtonGroup[] = [ - { name: 'Test Group 1', items: [] }, - { name: 'Test Group 2', items: [] }, - ]; - const { findByText } = renderWrapped( - , - ); - for (const group of mockGroups) { - expect(await findByText(group.name)).toBeInTheDocument(); - } - }); - - it('should render the different items and their names', async () => { - const mockGroups: ButtonGroup[] = [ - { - name: 'Test Group 1', - items: [ - { - id: 'all', - label: 'First Label', - filterFn: () => true, - }, - { - id: 'starred', - label: 'Second Label', - filterFn: () => false, - }, - ], - }, - ]; - - const { findByText } = renderWrapped( - , - ); - - for (const item of mockGroups[0].items) { - expect(await findByText(item.label)).toBeInTheDocument(); - } - }); - - it('selects the first item if no desired initial one is set', async () => { - const mockGroups: ButtonGroup[] = [ - { - name: 'Test Group 1', - items: [ - { - id: 'all', - label: 'First Label', - filterFn: () => true, - }, - { - id: 'starred', - label: 'Second Label', - filterFn: () => false, - }, - ], - }, - ]; - - const onChange = jest.fn(); - - renderWrapped( - , - ); - - await waitFor(() => { - expect(onChange).toHaveBeenLastCalledWith({ - id: 'all', - label: 'First Label', - }); + describe('filter groups', () => { + it('should render the different groups', async () => { + const mockGroups: ButtonGroup[] = [ + { name: 'Test Group 1', items: [] }, + { name: 'Test Group 2', items: [] }, + ]; + const { findByText } = renderWrapped( + , + ); + for (const group of mockGroups) { + expect(await findByText(group.name)).toBeInTheDocument(); + } }); }); - it('selects the initial item', async () => { - const mockGroups: ButtonGroup[] = [ - { - name: 'Test Group 1', - items: [ - { - id: 'all', - label: 'First Label', - filterFn: () => true, - }, - { - id: 'starred', - label: 'Second Label', - filterFn: () => false, - }, - ], - }, - ]; + describe('filter items', () => { + it('should render the different items and their names', async () => { + const mockGroups: ButtonGroup[] = [ + { + name: 'Test Group 1', + items: [ + { + id: 'all', + label: 'First Label', + filterFn: () => true, + }, + { + id: 'starred', + label: 'Second Label', + filterFn: () => false, + }, + ], + }, + ]; - const onChange = jest.fn(); + const { findByText } = renderWrapped( + , + ); - renderWrapped( - , - ); - - await waitFor(() => { - expect(onChange).toHaveBeenLastCalledWith({ - id: 'starred', - label: 'Second Label', - }); + for (const item of mockGroups[0].items) { + expect(await findByText(item.label)).toBeInTheDocument(); + } }); - }); - it('can change the selected item', async () => { - const mockGroups: ButtonGroup[] = [ - { - name: 'Test Group 1', - items: [ - { - id: 'all', - label: 'First Label', - filterFn: () => true, - }, - { - id: 'starred', - label: 'Second Label', - filterFn: () => false, - }, - ], - }, - ]; + it('selects the first item if no desired initial one is set', async () => { + const mockGroups: ButtonGroup[] = [ + { + name: 'Test Group 1', + items: [ + { + id: 'all', + label: 'First Label', + filterFn: () => true, + }, + { + id: 'starred', + label: 'Second Label', + filterFn: () => false, + }, + ], + }, + ]; - const onChange = jest.fn(); + const onChange = jest.fn(); - const { findByText } = renderWrapped( - , - ); + renderWrapped( + , + ); - await waitFor(() => { - expect(onChange).toHaveBeenLastCalledWith({ - id: 'all', - label: 'First Label', + await waitFor(() => { + expect(onChange).toHaveBeenLastCalledWith({ + id: 'all', + label: 'First Label', + }); }); }); - fireEvent.click(await findByText('Second Label')); + it('selects the initial item', async () => { + const mockGroups: ButtonGroup[] = [ + { + name: 'Test Group 1', + items: [ + { + id: 'all', + label: 'First Label', + filterFn: () => true, + }, + { + id: 'starred', + label: 'Second Label', + filterFn: () => false, + }, + ], + }, + ]; - await waitFor(() => { - expect(onChange).toHaveBeenLastCalledWith({ - id: 'starred', - label: 'Second Label', + const onChange = jest.fn(); + + renderWrapped( + , + ); + + await waitFor(() => { + expect(onChange).toHaveBeenLastCalledWith({ + id: 'starred', + label: 'Second Label', + }); }); }); - }); - it('displays match counts properly', async () => { - const mockGroups: ButtonGroup[] = [ - { - name: 'Test Group 1', - items: [ - { - id: 'owned', - label: 'First Label', - filterFn: entity => entity.spec?.owner === 'tools@example.com', - }, - ], - }, - ]; + it('can change the selected item', async () => { + const mockGroups: ButtonGroup[] = [ + { + name: 'Test Group 1', + items: [ + { + id: 'all', + label: 'First Label', + filterFn: () => true, + }, + { + id: 'starred', + label: 'Second Label', + filterFn: () => false, + }, + ], + }, + ]; - const { findByText } = renderWrapped( - , - ); + const onChange = jest.fn(); - expect(await findByText('1')).toBeInTheDocument(); + const { findByText } = renderWrapped( + , + ); + + await waitFor(() => { + expect(onChange).toHaveBeenLastCalledWith({ + id: 'all', + label: 'First Label', + }); + }); + + fireEvent.click(await findByText('Second Label')); + + await waitFor(() => { + expect(onChange).toHaveBeenLastCalledWith({ + id: 'starred', + label: 'Second Label', + }); + }); + }); + + it('displays match counts properly', async () => { + const mockGroups: ButtonGroup[] = [ + { + name: 'Test Group 1', + items: [ + { + id: 'owned', + label: 'First Label', + filterFn: entity => entity.spec?.owner === 'tools@example.com', + }, + ], + }, + ]; + + const { findByText } = renderWrapped( + , + ); + + expect(await findByText('1')).toBeInTheDocument(); + }); }); }); diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index eba8a1bd48..f3ccd1d440 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -14,7 +14,8 @@ * limitations under the License. */ -import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; +import React, { useEffect, useMemo, useState } from 'react'; +import { EntityMeta, TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { configApiRef, Content, @@ -28,7 +29,6 @@ import { WarningPanel, } from '@backstage/core'; import { Button, Grid, Link, makeStyles, Typography } from '@material-ui/core'; -import React, { useEffect, useMemo, useState } from 'react'; import { Link as RouterLink } from 'react-router-dom'; import { EntityFilterGroupsProvider, useFilteredEntities } from '../../filter'; import { TemplateCard, TemplateCardProps } from '../TemplateCard'; @@ -36,7 +36,6 @@ import { ResultsFilter } from '../ResultsFilter/ResultsFilter'; import { ScaffolderFilter } from '../ScaffolderFilter'; import { ButtonGroup } from '../ScaffolderFilter/ScaffolderFilter'; import StarIcon from '@material-ui/icons/Star'; -import { useOwnUser } from '../useOwnUser'; import { useStarredEntities } from '../../hooks/useStarredEntities'; import SearchToolbar from '../SearchToolbar/SearchToolbar'; @@ -104,21 +103,18 @@ export const ScaffolderPageContents = () => { [] as TemplateEntityV1alpha1[], ); - // Match templates by search input value + const matchesQuery = (metadata: EntityMeta, query: string) => + `${metadata.title}`.toUpperCase().indexOf(query) !== -1 || + metadata.tags?.join('').toUpperCase().indexOf(query) !== -1; + useEffect(() => { - const searchUppercase = search.toUpperCase(); if (search.length === 0) { return setMatchingEntities(filteredEntities); } - // Match search by title|tags return setMatchingEntities( - filteredEntities.filter(template => { - const { title, tags } = template.metadata; - return ( - `${title}`.toUpperCase().indexOf(searchUppercase) !== -1 || - tags?.join('').toUpperCase().indexOf(searchUppercase) !== -1 - ); - }), + filteredEntities.filter(template => + matchesQuery(template.metadata, search.toUpperCase()), + ), ); }, [search, filteredEntities]); diff --git a/plugins/scaffolder/src/components/useOwnUser.ts b/plugins/scaffolder/src/components/useOwnUser.ts deleted file mode 100644 index 29d8a0d11f..0000000000 --- a/plugins/scaffolder/src/components/useOwnUser.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 { UserEntity } from '@backstage/catalog-model'; -import { identityApiRef, useApi } from '@backstage/core'; -import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import { useAsync } from 'react-use'; -import { AsyncState } from 'react-use/lib/useAsync'; - -/** - * Get the catalog User entity (if any) that matches the logged-in user. - */ -export function useOwnUser(): AsyncState { - const catalogApi = useApi(catalogApiRef); - const identityApi = useApi(identityApiRef); - - // TODO: get the full entity (or at least the full entity name) from the - // identityApi - return useAsync( - () => - catalogApi.getEntityByName({ - kind: 'User', - namespace: 'default', - name: identityApi.getUserId(), - }) as Promise, - [catalogApi, identityApi], - ); -} From e8e35fb5fd67e99a1d3008204cd546dc235667a1 Mon Sep 17 00:00:00 2001 From: Oscar Hernandez Date: Wed, 10 Feb 2021 12:03:25 -0600 Subject: [PATCH 017/270] Scaffolder: Search/Filter Changeset --- .changeset/itchy-rivers-judge.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/itchy-rivers-judge.md diff --git a/.changeset/itchy-rivers-judge.md b/.changeset/itchy-rivers-judge.md new file mode 100644 index 0000000000..711becd33b --- /dev/null +++ b/.changeset/itchy-rivers-judge.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +Adding Search and Filter features to Scaffolder/Templates Grid From a70af22a2d076923e58e09f6aefbfae5462f936e Mon Sep 17 00:00:00 2001 From: mclarke Date: Wed, 10 Feb 2021 20:00:45 +0000 Subject: [PATCH 018/270] add changeset --- .changeset/blue-taxis-behave.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/blue-taxis-behave.md diff --git a/.changeset/blue-taxis-behave.md b/.changeset/blue-taxis-behave.md new file mode 100644 index 0000000000..5961c3b770 --- /dev/null +++ b/.changeset/blue-taxis-behave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +--- + +update kubernetes plugin backend function to use classes From 3ed048216beeed78779cd831f213d8f06e7e4690 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 11 Feb 2021 09:19:03 +0100 Subject: [PATCH 019/270] Delete Jobstage component --- .../src/components/JobStage/JobStage.tsx | 173 ------------------ .../src/components/JobStage/LogModal.tsx | 67 ------- .../src/components/JobStage/index.ts | 16 -- 3 files changed, 256 deletions(-) delete mode 100644 plugins/scaffolder/src/components/JobStage/JobStage.tsx delete mode 100644 plugins/scaffolder/src/components/JobStage/LogModal.tsx delete mode 100644 plugins/scaffolder/src/components/JobStage/index.ts diff --git a/plugins/scaffolder/src/components/JobStage/JobStage.tsx b/plugins/scaffolder/src/components/JobStage/JobStage.tsx deleted file mode 100644 index 1448fedf5c..0000000000 --- a/plugins/scaffolder/src/components/JobStage/JobStage.tsx +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 { - Accordion, - AccordionDetails, - AccordionSummary, - AccordionActions, - Box, - CircularProgress, - LinearProgress, - Typography, - Button, -} from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import ExpandLessIcon from '@material-ui/icons/ExpandLess'; -import cn from 'classnames'; -import moment from 'moment'; -import React, { Suspense, useEffect, useState } from 'react'; -import { LogModal } from './LogModal'; -import { Job } from '../../types'; - -const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog')); -moment.relativeTimeThreshold('ss', 0); - -const useStyles = makeStyles(theme => ({ - accordionDetails: { - padding: 0, - }, - button: { - order: -1, - margin: '0 1em 0 -20px', - }, - cardContent: { - backgroundColor: theme.palette.background.default, - }, - accordion: { - position: 'relative', - '&:after': { - pointerEvents: 'none', - content: '""', - position: 'absolute', - top: 0, - right: 0, - left: 0, - bottom: 0, - }, - }, - neutral: {}, - failed: { - '&:after': { - boxShadow: `inset 4px 0px 0px ${theme.palette.error.main}`, - }, - }, - started: { - '&:after': { - boxShadow: `inset 4px 0px 0px ${theme.palette.info.main}`, - }, - }, - completed: { - '&:after': { - boxShadow: `inset 4px 0px 0px ${theme.palette.success.main}`, - }, - }, - jobStatusTitle: { - display: 'flex', - width: '100%', - alignItems: 'center', - flexDirection: 'row', - justifyContent: 'space-between', - [theme.breakpoints.down('xs')]: { - flexDirection: 'column', - alignItems: 'flex-start', - justifyContent: 'flex-start', - }, - }, -})); - -type Props = { - name: string; - className?: string; - log: string[]; - startedAt: string; - endedAt?: string; - status: Job['status']; -}; - -export const JobStage = ({ endedAt, startedAt, name, log, status }: Props) => { - const classes = useStyles(); - - const [expanded, setExpanded] = useState(false); - useEffect(() => { - if (status === 'failed') setExpanded(true); - }, [status, setExpanded]); - - const timeElapsed = - status === 'processing' - ? moment - .duration(moment(endedAt ?? moment()).diff(moment(startedAt))) - .humanize() - : null; - - const [logsFullScreen, setLogsFullScreen] = useState(false); - const toggleLogsFullScreen = () => setLogsFullScreen(!logsFullScreen); - - return ( - ] ?? - classes.neutral, - )} - expanded={expanded} - onChange={(_, newState) => setExpanded(newState)} - > - : } - aria-controls={`panel-${name}-content`} - id={`panel-${name}-header`} - IconButtonProps={{ - className: classes.button, - }} - > - - {name} {timeElapsed && `(${timeElapsed})`}{' '} - {startedAt && !endedAt && } - - - - {log.length === 0 ? ( -
- -
- ) : ( - }> - -
- -
-
- )} -
- - - -
- ); -}; diff --git a/plugins/scaffolder/src/components/JobStage/LogModal.tsx b/plugins/scaffolder/src/components/JobStage/LogModal.tsx deleted file mode 100644 index 73b6253510..0000000000 --- a/plugins/scaffolder/src/components/JobStage/LogModal.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 React from 'react'; -import { - Dialog, - DialogTitle, - DialogContent, - IconButton, -} from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; -import Close from '@material-ui/icons/Close'; -import LazyLog from 'react-lazylog/build/LazyLog'; - -type Props = { - log: string[]; - open?: boolean; - onClose(): void; -}; - -const useStyles = makeStyles(theme => ({ - header: { - width: '100%', - padding: theme.spacing(1, 4), - }, - closeIcon: { - float: 'right', - padding: theme.spacing(0.5, 0), - }, - logs: { - boxShadow: '-3px -1px 7px 0px rgba(50, 50, 50, 0.59)', - height: '100%', - width: '100%', - }, -})); - -export const LogModal = ({ log, open = false, onClose }: Props) => { - const classes = useStyles(); - - return ( - - - Logs - - - - - -
- -
-
-
- ); -}; diff --git a/plugins/scaffolder/src/components/JobStage/index.ts b/plugins/scaffolder/src/components/JobStage/index.ts deleted file mode 100644 index d6d3534a88..0000000000 --- a/plugins/scaffolder/src/components/JobStage/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 { JobStage } from './JobStage'; From 8547d8d5ef1f153c342ad4a863f6b093ac6ad9b3 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 11 Feb 2021 09:25:03 +0100 Subject: [PATCH 020/270] cleanup imports --- .../src/components/TaskPage/TaskPage.tsx | 31 ++----------------- .../components/TemplatePage/TemplatePage.tsx | 3 +- 2 files changed, 4 insertions(+), 30 deletions(-) diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index d9a6f86703..2e7781bc92 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -16,30 +16,14 @@ import { Page, Header, Lifecycle, Content } from '@backstage/core'; import React, { useState, useEffect, memo, useMemo } from 'react'; -import { - makeStyles, - Theme, - createStyles, - withStyles, -} from '@material-ui/core/styles'; +import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'; import Stepper from '@material-ui/core/Stepper'; import Step from '@material-ui/core/Step'; import StepLabel from '@material-ui/core/StepLabel'; -import StepContent from '@material-ui/core/StepContent'; -import StepConnector from '@material-ui/core/StepConnector'; import Grid from '@material-ui/core/Grid'; -import Button from '@material-ui/core/Button'; -import Paper from '@material-ui/core/Paper'; -import clsx from 'clsx'; -import Check from '@material-ui/icons/Check'; -import Cancel from '@material-ui/icons/Cancel'; import Typography from '@material-ui/core/Typography'; import { useParams } from 'react-router'; -import { - useTaskEventStream, - TaskStream, - Status, -} from '../hooks/useEventStream'; +import { useTaskEventStream } from '../hooks/useEventStream'; import LazyLog from 'react-lazylog/build/LazyLog'; import { StepButton, StepIconProps } from '@material-ui/core'; @@ -95,7 +79,7 @@ export const TaskStatusStepper = memo( - {step.name} + {step.name} @@ -115,15 +99,6 @@ const TaskLogger = memo(({ log }: { log: string }) => { ); }); -const TaskActionsBar = () => { - return ( - <> - - - - ); -}; - export const TaskPage = () => { const [userSelectedStepId, setUserSelectedStepId] = useState< string | undefined diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 85f3d955ac..7c25829e5e 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -29,12 +29,11 @@ import { LinearProgress } from '@material-ui/core'; import { IChangeEvent } from '@rjsf/core'; import parseGitUrl from 'git-url-parse'; import React, { useCallback, useState } from 'react'; -import { Navigate } from 'react-router'; +import { Navigate, useNavigate } from 'react-router'; import { useParams } from 'react-router-dom'; import { useAsync } from 'react-use'; import { scaffolderApiRef } from '../../api'; import { rootRoute, taskRoute } from '../../routes'; -import { useNavigate } from 'react-router'; import { MultistepJsonForm } from '../MultistepJsonForm'; const useTemplate = ( From 269f1eec60b954c0277e0ef3c08896a696602c84 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 11 Feb 2021 10:34:40 +0100 Subject: [PATCH 021/270] Fix task status info Co-authored-by: blam --- plugins/scaffolder/package.json | 3 + .../src/components/TaskPage/TaskPage.tsx | 64 +++++++++++++++++-- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index b332c37c28..a97d339ba8 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -43,6 +43,8 @@ "classnames": "^2.2.6", "clsx": "^1.1.1", "git-url-parse": "^11.4.4", + "humanize-duration": "^3.25.1", + "luxon": "^1.25.0", "moment": "^2.26.0", "react": "^16.13.1", "react-dom": "^16.13.1", @@ -61,6 +63,7 @@ "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", + "@types/humanize-duration": "^3.18.1", "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "cross-fetch": "^3.0.6", diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index 2e7781bc92..01aa110582 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -26,6 +26,12 @@ import { useParams } from 'react-router'; import { useTaskEventStream } from '../hooks/useEventStream'; import LazyLog from 'react-lazylog/build/LazyLog'; import { StepButton, StepIconProps } from '@material-ui/core'; +import { Status } from '../../types'; +import { DateTime, Interval } from 'luxon'; +import { useInterval } from 'react-use'; + +// typings are wrong for this library, so fallback to not parsing types. +const humanizeDuration = require('humanize-duration'); const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -42,13 +48,48 @@ const useStyles = makeStyles((theme: Theme) => resetContainer: { padding: theme.spacing(3), }, + labelWrapper: { + display: 'flex', + flex: 1, + flexDirection: 'row', + justifyContent: 'space-between', + }, + stepWrapper: { + width: '100%', + }, }), ); -type Steps = { +type TaskS = { id: string; name: string; status: Status; + startedAt?: string; + endedAt?: string; +}; + +const StepTimeTicker = ({ step }: { step: TaskS }) => { + const [time, setTime] = useState(''); + + useInterval(() => { + if (!step.startedAt) { + setTime(''); + return; + } + + const end = step.endedAt + ? DateTime.fromISO(step.endedAt) + : DateTime.local(); + + const startedAt = DateTime.fromISO(step.startedAt); + const formatted = Interval.fromDateTimes(startedAt, end) + .toDuration() + .valueOf(); + + setTime(humanizeDuration(formatted, { round: true })); + }, 1000); + + return {time}; }; export const TaskStatusStepper = memo( @@ -57,7 +98,7 @@ export const TaskStatusStepper = memo( currentStepId, onUserStepChange, }: { - steps: Steps[]; + steps: TaskS[]; currentStepId: string | undefined; onUserStepChange: (id: string) => void; }) => { @@ -78,8 +119,12 @@ export const TaskStatusStepper = memo( onUserStepChange(step.id)}> - {step.name} +
+ {step.name} + +
@@ -108,6 +153,7 @@ export const TaskPage = () => { ); const { taskId } = useParams(); const taskStream = useTaskEventStream(taskId); + const completed = taskStream.completed; const steps = useMemo( () => @@ -121,8 +167,14 @@ export const TaskPage = () => { const activeStep = steps.find(step => ['failed', 'processing'].includes(step.status), ); + + if (completed) { + setLastActiveStepId(steps[steps.length - 1]?.id); + return; + } + setLastActiveStepId(activeStep?.id); - }, [steps]); + }, [steps, completed]); const currentStepId = userSelectedStepId ?? lastActiveStepId; @@ -151,14 +203,14 @@ export const TaskPage = () => { /> - + - + From 499f85ba4fc2fe7322cfb30c8beff45e65b94f20 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 11 Feb 2021 10:36:47 +0100 Subject: [PATCH 022/270] Fix name --- plugins/scaffolder/src/components/TaskPage/TaskPage.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index 01aa110582..240f0be189 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -60,7 +60,7 @@ const useStyles = makeStyles((theme: Theme) => }), ); -type TaskS = { +type TaskStep = { id: string; name: string; status: Status; @@ -68,7 +68,7 @@ type TaskS = { endedAt?: string; }; -const StepTimeTicker = ({ step }: { step: TaskS }) => { +const StepTimeTicker = ({ step }: { step: TaskStep }) => { const [time, setTime] = useState(''); useInterval(() => { @@ -98,7 +98,7 @@ export const TaskStatusStepper = memo( currentStepId, onUserStepChange, }: { - steps: TaskS[]; + steps: TaskStep[]; currentStepId: string | undefined; onUserStepChange: (id: string) => void; }) => { From c2025990084606c89c418938d5897f201a47dc82 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 11 Feb 2021 11:26:26 +0100 Subject: [PATCH 023/270] Add progress icons --- .../src/scaffolder/tasks/TemplateConverter.ts | 4 +- .../src/components/TaskPage/TaskPage.tsx | 66 +++++++++++++++++-- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts index 69788238cc..9c5df52c5e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts @@ -18,7 +18,7 @@ import { resolve as resolvePath } from 'path'; import { JsonValue } from '@backstage/config'; import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { Logger } from 'winston'; -import type { Writable } from 'stream'; +import { Writable } from 'stream'; import { TaskSpec } from './types'; import { ConflictError, NotFoundError } from '@backstage/backend-common'; @@ -69,7 +69,7 @@ export function templateEntityToSpec( steps.push({ id: 'publish', - name: 'Publishing', + name: 'Publish', action: 'legacy:publish', parameters: { values, diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index 240f0be189..792e5ba3e3 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -25,10 +25,14 @@ import Typography from '@material-ui/core/Typography'; import { useParams } from 'react-router'; import { useTaskEventStream } from '../hooks/useEventStream'; import LazyLog from 'react-lazylog/build/LazyLog'; -import { StepButton, StepIconProps } from '@material-ui/core'; +import { CircularProgress, StepButton, StepIconProps } from '@material-ui/core'; import { Status } from '../../types'; import { DateTime, Interval } from 'luxon'; import { useInterval } from 'react-use'; +import clsx from 'clsx'; +import Check from '@material-ui/icons/Check'; +import Cancel from '@material-ui/icons/Cancel'; +import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord'; // typings are wrong for this library, so fallback to not parsing types. const humanizeDuration = require('humanize-duration'); @@ -92,6 +96,54 @@ const StepTimeTicker = ({ step }: { step: TaskStep }) => { return {time}; }; +const useQontoStepIconStyles = makeStyles({ + root: { + color: '#eaeaf0', + display: 'flex', + height: 22, + alignItems: 'center', + }, + active: { + color: 'gray', + }, + completed: { + color: 'green', + }, + error: { + color: 'red', + }, +}); + +function TaskStepIconComponent(props: StepIconProps) { + const classes = useQontoStepIconStyles(); + const { active, completed, error } = props; + + const getMiddle = () => { + if (active) { + return ; + } + if (completed) { + return ; + } + if (error) { + return ; + } + return ; + }; + + return ( +
+ {getMiddle()} +
+ ); +} + export const TaskStatusStepper = memo( ({ steps, @@ -114,11 +166,17 @@ export const TaskStatusStepper = memo( {steps.map((step, index) => { const isCompleted = step.status === 'completed'; const isFailed = step.status === 'failed'; + const isActive = step.status === 'processing'; return ( onUserStepChange(step.id)}>
@@ -203,14 +261,14 @@ export const TaskPage = () => { /> - + - + From 5aa002fb201389e07986555088400914ebaaef93 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 11 Feb 2021 14:02:00 +0100 Subject: [PATCH 024/270] Add support for output templating MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- plugins/scaffolder-backend/package.json | 5 +- .../src/scaffolder/tasks/StorageTaskBroker.ts | 10 +++- .../src/scaffolder/tasks/TaskWorker.ts | 50 ++++++++++++++++--- .../src/scaffolder/tasks/TemplateConverter.ts | 8 ++- .../src/scaffolder/tasks/types.ts | 3 +- 5 files changed, 64 insertions(+), 12 deletions(-) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index ecce161c8e..252f498bd6 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -50,6 +50,7 @@ "fs-extra": "^9.0.0", "git-url-parse": "^11.4.4", "globby": "^11.0.0", + "handlebars": "^4.7.6", "helmet": "^4.0.0", "isomorphic-git": "^1.8.0", "jsonschema": "^1.2.6", @@ -66,9 +67,9 @@ "@types/mock-fs": "^4.13.0", "@types/supertest": "^2.0.8", "mock-fs": "^4.13.0", + "msw": "^0.21.2", "supertest": "^4.0.2", - "yaml": "^1.10.0", - "msw": "^0.21.2" + "yaml": "^1.10.0" }, "files": [ "dist", diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index d3b2c098c7..fb1f4ab422 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -63,11 +63,17 @@ export class TaskAgent implements Task { }); } - async complete(result: CompletedTaskState): Promise { + async complete( + result: CompletedTaskState, + metadata?: JsonObject, + ): Promise { await this.storage.completeTask({ taskId: this.state.taskId, status: result === 'failed' ? 'failed' : 'completed', - eventBody: { message: `Run completed with status: ${result}` }, + eventBody: { + message: `Run completed with status: ${result}`, + ...metadata, + }, }); this.isDone = true; if (this.heartbeatTimeoutId) { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 66d1331c5c..51260aef7d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -22,6 +22,7 @@ import { TaskBroker, Task } from './types'; import fs from 'fs-extra'; import path from 'path'; import { TemplateActionRegistry } from './TemplateConverter'; +import * as handlebars from 'handlebars'; type Options = { logger: Logger; @@ -55,7 +56,11 @@ export class TaskWorker { `Starting up work with ${task.spec.steps.length} steps`, ); - const outputs: { [name: string]: JsonValue } = {}; + const templateCtx: { + steps: { + [stepName: string]: { output: { [outputName: string]: JsonValue } }; + }; + } = { steps: {} }; for (const step of task.spec.steps) { const metadata = { stepId: step.id }; @@ -87,8 +92,24 @@ export class TaskWorker { throw new Error(`Action '${step.action}' does not exist`); } - // TODO: substitute any placeholders with output from previous steps - const parameters = step.parameters!; + const parameters: { [name: string]: JsonValue } = {}; + for (const [name, maybeTemplateStr] of Object.entries( + step.parameters ?? {}, + )) { + if (typeof maybeTemplateStr === 'string') { + const value = handlebars.compile(maybeTemplateStr, { + noEscape: true, + strict: true, + data: false, + preventIndent: true, + })(templateCtx); + parameters[name] = value; + } else { + parameters[name] = maybeTemplateStr; + } + } + + const stepOutputs: { [name: string]: JsonValue } = {}; await action.handler({ logger, @@ -96,10 +117,12 @@ export class TaskWorker { parameters, workspacePath, output(name: string, value: JsonValue) { - outputs[name] = value; + stepOutputs[name] = value; }, }); + templateCtx.steps[step.id] = { output: stepOutputs }; + await task.emitLog(`Finished step ${step.name}`, { ...metadata, status: 'completed', @@ -112,9 +135,24 @@ export class TaskWorker { throw error; } } - await task.complete('completed'); + + const output = Object.fromEntries( + Object.entries(task.spec.output).map(([name, templateStr]) => { + const value = handlebars.compile(templateStr, { + noEscape: true, + strict: true, + data: false, + preventIndent: true, + })(templateCtx); + return [name, value]; + }), + ); + + await task.complete('completed', { output }); } catch (error) { - await task.complete('failed'); + await task.complete('failed', { + error: { name: error.name, message: error.message }, + }); } } } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts index 9c5df52c5e..48f31f73f2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts @@ -76,7 +76,13 @@ export function templateEntityToSpec( }, }); - return { steps }; + return { + steps, + output: { + remoteUrl: '{{ steps.publish.output.remoteUrl }}', + catalogInfoUrl: '{{ steps.publish.output.catalogInfoUrl }}', + }, + }; } type ActionContext = { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 783528dbdf..ae18a59e41 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -49,6 +49,7 @@ export type TaskSpec = { action: string; parameters?: { [name: string]: JsonValue }; }>; + output: { [name: string]: string }; }; export type DispatchResult = { @@ -59,7 +60,7 @@ export interface Task { spec: TaskSpec; done: boolean; emitLog(message: string, metadata?: JsonValue): Promise; - complete(result: CompletedTaskState): Promise; + complete(result: CompletedTaskState, metadata?: JsonValue): Promise; getWorkspaceName(): Promise; } From 452602094f23e5bb0eb150e649c9661066d2f75e Mon Sep 17 00:00:00 2001 From: Oscar Hernandez Date: Thu, 11 Feb 2021 13:02:38 -0600 Subject: [PATCH 025/270] Scaffolder: PR comments --- .../src/components/ScaffolderPage/ScaffolderPage.tsx | 3 +-- plugins/scaffolder/src/hooks/useStarredEntities.test.tsx | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index f3ccd1d440..74582d0762 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -167,8 +167,7 @@ export const ScaffolderPageContents = () => { )} {error && ( - - Oops! Something went wrong loading the templates:{' '} + {error.message} )} diff --git a/plugins/scaffolder/src/hooks/useStarredEntities.test.tsx b/plugins/scaffolder/src/hooks/useStarredEntities.test.tsx index 78d2c4a58f..f1a8df1f32 100644 --- a/plugins/scaffolder/src/hooks/useStarredEntities.test.tsx +++ b/plugins/scaffolder/src/hooks/useStarredEntities.test.tsx @@ -65,7 +65,7 @@ describe('useStarredEntities', () => { expect(result.current.starredEntities.size).toBe(0); }); - it('should return a set with the current items when there is items in storage', async () => { + it('should return a set with the current items when there are items in storage', async () => { const expectedIds = ['i', 'am', 'some', 'test', 'ids']; const store = mockStorage?.forBucket('settings'); await store?.set('starredEntities', expectedIds); @@ -95,7 +95,7 @@ describe('useStarredEntities', () => { expect(result.current.isStarredEntity(mockEntity)).toBeTruthy(); }); - it('should write new entries to the local store when adding a togglging entity', async () => { + it('should write new entries to the local store when adding a toggling entity', async () => { const { result } = renderHook(() => useStarredEntities(), { wrapper }); act(() => { From 2499f6cdefc52f7c781fc5c1ecbe7b8c3ec31be2 Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Thu, 11 Feb 2021 11:00:24 -0800 Subject: [PATCH 026/270] feat: add support for assuming role in plugins that use AWS Signed-off-by: Jonah Back --- .changeset/sixty-chicken-ring.md | 7 ++ .../src/stages/publish/awsS3.ts | 33 ++++++-- plugins/catalog-backend/config.d.ts | 13 ++++ plugins/catalog-backend/package-lock.json | 76 +++++++++++++++++++ ...sOrganizationCloudAccountProcessor.test.ts | 6 +- .../AwsOrganizationCloudAccountProcessor.ts | 41 +++++++++- .../processors/awsOrganization/config.test.ts | 37 +++++++++ .../processors/awsOrganization/config.ts | 44 +++++++++++ plugins/techdocs/config.d.ts | 6 ++ 9 files changed, 253 insertions(+), 10 deletions(-) create mode 100644 .changeset/sixty-chicken-ring.md create mode 100644 plugins/catalog-backend/package-lock.json create mode 100644 plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.test.ts create mode 100644 plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.ts diff --git a/.changeset/sixty-chicken-ring.md b/.changeset/sixty-chicken-ring.md new file mode 100644 index 0000000000..0fff26d6f9 --- /dev/null +++ b/.changeset/sixty-chicken-ring.md @@ -0,0 +1,7 @@ +--- +'@backstage/techdocs-common': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-techdocs': patch +--- + +Add support for assuming role in AWS integrations diff --git a/packages/techdocs-common/src/stages/publish/awsS3.ts b/packages/techdocs-common/src/stages/publish/awsS3.ts index 8eaf03eb2f..b29d0dfb58 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.ts @@ -15,7 +15,7 @@ */ import path from 'path'; import express from 'express'; -import aws from 'aws-sdk'; +import aws, { Credentials } from 'aws-sdk'; import { ManagedUpload } from 'aws-sdk/clients/s3'; import { Logger } from 'winston'; import { Entity, EntityName } from '@backstage/catalog-model'; @@ -26,6 +26,7 @@ import fs from 'fs-extra'; import { Readable } from 'stream'; import JSON5 from 'json5'; import createLimiter from 'p-limit'; +import { CredentialsOptions } from 'aws-sdk/lib/credentials'; const streamToBuffer = (stream: Readable): Promise => { return new Promise((resolve, reject) => { @@ -61,9 +62,30 @@ export class AwsS3Publish implements PublisherBase { ); let accessKeyId = undefined; let secretAccessKey = undefined; + let awsCredentials: + | Credentials + | CredentialsOptions + | undefined = undefined; if (credentials) { - accessKeyId = credentials.getOptionalString('accessKeyId'); - secretAccessKey = credentials.getOptionalString('secretAccessKey'); + const roleArn = credentials.getOptionalString('roleArn'); + if (roleArn && aws.config.credentials instanceof Credentials) { + awsCredentials = new aws.ChainableTemporaryCredentials({ + masterCredentials: aws.config.credentials as Credentials, + params: { + RoleSessionName: 'backstage-aws-organization-processor', + RoleArn: roleArn, + }, + }); + } else { + accessKeyId = credentials.getOptionalString('accessKeyId'); + secretAccessKey = credentials.getOptionalString('secretAccessKey'); + if (accessKeyId && secretAccessKey) { + awsCredentials = { + accessKeyId, + secretAccessKey, + }; + } + } } // AWS Region is an optional config. If missing, default AWS env variable AWS_REGION @@ -76,10 +98,7 @@ export class AwsS3Publish implements PublisherBase { ...(credentials && accessKeyId && secretAccessKey && { - credentials: { - accessKeyId, - secretAccessKey, - }, + credentials: awsCredentials, }), ...(region && { region, diff --git a/plugins/catalog-backend/config.d.ts b/plugins/catalog-backend/config.d.ts index 7877f34dcf..b85846af2a 100644 --- a/plugins/catalog-backend/config.d.ts +++ b/plugins/catalog-backend/config.d.ts @@ -334,6 +334,19 @@ export interface Config { }>; }; + /** + * AwsOrganizationCloudAccountProcessor configuration + */ + awsOrganization?: { + providers: Array<{ + /** + * The role to be assumed by this processor + * + */ + roleArn?: string; + }>; + }; + /** * MicrosoftGraphOrgReaderProcessor configuration */ diff --git a/plugins/catalog-backend/package-lock.json b/plugins/catalog-backend/package-lock.json new file mode 100644 index 0000000000..a0474f89fc --- /dev/null +++ b/plugins/catalog-backend/package-lock.json @@ -0,0 +1,76 @@ +{ + "name": "@backstage/plugin-catalog-backend", + "version": "0.5.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "ts-node": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + }, + "dependencies": { + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } + } + } +} diff --git a/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.test.ts index ba163fa0c3..596e62748f 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.test.ts @@ -15,10 +15,14 @@ */ import { AwsOrganizationCloudAccountProcessor } from './AwsOrganizationCloudAccountProcessor'; +import * as winston from 'winston'; describe('AwsOrganizationCloudAccountProcessor', () => { describe('readLocation', () => { - const processor = new AwsOrganizationCloudAccountProcessor(); + const processor = new AwsOrganizationCloudAccountProcessor({ + providers: [], + logger: winston.createLogger(), + }); const location = { type: 'aws-cloud-accounts', target: '' }; const emit = jest.fn(); const listAccounts = jest.fn(); diff --git a/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.ts index 0e988e7307..83fd89801f 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.ts @@ -14,11 +14,17 @@ * limitations under the License. */ import { LocationSpec, ResourceEntityV1alpha1 } from '@backstage/catalog-model'; -import AWS, { Organizations } from 'aws-sdk'; +import AWS, { Credentials, Organizations } from 'aws-sdk'; import { Account, ListAccountsResponse } from 'aws-sdk/clients/organizations'; import * as results from './results'; import { CatalogProcessor, CatalogProcessorEmit } from './types'; +import { Config } from '../../../../../packages/config/src'; +import { Logger } from 'winston'; +import { + AwsOrganizationProviderConfig, + readAwsOrganizationConfig, +} from './awsOrganization/config'; const AWS_ORGANIZATION_REGION = 'us-east-1'; const LOCATION_TYPE = 'aws-cloud-accounts'; @@ -33,9 +39,40 @@ const ORGANIZATION_ANNOTATION: string = 'amazonaws.com/organization-id'; * If custom authentication is needed, it can be achieved by configuring the global AWS.credentials object. */ export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor { + logger: Logger; organizations: Organizations; - constructor() { + providers: AwsOrganizationProviderConfig[]; + + static fromConfig(config: Config, options: { logger: Logger }) { + const c = config.getOptionalConfig('catalog.processors.awsOrganization'); + return new AwsOrganizationCloudAccountProcessor({ + ...options, + providers: c ? readAwsOrganizationConfig(c) : [], + }); + } + + constructor(options: { + providers: AwsOrganizationProviderConfig[]; + logger: Logger; + }) { + this.providers = options.providers; + this.logger = options.logger; + let credentials = undefined; + if ( + this.providers.length > 0 && + this.providers[0].roleArn !== undefined && + AWS.config.credentials instanceof Credentials + ) { + credentials = new AWS.ChainableTemporaryCredentials({ + masterCredentials: AWS.config.credentials as Credentials, + params: { + RoleSessionName: 'backstage-aws-organization-processor', + RoleArn: this.providers[0].roleArn, + }, + }); + } this.organizations = new AWS.Organizations({ + credentials, region: AWS_ORGANIZATION_REGION, }); // Only available in us-east-1 } diff --git a/plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.test.ts b/plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.test.ts new file mode 100644 index 0000000000..571d19c829 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.test.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { ConfigReader } from '@backstage/config'; +import { readAwsOrganizationConfig } from './config'; + +describe('readAwsOrganizationConfig', () => { + it('applies all of the defaults', () => { + const config = { + providers: [ + { + roleArn: 'aws::arn::foo', + }, + ], + }; + const actual = readAwsOrganizationConfig(new ConfigReader(config)); + const expected = [ + { + roleArn: 'aws::arn::foo', + }, + ]; + expect(actual).toEqual(expected); + }); +}); diff --git a/plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.ts b/plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.ts new file mode 100644 index 0000000000..120079d679 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.ts @@ -0,0 +1,44 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { Config } from '@backstage/config'; + +/** + * The configuration parameters for a single AWS Organization Processor + */ +export type AwsOrganizationProviderConfig = { + /** + * The role to assume for the processor. + */ + roleArn?: string; +}; + +export function readAwsOrganizationConfig( + config: Config, +): AwsOrganizationProviderConfig[] { + const providers: AwsOrganizationProviderConfig[] = []; + const providerConfigs = config.getOptionalConfigArray('providers') ?? []; + + for (const providerConfig of providerConfigs) { + const roleArn = providerConfig.getOptionalString('roleArn'); + + providers.push({ + roleArn, + }); + } + + return providers; +} diff --git a/plugins/techdocs/config.d.ts b/plugins/techdocs/config.d.ts index 1104d75237..d0c2ee2e6f 100644 --- a/plugins/techdocs/config.d.ts +++ b/plugins/techdocs/config.d.ts @@ -86,6 +86,12 @@ export interface Config { * @visibility secret */ secretAccessKey: string; + /** + * ARN of role to be assumed + * attr: 'roleArn' - accepts a string value + * @visibility secret + */ + roleArn: string; }; /** * (Required) Cloud Storage Bucket Name From 6237522a1f1f31f67d0be70182e7b6354267c627 Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Thu, 11 Feb 2021 11:45:23 -0800 Subject: [PATCH 027/270] remove accidental package-lock.json --- plugins/catalog-backend/package-lock.json | 76 ----------------------- 1 file changed, 76 deletions(-) delete mode 100644 plugins/catalog-backend/package-lock.json diff --git a/plugins/catalog-backend/package-lock.json b/plugins/catalog-backend/package-lock.json deleted file mode 100644 index a0474f89fc..0000000000 --- a/plugins/catalog-backend/package-lock.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "name": "@backstage/plugin-catalog-backend", - "version": "0.5.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "requires": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "dependencies": { - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - } - } - } - } -} From 86c391d260bd2aa888d8c19d31c8df90707e9625 Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Thu, 11 Feb 2021 12:27:37 -0800 Subject: [PATCH 028/270] Make roleArn optional Co-authored-by: Himanshu Mishra --- plugins/techdocs/config.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/techdocs/config.d.ts b/plugins/techdocs/config.d.ts index d0c2ee2e6f..da30578c99 100644 --- a/plugins/techdocs/config.d.ts +++ b/plugins/techdocs/config.d.ts @@ -91,7 +91,7 @@ export interface Config { * attr: 'roleArn' - accepts a string value * @visibility secret */ - roleArn: string; + roleArn?: string; }; /** * (Required) Cloud Storage Bucket Name From 58fdcb6a3033307c445540e49bbaa5ebf018b491 Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Thu, 11 Feb 2021 12:48:23 -0800 Subject: [PATCH 029/270] add docs around assuming role for techdocs storage --- docs/features/techdocs/using-cloud-storage.md | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md index de2d505c87..084279b09b 100644 --- a/docs/features/techdocs/using-cloud-storage.md +++ b/docs/features/techdocs/using-cloud-storage.md @@ -149,7 +149,7 @@ If the environment variables - `AWS_REGION` are set and can be used to access the bucket you created in step 2, they will be -used by the AWS SDK v3 Node.js client for authentication. +used by the AWS SDK v2 Node.js client for authentication. [Refer to the official documentation.](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-environment.html) If the environment variables are missing, the AWS SDK tries to read the @@ -181,7 +181,7 @@ techdocs: ``` Refer to the -[official AWS documentation for obtaining the credentials](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/getting-your-credentials.html). +[official AWS documentation for obtaining the credentials](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/getting-your-credentials.html). Note: If you are using Amazon EC2 instance to deploy Backstage, you do not need to obtain the access keys separately. They can be made available in the @@ -189,6 +189,27 @@ environment automatically by defining appropriate IAM role with access to the bucket. Read more [here](https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html#use-roles). +**3c. Authentication using an assumed role** Users with multiple AWS accounts +may want to use a role for S3 storage that is in a different AWS account. Using +the `roleArn` parameter as seen below, you can instruct the TechDocs publisher +to assume a role before accessing S3. + +```yaml +techdocs: + publisher: + type: 'awsS3' + awsS3: + bucketName: 'name-of-techdocs-storage-bucket' + region: + $env: AWS_REGION + credentials: + roleArn: arn:aws:iam::123456789012:role/my-backstage-role +``` + +Note: Assuming a role requires that primary credentials are already configured +at `AWS.config.credentials`. Read more about assuming roles +[here](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html) + **4. That's it!** Your Backstage app is now ready to use AWS S3 for TechDocs, to store and read From 5cd564dc0d562e8b597af69bcb24676721469c84 Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Thu, 11 Feb 2021 12:49:48 -0800 Subject: [PATCH 030/270] cleanup conditionals on s3 client creation --- packages/techdocs-common/src/stages/publish/awsS3.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/awsS3.ts b/packages/techdocs-common/src/stages/publish/awsS3.ts index b29d0dfb58..0a78af7135 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.ts @@ -95,11 +95,7 @@ export class AwsS3Publish implements PublisherBase { const region = config.getOptionalString('techdocs.publisher.awsS3.region'); const storageClient = new aws.S3({ - ...(credentials && - accessKeyId && - secretAccessKey && { - credentials: awsCredentials, - }), + credentials, ...(region && { region, }), From 0d8b3253a48914f24a00675d3556285ac6257938 Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Thu, 11 Feb 2021 14:23:37 -0800 Subject: [PATCH 031/270] Update docs/features/techdocs/using-cloud-storage.md Co-authored-by: Himanshu Mishra --- docs/features/techdocs/using-cloud-storage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md index 084279b09b..a51a586813 100644 --- a/docs/features/techdocs/using-cloud-storage.md +++ b/docs/features/techdocs/using-cloud-storage.md @@ -150,7 +150,7 @@ If the environment variables are set and can be used to access the bucket you created in step 2, they will be used by the AWS SDK v2 Node.js client for authentication. -[Refer to the official documentation.](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-environment.html) +[Refer to the official documentation.](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html) If the environment variables are missing, the AWS SDK tries to read the `~/.aws/credentials` file for credentials. From 9c80eca8d5e15582fad70a94fd6bd90d3338e054 Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Thu, 11 Feb 2021 14:24:49 -0800 Subject: [PATCH 032/270] Update docs/features/techdocs/using-cloud-storage.md Co-authored-by: Himanshu Mishra --- docs/features/techdocs/using-cloud-storage.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md index a51a586813..3c648e5798 100644 --- a/docs/features/techdocs/using-cloud-storage.md +++ b/docs/features/techdocs/using-cloud-storage.md @@ -207,8 +207,7 @@ techdocs: ``` Note: Assuming a role requires that primary credentials are already configured -at `AWS.config.credentials`. Read more about assuming roles -[here](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html) +at `AWS.config.credentials`. Read more about [assuming roles in AWS](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html). **4. That's it!** From 0e19926cf49421028a9efe8e5238e121ad6c0223 Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Thu, 11 Feb 2021 14:29:38 -0800 Subject: [PATCH 033/270] fix ts errors --- .../src/stages/publish/awsS3.ts | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/awsS3.ts b/packages/techdocs-common/src/stages/publish/awsS3.ts index 0a78af7135..28c545c420 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.ts @@ -57,19 +57,16 @@ export class AwsS3Publish implements PublisherBase { // or AWS shared credentials file at ~/.aws/credentials will be used to authenticate // https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-environment.html // https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-shared.html - const credentials = config.getOptionalConfig( + const credentialsConfig = config.getOptionalConfig( 'techdocs.publisher.awsS3.credentials', ); let accessKeyId = undefined; let secretAccessKey = undefined; - let awsCredentials: - | Credentials - | CredentialsOptions - | undefined = undefined; - if (credentials) { - const roleArn = credentials.getOptionalString('roleArn'); + let credentials: Credentials | CredentialsOptions | undefined = undefined; + if (credentialsConfig) { + const roleArn = credentialsConfig.getOptionalString('roleArn'); if (roleArn && aws.config.credentials instanceof Credentials) { - awsCredentials = new aws.ChainableTemporaryCredentials({ + credentials = new aws.ChainableTemporaryCredentials({ masterCredentials: aws.config.credentials as Credentials, params: { RoleSessionName: 'backstage-aws-organization-processor', @@ -77,10 +74,12 @@ export class AwsS3Publish implements PublisherBase { }, }); } else { - accessKeyId = credentials.getOptionalString('accessKeyId'); - secretAccessKey = credentials.getOptionalString('secretAccessKey'); + accessKeyId = credentialsConfig.getOptionalString('accessKeyId'); + secretAccessKey = credentialsConfig.getOptionalString( + 'secretAccessKey', + ); if (accessKeyId && secretAccessKey) { - awsCredentials = { + credentials = { accessKeyId, secretAccessKey, }; From 05e169464c62446a44de6e81789d6f4e52678214 Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Thu, 11 Feb 2021 15:32:52 -0800 Subject: [PATCH 034/270] run prettier fix --- docs/features/techdocs/using-cloud-storage.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md index 3c648e5798..72db05ec1c 100644 --- a/docs/features/techdocs/using-cloud-storage.md +++ b/docs/features/techdocs/using-cloud-storage.md @@ -207,7 +207,8 @@ techdocs: ``` Note: Assuming a role requires that primary credentials are already configured -at `AWS.config.credentials`. Read more about [assuming roles in AWS](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html). +at `AWS.config.credentials`. Read more about +[assuming roles in AWS](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html). **4. That's it!** From 1ae69b890a9c051629e039615dc28bd867b682c4 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 12 Feb 2021 08:38:20 +0100 Subject: [PATCH 035/270] scaffolder: Add registration step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- packages/backend/src/plugins/scaffolder.ts | 3 ++ .../src/scaffolder/stages/legacy.ts | 29 ++++++++++++++++++- .../src/scaffolder/tasks/TemplateConverter.ts | 9 ++++++ .../scaffolder-backend/src/service/router.ts | 6 ++-- plugins/scaffolder/package.json | 4 +-- 5 files changed, 46 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts index 723165ff82..ae4b22759b 100644 --- a/packages/backend/src/plugins/scaffolder.ts +++ b/packages/backend/src/plugins/scaffolder.ts @@ -26,6 +26,7 @@ import { import { SingleHostDiscovery } from '@backstage/backend-common'; import type { PluginEnvironment } from '../types'; import Docker from 'dockerode'; +import { CatalogClient } from '@backstage/catalog-client'; export default async function createPlugin({ logger, @@ -46,6 +47,7 @@ export default async function createPlugin({ const discovery = SingleHostDiscovery.fromConfig(config); const entityClient = new CatalogEntityClient({ discovery }); + const catalogClient = new CatalogClient({ discoveryApi: discovery }); return await createRouter({ preparers, @@ -56,5 +58,6 @@ export default async function createPlugin({ dockerClient, entityClient, database, + catalogClient, }); } diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts b/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts index f151780ba9..5cead565f1 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts @@ -19,19 +19,28 @@ import { FilePreparer, PreparerBuilder } from './prepare'; import Docker from 'dockerode'; import { TemplaterBuilder, TemplaterValues } from './templater'; import { PublisherBuilder } from './publish'; +import { CatalogApi } from '@backstage/catalog-client'; +import { getEntityName } from '@backstage/catalog-model'; type Options = { dockerClient: Docker; preparers: PreparerBuilder; templaters: TemplaterBuilder; publishers: PublisherBuilder; + catalogClient: CatalogApi; }; export function registerLegacyActions( registry: TemplateActionRegistry, options: Options, ) { - const { dockerClient, preparers, templaters, publishers } = options; + const { + dockerClient, + preparers, + templaters, + publishers, + catalogClient, + } = options; registry.register({ id: 'legacy:prepare', @@ -107,4 +116,22 @@ export function registerLegacyActions( } }, }); + + registry.register({ + id: 'catalog:register', + async handler(ctx) { + const { logger } = ctx; + const { catalogInfoUrl } = ctx.parameters; // TODO update schema + + logger.info(`Registering ${catalogInfoUrl} in the catalog`); + const result = await catalogClient.addLocation({ + type: 'url', + target: catalogInfoUrl as string, + }); + if (result.entities.length === 1) { + const { kind, name, namespace } = getEntityName(result.entities[0]); + ctx.output('entityRef', `${kind}:${namespace}/${name}`); + } + }, + }); } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts index 48f31f73f2..20e8886884 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts @@ -76,6 +76,15 @@ export function templateEntityToSpec( }, }); + steps.push({ + id: 'register', + name: 'Register', + action: 'catalog:register', + parameters: { + catalogInfoUrl: '{{ steps.publish.output.catalogInfoUrl }}', + }, + }); + return { steps, output: { diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 959e9e42c5..26be55d898 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -48,6 +48,7 @@ import { NotFoundError, PluginDatabaseManager, } from '@backstage/backend-common'; +import { CatalogClient } from '@backstage/catalog-client'; export interface RouterOptions { preparers: PreparerBuilder; @@ -59,6 +60,7 @@ export interface RouterOptions { dockerClient: Docker; entityClient: CatalogEntityClient; database: PluginDatabaseManager; + catalogClient: CatalogClient; } export async function createRouter( @@ -76,6 +78,7 @@ export async function createRouter( dockerClient, entityClient, database, + catalogClient, } = options; const logger = parentLogger.child({ plugin: 'scaffolder' }); @@ -99,6 +102,7 @@ export async function createRouter( preparers, publishers, templaters, + catalogClient, }); worker.start(); @@ -254,9 +258,7 @@ export async function createRouter( }) .get('/v2/tasks/:taskId', async (req, res) => { const { taskId } = req.params; - console.warn('getting task'); const task = await taskBroker.get(taskId); - console.warn('got task', task); if (!task) { throw new NotFoundError(`task with id ${taskId} does not exist`); } diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 6f55d0114e..f602f01b5b 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -30,9 +30,10 @@ "clean": "backstage-cli clean" }, "dependencies": { + "@backstage/catalog-client": "^0.3.6", "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.1", "@backstage/config": "^0.1.2", + "@backstage/core": "^0.6.1", "@backstage/plugin-catalog-react": "^0.0.3", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", @@ -60,7 +61,6 @@ "@backstage/cli": "^0.6.0", "@backstage/dev-utils": "^0.1.10", "@backstage/test-utils": "^0.1.7", - "@backstage/catalog-client": "^0.3.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", From d4c77f931654e7193b3d6662e1df99d428a6b773 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 12 Feb 2021 09:46:33 +0100 Subject: [PATCH 036/270] Scaffolder: Initial 404 view --- .../src/components/TaskPage/TaskPage.tsx | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index 792e5ba3e3..e2a19f9f63 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -248,6 +248,11 @@ export const TaskPage = () => { return log.join('\n'); }, [taskStream.stepLogs, currentStepId]); + const taskNotFound = + taskStream.completed === true && + taskStream.loading === false && + !taskStream.task; + return (
{ subtitle={`Activity for task: ${taskId}`} /> - - - + {taskNotFound ? ( +
Task not found
+ ) : ( + + + + + + + - - - -
+ )}
); From 087bee17446d8d0e1750d6ccdc58a3e3a517ad9f Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 12 Feb 2021 09:46:59 +0100 Subject: [PATCH 037/270] scaffolder: Pass correct logger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- .../scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 51260aef7d..4511a836f6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -76,9 +76,11 @@ export class TaskWorker { }); const stream = new PassThrough(); - stream.on('data', data => { + stream.on('data', async data => { const message = data.toString().trim(); - if (message?.length > 1) task.emitLog(message, metadata); + if (message?.length > 1) { + await task.emitLog(message, metadata); + } }); taskLogger.add(new winston.transports.Stream({ stream })); @@ -112,7 +114,7 @@ export class TaskWorker { const stepOutputs: { [name: string]: JsonValue } = {}; await action.handler({ - logger, + logger: taskLogger, logStream: stream, parameters, workspacePath, From 9fe687aa37a70a8477294e7475c066fccd78de18 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 12 Feb 2021 15:11:25 +0100 Subject: [PATCH 038/270] scaffolder: fix route name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- plugins/scaffolder/src/routes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts index 6b55f89ff1..08f9f3bfa3 100644 --- a/plugins/scaffolder/src/routes.ts +++ b/plugins/scaffolder/src/routes.ts @@ -26,7 +26,7 @@ export const templateRoute = createRouteRef({ }); export const taskRoute = createRouteRef({ - path: '/scaffolder/task/:taskId', + path: '/scaffolder/tasks/:taskId', title: 'Task information', params: ['taskId'], }); From 73760c688f211ff4d1932d5b62262374a93ef637 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 12 Feb 2021 15:13:08 +0100 Subject: [PATCH 039/270] Scaffolder: display entity button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam --- .../src/scaffolder/stages/legacy.ts | 14 ++--- .../src/scaffolder/tasks/TaskWorker.ts | 2 +- .../src/scaffolder/tasks/TemplateConverter.ts | 1 + .../src/components/TaskPage/TaskPage.tsx | 58 +++++++++++++------ .../src/components/hooks/useEventStream.ts | 21 +++++-- 5 files changed, 62 insertions(+), 34 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts b/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts index 5cead565f1..efba57a063 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts @@ -45,11 +45,11 @@ export function registerLegacyActions( registry.register({ id: 'legacy:prepare', async handler(ctx) { + ctx.logger.info('Preparing the skeleton'); const { protocol, url } = ctx.parameters; const preparer = protocol === 'file' ? new FilePreparer() : preparers.get(url as string); - ctx.logger.info('Prepare the skeleton'); await preparer.prepare({ url: url as string, logger: ctx.logger, @@ -61,11 +61,8 @@ export function registerLegacyActions( registry.register({ id: 'legacy:template', async handler(ctx) { - const { logger } = ctx; - + ctx.logger.info('Running the templater'); const templater = templaters.get(ctx.parameters.templater as string); - - logger.info('Run the templater'); await templater.run({ workspacePath: ctx.workspacePath, dockerClient, @@ -120,15 +117,14 @@ export function registerLegacyActions( registry.register({ id: 'catalog:register', async handler(ctx) { - const { logger } = ctx; - const { catalogInfoUrl } = ctx.parameters; // TODO update schema + const { catalogInfoUrl } = ctx.parameters; + ctx.logger.info(`Registering ${catalogInfoUrl} in the catalog`); - logger.info(`Registering ${catalogInfoUrl} in the catalog`); const result = await catalogClient.addLocation({ type: 'url', target: catalogInfoUrl as string, }); - if (result.entities.length === 1) { + if (result.entities.length >= 1) { const { kind, name, namespace } = getEntityName(result.entities[0]); ctx.output('entityRef', `${kind}:${namespace}/${name}`); } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 4511a836f6..6eac36b3f3 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -45,7 +45,7 @@ export class TaskWorker { async runOneTask(task: Task) { try { - const { actionRegistry, logger } = this.options; + const { actionRegistry } = this.options; const workspacePath = path.join( this.options.workingDirectory, diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts index 20e8886884..4139948513 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts @@ -90,6 +90,7 @@ export function templateEntityToSpec( output: { remoteUrl: '{{ steps.publish.output.remoteUrl }}', catalogInfoUrl: '{{ steps.publish.output.catalogInfoUrl }}', + entityRef: '{{ steps.register.output.entityRef }}', }, }; } diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index e2a19f9f63..40a52d8b93 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -25,7 +25,14 @@ import Typography from '@material-ui/core/Typography'; import { useParams } from 'react-router'; import { useTaskEventStream } from '../hooks/useEventStream'; import LazyLog from 'react-lazylog/build/LazyLog'; -import { CircularProgress, StepButton, StepIconProps } from '@material-ui/core'; +import { + Box, + Button, + CircularProgress, + Paper, + StepButton, + StepIconProps, +} from '@material-ui/core'; import { Status } from '../../types'; import { DateTime, Interval } from 'luxon'; import { useInterval } from 'react-use'; @@ -33,6 +40,8 @@ import clsx from 'clsx'; import Check from '@material-ui/icons/Check'; import Cancel from '@material-ui/icons/Cancel'; import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord'; +import { EntityRefLink } from '@backstage/plugin-catalog-react'; +import { parseEntityName } from '@backstage/catalog-model'; // typings are wrong for this library, so fallback to not parsing types. const humanizeDuration = require('humanize-duration'); @@ -197,7 +206,7 @@ export const TaskStatusStepper = memo( const TaskLogger = memo(({ log }: { log: string }) => { return (
- +
); }); @@ -212,7 +221,6 @@ export const TaskPage = () => { const { taskId } = useParams(); const taskStream = useTaskEventStream(taskId); const completed = taskStream.completed; - const steps = useMemo( () => taskStream.task?.spec.steps.map(step => ({ @@ -221,17 +229,17 @@ export const TaskPage = () => { })) ?? [], [taskStream], ); + useEffect(() => { - const activeStep = steps.find(step => + const mostRecentFailedOrActiveStep = steps.find(step => ['failed', 'processing'].includes(step.status), ); - - if (completed) { + if (completed && !mostRecentFailedOrActiveStep) { setLastActiveStepId(steps[steps.length - 1]?.id); return; } - setLastActiveStepId(activeStep?.id); + setLastActiveStepId(mostRecentFailedOrActiveStep?.id); }, [steps, completed]); const currentStepId = userSelectedStepId ?? lastActiveStepId; @@ -253,6 +261,7 @@ export const TaskPage = () => { taskStream.loading === false && !taskStream.task; + const entityRef = taskStream.output?.entityRef; return (
{ {taskNotFound ? (
Task not found
) : ( - - - +
+ + + + + {entityRef && ( + + + + )} + + + + + - - - - +
)} diff --git a/plugins/scaffolder/src/components/hooks/useEventStream.ts b/plugins/scaffolder/src/components/hooks/useEventStream.ts index 8bd8bb4d6c..210fd8cb9b 100644 --- a/plugins/scaffolder/src/components/hooks/useEventStream.ts +++ b/plugins/scaffolder/src/components/hooks/useEventStream.ts @@ -26,6 +26,8 @@ type Step = { startedAt?: string; }; +type TaskOutput = { entityRef?: string } & { [key in string]: string }; + export type TaskStream = { loading: boolean; error?: Error; @@ -33,17 +35,23 @@ export type TaskStream = { completed: boolean; task?: ScaffolderTask; steps: { [stepId in string]: Step }; + output?: TaskOutput; }; type ReducerLogEntry = { createdAt: string; - body: { stepId?: string; status?: Status; message: string }; + body: { + stepId?: string; + status?: Status; + message: string; + output?: TaskOutput; + }; }; type ReducerAction = | { type: 'INIT'; data: ScaffolderTask } | { type: 'LOGS'; data: ReducerLogEntry[] } - | { type: 'COMPLETED' } + | { type: 'COMPLETED'; data: ReducerLogEntry } | { type: 'ERROR'; data: Error }; function reducer(draft: TaskStream, action: ReducerAction) { @@ -101,6 +109,7 @@ function reducer(draft: TaskStream, action: ReducerAction) { case 'COMPLETED': { draft.completed = true; + draft.output = action.data.body.output; return; } @@ -164,6 +173,10 @@ export const useTaskEventStream = (taskId: string): TaskStream => { switch (event.type) { case 'log': return collectedLogEvents.push(event); + case 'completion': + emitLogs(); + dispatch({ type: 'COMPLETED', data: event }); + return undefined; default: throw new Error( `Unhandled event type ${event.type} in observer`, @@ -174,10 +187,6 @@ export const useTaskEventStream = (taskId: string): TaskStream => { emitLogs(); dispatch({ type: 'ERROR', data: error }); }, - complete: () => { - emitLogs(); - dispatch({ type: 'COMPLETED' }); - }, }); }, error => { From 028603219f7a448203d641e1e57f9f509abeb8d6 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 12 Feb 2021 16:07:39 +0100 Subject: [PATCH 040/270] Add task not found page Co-authored-by: blam --- .../components/TaskNotFound/TaskNotFound.tsx | 59 +++++++++++++++++++ .../src/components/TaskPage/TaskPage.tsx | 45 +++++++++++--- 2 files changed, 95 insertions(+), 9 deletions(-) create mode 100644 plugins/scaffolder/src/components/TaskNotFound/TaskNotFound.tsx diff --git a/plugins/scaffolder/src/components/TaskNotFound/TaskNotFound.tsx b/plugins/scaffolder/src/components/TaskNotFound/TaskNotFound.tsx new file mode 100644 index 0000000000..24ac8c357d --- /dev/null +++ b/plugins/scaffolder/src/components/TaskNotFound/TaskNotFound.tsx @@ -0,0 +1,59 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 React from 'react'; +import { Grid, Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import { BackstageTheme } from '@backstage/theme'; + +const useStyles = makeStyles(theme => ({ + container: { + paddingTop: theme.spacing(24), + paddingLeft: theme.spacing(8), + [theme.breakpoints.down('xs')]: { + padding: theme.spacing(2), + }, + }, + title: { + paddingBottom: theme.spacing(2), + [theme.breakpoints.down('xs')]: { + fontSize: 32, + }, + }, + body: { + paddingBottom: theme.spacing(6), + [theme.breakpoints.down('xs')]: { + paddingBottom: theme.spacing(5), + }, + }, +})); + +export const TaskNotFound = () => { + const classes = useStyles(); + + return ( + + + + Task not found + + + No task found with this ID + + + + ); +}; diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index 40a52d8b93..8c9b63fa1b 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -22,9 +22,10 @@ import Step from '@material-ui/core/Step'; import StepLabel from '@material-ui/core/StepLabel'; import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; -import { useParams } from 'react-router'; +import { generatePath, useParams } from 'react-router'; import { useTaskEventStream } from '../hooks/useEventStream'; import LazyLog from 'react-lazylog/build/LazyLog'; +import { Link } from 'react-router-dom'; import { Box, Button, @@ -40,8 +41,9 @@ import clsx from 'clsx'; import Check from '@material-ui/icons/Check'; import Cancel from '@material-ui/icons/Cancel'; import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord'; -import { EntityRefLink } from '@backstage/plugin-catalog-react'; +import { entityRoute } from '@backstage/plugin-catalog-react'; import { parseEntityName } from '@backstage/catalog-model'; +import { TaskNotFound } from '../TaskNotFound/TaskNotFound'; // typings are wrong for this library, so fallback to not parsing types. const humanizeDuration = require('humanize-duration'); @@ -262,6 +264,7 @@ export const TaskPage = () => { !taskStream.task; const entityRef = taskStream.output?.entityRef; + const remoteUrl = taskStream.output?.remoteUrl; return (
{ /> {taskNotFound ? ( -
Task not found
+ ) : (
@@ -286,13 +289,37 @@ export const TaskPage = () => { currentStepId={currentStepId} onUserStepChange={setUserSelectedStepId} /> - {entityRef && ( - - + + )} + {remoteUrl && ( + + )} )} From 808153c0f78828ead4a09622f89b15407510155e Mon Sep 17 00:00:00 2001 From: ebarrios Date: Fri, 12 Feb 2021 16:18:30 +0100 Subject: [PATCH 041/270] Added GithubApp authentication to the scaffolder plugin --- packages/integration/src/github/config.ts | 7 ++++ .../src/scaffolder/stages/publish/github.ts | 38 +++++++++++++++++-- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/packages/integration/src/github/config.ts b/packages/integration/src/github/config.ts index 94ed00731e..4b80a9aa60 100644 --- a/packages/integration/src/github/config.ts +++ b/packages/integration/src/github/config.ts @@ -59,6 +59,13 @@ export type GitHubIntegrationConfig = { */ token?: string; + /** + * The accessType (oAuth|githubApp) to use for requests to this provider. + * + * If no user is specified, oAuth access is used. + */ + accessType?: string; + /** * The GitHub Apps configuration to use for requests to this provider. * diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts index 13aab7ff34..667764dc1e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts @@ -17,6 +17,7 @@ import { PublisherBase, PublisherOptions, PublisherResult } from './types'; import { initRepoAndPush } from './helpers'; import { GitHubIntegrationConfig } from '@backstage/integration'; +import { createAppAuth } from '@octokit/auth-app'; import parseGitUrl from 'git-url-parse'; import { Octokit } from '@octokit/rest'; import path from 'path'; @@ -28,8 +29,23 @@ export class GithubPublisher implements PublisherBase { config: GitHubIntegrationConfig, { repoVisibility }: { repoVisibility: RepoVisibilityOptions }, ) { + config.accessType = 'oAuth'; + if (!config.token) { - return undefined; + if (!config.apps) { + return undefined + } + const auth = createAppAuth({ + appId: config.apps[0].appId, + privateKey: config.apps[0].privateKey, + installationId: process.env.GITHUB_INSTALLATION_ID, + clientId: config.apps[0].clientId, + clientSecret: config.apps[0].clientSecret, + }); + const appAuthentication = await auth({ type: 'installation' }); + + config.token = appAuthentication.token; + config.accessType = 'githubApp' } const githubClient = new Octokit({ @@ -39,6 +55,7 @@ export class GithubPublisher implements PublisherBase { return new GithubPublisher({ token: config.token, + accessType: config.accessType, client: githubClient, repoVisibility, }); @@ -47,6 +64,7 @@ export class GithubPublisher implements PublisherBase { constructor( private readonly config: { token: string; + accessType: string; client: Octokit; repoVisibility: RepoVisibilityOptions; }, @@ -68,15 +86,27 @@ export class GithubPublisher implements PublisherBase { owner, }); - await initRepoAndPush({ + if (this.config.accessType === 'githubApp') { + await initRepoAndPush({ dir: path.join(workspacePath, 'result'), remoteUrl, auth: { - username: this.config.token, - password: 'x-oauth-basic', + username: 'x-access-token', + password: this.config.token, }, logger, }); + } else if (this.config.accessType === 'oAuth'){ + await initRepoAndPush({ + dir: path.join(workspacePath, 'result'), + remoteUrl, + auth: { + username: this.config.token, + password: 'x-oauth-basic', + }, + logger, + }); + } const catalogInfoUrl = remoteUrl.replace( /\.git$/, From cbf4566ed619f5e19707a7ee3e9b18bfcbaaa4de Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 12 Feb 2021 23:31:55 +0100 Subject: [PATCH 042/270] docs: rework the getting started documentation for both contributors and integraters are seperated --- CONTRIBUTING.md | 2 +- .../development-environment.md | 30 +++++++++++++++---- docs/getting-started/index.md | 7 +---- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d163a42954..fb6b524c14 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,7 +66,7 @@ Have you started using Backstage? Adding your company to [ADOPTERS](ADOPTERS.md) So...feel ready to jump in? Let's do this. 👏🏻💯 -Start by reading our [Getting Started](https://backstage.io/docs/getting-started/) page. If you need help, just jump into our [Discord chatroom](https://discord.gg/MUpMjP2). +Start by reading our [Development Environment](https://backstage.io/docs/getting-started/development-environment) page to get yourself setup with a fresh copy of Backstage ready for your contributions. If you need help, just jump into our [Discord chatroom](https://discord.gg/MUpMjP2). ## Coding Guidelines diff --git a/docs/getting-started/development-environment.md b/docs/getting-started/development-environment.md index 01fd7bdc24..cbbb2798f4 100644 --- a/docs/getting-started/development-environment.md +++ b/docs/getting-started/development-environment.md @@ -10,6 +10,9 @@ repository. ## Cloning the Repository +Ok. So you're gonna want some code right? Go ahead and fork the repository into +your own GitHub account and clone that code to your local machine! + After you have cloned the Backstage repository, you should run the following commands once to set things up for development: @@ -25,9 +28,11 @@ Open a terminal window and start the web app by using the following command from the project root. Make sure you have run the above mentioned commands first. ```bash -$ yarn start +$ yarn dev ``` +This is going to start two things, the frontend (:3000) and the backend (:7000). + This should open a local instance of Backstage in your browser, otherwise open one of the URLs printed in the terminal. @@ -39,12 +44,27 @@ Once successfully started, you should see the following message in your terminal window: ``` -You can now view example-app in the browser. - - Local: http://localhost:8080 - On Your Network: http://192.168.1.224:8080 +$ concurrently "yarn start" "yarn start-backend" +$ yarn workspace example-app start +$ yarn workspace example-backend start +$ backstage-cli app:serve +$ backstage-cli backend:dev +[0] Loaded config from app-config.yaml +[1] Build succeeded +[0] ℹ 「wds」: Project is running at http://localhost:3000/ +[0] ℹ 「wds」: webpack output is served from / +[0] ℹ 「wds」: Content not from webpack is served from $BACKSTAGE_DIR/packages/app/public +[0] ℹ 「wds」: 404s will fallback to /index.html +[0] ℹ 「wdm」: wait until bundle finished: / +[1] 2021-02-12T20:58:17.614Z backstage info Loaded config from app-config.yaml ``` +You'll see how you get both logs for the frontend `webpack-dev-server` which +serves the react app ([0]) and the backend ([1]); + +Visit http://localhost:3000 and you should see the bleeding edge of Backstage +ready for contributions! + ## Editor The Backstage development environment does not require any specific editor, but diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index 16c837d676..b946039dfb 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -19,7 +19,7 @@ general, it's easier to fork and clone this project. That will let you stay up to date with the latest changes, and gives you an easier path to make Pull Requests towards this repo. -### Creating a Standalone App +### Create your Backstage App Backstage provides the `@backstage/create-app` package to scaffold standalone instances of Backstage. You will need to have @@ -46,8 +46,3 @@ look something like this. You can read more about this process in You can read more in our [CONTRIBUTING](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) guide, which can help you get setup with a Backstage development environment. - -### Next steps - -Take a look at the [Running Backstage Locally](./running-backstage-locally.md) -guide to learn how to set up Backstage, and how to develop on the platform. From 687ddc49d7c635e183905b52536f460e4ff76a43 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 12 Feb 2021 23:50:03 +0100 Subject: [PATCH 043/270] chore: reworking the title of the docs to avoid confusion --- CONTRIBUTING.md | 2 +- .../{development-environment.md => contributors.md} | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) rename docs/getting-started/{development-environment.md => contributors.md} (96%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fb6b524c14..ddc5449cd8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,7 +66,7 @@ Have you started using Backstage? Adding your company to [ADOPTERS](ADOPTERS.md) So...feel ready to jump in? Let's do this. 👏🏻💯 -Start by reading our [Development Environment](https://backstage.io/docs/getting-started/development-environment) page to get yourself setup with a fresh copy of Backstage ready for your contributions. If you need help, just jump into our [Discord chatroom](https://discord.gg/MUpMjP2). +Start by reading our [Getting Started for Contributors](https://backstage.io/docs/getting-started/contributors) page to get yourself setup with a fresh copy of Backstage ready for your contributions. If you need help, just jump into our [Discord chatroom](https://discord.gg/MUpMjP2). ## Coding Guidelines diff --git a/docs/getting-started/development-environment.md b/docs/getting-started/contributors.md similarity index 96% rename from docs/getting-started/development-environment.md rename to docs/getting-started/contributors.md index cbbb2798f4..e7e1986170 100644 --- a/docs/getting-started/development-environment.md +++ b/docs/getting-started/contributors.md @@ -1,6 +1,6 @@ --- -id: development-environment -title: Development Environment +id: contributors +title: Contributors # prettier-ignore description: Documentation on how to get set up for doing development on the Backstage repository --- @@ -11,7 +11,12 @@ repository. ## Cloning the Repository Ok. So you're gonna want some code right? Go ahead and fork the repository into -your own GitHub account and clone that code to your local machine! +your own GitHub account and clone that code to your local machine or you can +grab the one for the origin like so: + +```bash +git clone git@github.com/backstage/backstage --depth 1 +``` After you have cloned the Backstage repository, you should run the following commands once to set things up for development: @@ -43,7 +48,7 @@ setting an environment variable `PORT` on your local machine. e.g. Once successfully started, you should see the following message in your terminal window: -``` +```sh $ concurrently "yarn start" "yarn start-backend" $ yarn workspace example-app start $ yarn workspace example-backend start From 07b996ed3d5807978f39c8904d1bfa488a593115 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 15 Feb 2021 09:19:55 +0100 Subject: [PATCH 044/270] Rename styles --- plugins/scaffolder/src/components/TaskPage/TaskPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index 8c9b63fa1b..fbf2a09a21 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -107,7 +107,7 @@ const StepTimeTicker = ({ step }: { step: TaskStep }) => { return {time}; }; -const useQontoStepIconStyles = makeStyles({ +const useStepIconStyles = makeStyles({ root: { color: '#eaeaf0', display: 'flex', @@ -126,7 +126,7 @@ const useQontoStepIconStyles = makeStyles({ }); function TaskStepIconComponent(props: StepIconProps) { - const classes = useQontoStepIconStyles(); + const classes = useStepIconStyles(); const { active, completed, error } = props; const getMiddle = () => { From c649761548b931d0db222b6f81a9dfbbf4c53ddf Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 15 Feb 2021 09:20:12 +0100 Subject: [PATCH 045/270] Drop unused import --- plugins/scaffolder/src/api.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index 36bac5875c..4978f7fafa 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { JsonObject } from '@backstage/config'; import { createApiRef, DiscoveryApi, From 1f077511bb074069ac0ba68c445c624d413a2c87 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 15 Feb 2021 09:30:13 +0100 Subject: [PATCH 046/270] Fix typescript warnings --- .../src/scaffolder/tasks/StorageTaskBroker.test.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts index ffb8de6e3d..fd2de30332 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts @@ -47,7 +47,7 @@ describe('StorageTaskBroker', () => { const logger = getVoidLogger(); it('should claim a dispatched work item', async () => { const broker = new StorageTaskBroker(storage, logger); - await broker.dispatch({ steps: [] }); + await broker.dispatch({} as TaskSpec); await expect(broker.claim()).resolves.toEqual(expect.any(TaskAgent)); }); @@ -57,7 +57,7 @@ describe('StorageTaskBroker', () => { await expect(Promise.race([promise, 'waiting'])).resolves.toBe('waiting'); - await broker.dispatch({ steps: [] }); + await broker.dispatch({} as TaskSpec); await expect(promise).resolves.toEqual(expect.any(TaskAgent)); }); @@ -80,7 +80,7 @@ describe('StorageTaskBroker', () => { it('should complete a task', async () => { const broker = new StorageTaskBroker(storage, logger); - const dispatchResult = await broker.dispatch({ steps: [] }); + const dispatchResult = await broker.dispatch({} as TaskSpec); const task = await broker.claim(); await task.complete('completed'); const taskRow = await storage.getTask(dispatchResult.taskId); @@ -89,7 +89,7 @@ describe('StorageTaskBroker', () => { it('should fail a task', async () => { const broker = new StorageTaskBroker(storage, logger); - const dispatchResult = await broker.dispatch({ steps: [] }); + const dispatchResult = await broker.dispatch({} as TaskSpec); const task = await broker.claim(); await task.complete('failed'); const taskRow = await storage.getTask(dispatchResult.taskId); @@ -100,7 +100,7 @@ describe('StorageTaskBroker', () => { const broker1 = new StorageTaskBroker(storage, logger); const broker2 = new StorageTaskBroker(storage, logger); - const { taskId } = await broker1.dispatch({ steps: [] }); + const { taskId } = await broker1.dispatch({} as TaskSpec); const logPromise = new Promise(resolve => { const observedEvents = new Array(); @@ -139,7 +139,7 @@ describe('StorageTaskBroker', () => { it('should heartbeat', async () => { const broker = new StorageTaskBroker(storage, logger); - const { taskId } = await broker.dispatch({ steps: [] }); + const { taskId } = await broker.dispatch({} as TaskSpec); const task = await broker.claim(); const initialTask = await storage.getTask(taskId); @@ -157,7 +157,7 @@ describe('StorageTaskBroker', () => { it('should be update the status to failed if heartbeat fails', async () => { const broker = new StorageTaskBroker(storage, logger); - const { taskId } = await broker.dispatch({ steps: [] }); + const { taskId } = await broker.dispatch({} as TaskSpec); const task = await broker.claim(); jest From 93765c04d8504130932ed13a4e9724a6b23c2e10 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 15 Feb 2021 09:53:50 +0100 Subject: [PATCH 047/270] mock catalogClient --- plugins/scaffolder-backend/src/service/router.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 8a0fa6cba6..b5de493680 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -39,6 +39,10 @@ import request from 'supertest'; import { createRouter } from './router'; import { Templaters, Preparers, Publishers } from '../scaffolder'; import Docker from 'dockerode'; +import { CatalogClient } from '@backstage/catalog-client'; + +jest.mock('@backstage/catalog-client'); +const MockedCatalogClient = CatalogClient as jest.Mock; jest.mock('dockerode'); @@ -111,6 +115,7 @@ describe('createRouter - working directory', () => { dockerClient: new Docker(), entityClient: mockedEntityClient, database: createDatabase(), + catalogClient: new MockedCatalogClient(), }), ).rejects.toThrow('access error'); }); @@ -125,6 +130,7 @@ describe('createRouter - working directory', () => { dockerClient: new Docker(), entityClient: mockedEntityClient, database: createDatabase(), + catalogClient: new MockedCatalogClient(), }); const app = express().use(router); @@ -154,6 +160,7 @@ describe('createRouter - working directory', () => { dockerClient: new Docker(), entityClient: mockedEntityClient, database: createDatabase(), + catalogClient: new MockedCatalogClient(), }); const app = express().use(router); @@ -224,6 +231,7 @@ describe('createRouter', () => { dockerClient: new Docker(), entityClient: generateEntityClient(template), database: createDatabase(), + catalogClient: new MockedCatalogClient(), }); app = express().use(router); }); From 05487b0e693c3c8068cd5203bd56dbdadb973e48 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 15 Feb 2021 11:12:53 +0100 Subject: [PATCH 048/270] Export createExternalRouteRef and set id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- docs/plugins/composability.md | 2 +- packages/core-api/src/app/App.test.tsx | 8 ++++---- packages/core-api/src/routing/RouteRef.ts | 21 ++++++++++++++------ packages/core-api/src/routing/hooks.test.tsx | 6 +++--- packages/core-api/src/routing/hooks.tsx | 2 +- packages/core-api/src/routing/index.ts | 2 +- 6 files changed, 25 insertions(+), 16 deletions(-) diff --git a/docs/plugins/composability.md b/docs/plugins/composability.md index a496217875..6106fa9141 100644 --- a/docs/plugins/composability.md +++ b/docs/plugins/composability.md @@ -248,7 +248,7 @@ might be linking to, allowing the app to decide the final target. If the declare an `ExternalRouteRef` similar to this: ```ts -const headerLinkRouteRef = createExternalRouteRef(); +const headerLinkRouteRef = createExternalRouteRef({ id: 'header-link' }); ``` ### Binding External Routes in the App diff --git a/packages/core-api/src/app/App.test.tsx b/packages/core-api/src/app/App.test.tsx index 8dd40d52b0..6a1e80334b 100644 --- a/packages/core-api/src/app/App.test.tsx +++ b/packages/core-api/src/app/App.test.tsx @@ -28,7 +28,7 @@ import { generateBoundRoutes, PrivateAppImpl } from './App'; describe('generateBoundRoutes', () => { it('runs happy path', () => { - const external = { myRoute: createExternalRouteRef() }; + const external = { myRoute: createExternalRouteRef({ id: '1' }) }; const ref = createRouteRef({ path: '', title: '' }); const result = generateBoundRoutes(({ bind }) => { bind(external, { myRoute: ref }); @@ -38,7 +38,7 @@ describe('generateBoundRoutes', () => { }); it('throws on unknown keys', () => { - const external = { myRoute: createExternalRouteRef() }; + const external = { myRoute: createExternalRouteRef({ id: '2' }) }; const ref = createRouteRef({ path: '', title: '' }); expect(() => generateBoundRoutes(({ bind }) => { @@ -51,7 +51,7 @@ describe('generateBoundRoutes', () => { describe('Integration Test', () => { const plugin1RouteRef = createRouteRef({ path: '/blah1', title: '' }); const plugin2RouteRef = createRouteRef({ path: '/blah2', title: '' }); - const externalRouteRef = createExternalRouteRef(); + const externalRouteRef = createExternalRouteRef({ id: '3' }); const plugin1 = createPlugin({ id: 'blob', @@ -77,7 +77,7 @@ describe('Integration Test', () => { Promise.resolve((_: PropsWithChildren<{ path?: string }>) => { // eslint-disable-next-line react-hooks/rules-of-hooks const routeRefFunction = useRouteRef(externalRouteRef); - return
Our Route Is: {routeRefFunction({})}
; + return
Our Route Is: {routeRefFunction()}
; }), mountPoint: plugin1RouteRef, }), diff --git a/packages/core-api/src/routing/RouteRef.ts b/packages/core-api/src/routing/RouteRef.ts index 0a3df49b58..5ae85ab553 100644 --- a/packages/core-api/src/routing/RouteRef.ts +++ b/packages/core-api/src/routing/RouteRef.ts @@ -65,13 +65,22 @@ export function createRouteRef< } export class ExternalRouteRef { - private constructor() {} - - toString() { - return `externalRouteRef{}`; + private constructor(id: string) { + this.toString = () => `externalRouteRef{${id}}`; } } -export function createExternalRouteRef(): ExternalRouteRef { - return new ((ExternalRouteRef as unknown) as { new (): ExternalRouteRef })(); +export type ExternalRouteRefOptions = { + /** + * An identifier for this route, used to identify it in error messages + */ + id: string; +}; + +export function createExternalRouteRef( + options: ExternalRouteRefOptions, +): ExternalRouteRef { + return new ((ExternalRouteRef as unknown) as { + new (id: string): ExternalRouteRef; + })(options.id); } diff --git a/packages/core-api/src/routing/hooks.test.tsx b/packages/core-api/src/routing/hooks.test.tsx index 84a8314807..462b44c3bd 100644 --- a/packages/core-api/src/routing/hooks.test.tsx +++ b/packages/core-api/src/routing/hooks.test.tsx @@ -59,9 +59,9 @@ const ref2 = createRouteRef(mockConfig({ path: '/wat2' })); const ref3 = createRouteRef(mockConfig({ path: '/wat3' })); const ref4 = createRouteRef(mockConfig({ path: '/wat4' })); const ref5 = createRouteRef(mockConfig({ path: '/wat5' })); -const eRefA = createExternalRouteRef(); -const eRefB = createExternalRouteRef(); -const eRefC = createExternalRouteRef(); +const eRefA = createExternalRouteRef({ id: '1' }); +const eRefB = createExternalRouteRef({ id: '2' }); +const eRefC = createExternalRouteRef({ id: '3' }); const MockRouteSource = (props: { path?: string; diff --git a/packages/core-api/src/routing/hooks.tsx b/packages/core-api/src/routing/hooks.tsx index 3a18d8b0af..c478027ae2 100644 --- a/packages/core-api/src/routing/hooks.tsx +++ b/packages/core-api/src/routing/hooks.tsx @@ -111,7 +111,7 @@ class RouteResolver { const RoutingContext = createContext(undefined); -export function useRouteRef( +export function useRouteRef( routeRef: RouteRef | ExternalRouteRef, ): RouteFunc { const sourceLocation = useLocation(); diff --git a/packages/core-api/src/routing/index.ts b/packages/core-api/src/routing/index.ts index af71e0c3e9..ad88f8ff02 100644 --- a/packages/core-api/src/routing/index.ts +++ b/packages/core-api/src/routing/index.ts @@ -21,6 +21,6 @@ export type { MutableRouteRef, } from './types'; export { FlatRoutes } from './FlatRoutes'; -export { createRouteRef } from './RouteRef'; +export { createRouteRef, createExternalRouteRef } from './RouteRef'; export type { RouteRefConfig } from './RouteRef'; export { useRouteRef } from './hooks'; From 7a4d468d974e023e19588bdccb7317a92c842a44 Mon Sep 17 00:00:00 2001 From: ebarrios Date: Mon, 15 Feb 2021 11:53:38 +0100 Subject: [PATCH 049/270] Used GithubCredentialsProvider as requested by benjdlambert in the pr comments --- .../src/scaffolder/stages/publish/github.ts | 69 ++++++++++--------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts index 667764dc1e..4dd872ad8f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts @@ -16,8 +16,10 @@ import { PublisherBase, PublisherOptions, PublisherResult } from './types'; import { initRepoAndPush } from './helpers'; -import { GitHubIntegrationConfig } from '@backstage/integration'; -import { createAppAuth } from '@octokit/auth-app'; +import { + GitHubIntegrationConfig, + GithubCredentialsProvider, +} from '@backstage/integration'; import parseGitUrl from 'git-url-parse'; import { Octokit } from '@octokit/rest'; import path from 'path'; @@ -31,21 +33,13 @@ export class GithubPublisher implements PublisherBase { ) { config.accessType = 'oAuth'; + const credentialsProvider = GithubCredentialsProvider.create(config); + if (!config.token) { if (!config.apps) { - return undefined + return undefined; } - const auth = createAppAuth({ - appId: config.apps[0].appId, - privateKey: config.apps[0].privateKey, - installationId: process.env.GITHUB_INSTALLATION_ID, - clientId: config.apps[0].clientId, - clientSecret: config.apps[0].clientSecret, - }); - const appAuthentication = await auth({ type: 'installation' }); - - config.token = appAuthentication.token; - config.accessType = 'githubApp' + config.accessType = 'githubApp'; } const githubClient = new Octokit({ @@ -54,10 +48,12 @@ export class GithubPublisher implements PublisherBase { }); return new GithubPublisher({ - token: config.token, + token: config.token || '', accessType: config.accessType, + credentialsProvider, client: githubClient, repoVisibility, + apiBaseUrl: config.apiBaseUrl, }); } @@ -65,8 +61,10 @@ export class GithubPublisher implements PublisherBase { private readonly config: { token: string; accessType: string; + credentialsProvider: GithubCredentialsProvider; client: Octokit; repoVisibility: RepoVisibilityOptions; + apiBaseUrl: string | undefined; }, ) {} @@ -77,6 +75,28 @@ export class GithubPublisher implements PublisherBase { }: PublisherOptions): Promise { const { owner, name } = parseGitUrl(values.storePath); + let auth = { + username: this.config.token, + password: 'x-oauth-basic', + }; + + if (this.config.accessType === 'githubApp') { + this.config.token = + ( + await this.config.credentialsProvider.getCredentials({ + url: values.storePath, + }) + ).token || ''; + this.config.client = new Octokit({ + auth: this.config.token, + baseUrl: this.config.apiBaseUrl, + }); + auth = { + username: 'x-access-token', + password: this.config.token, + }; + } + const description = values.description as string; const access = values.access as string; const remoteUrl = await this.createRemote({ @@ -86,27 +106,12 @@ export class GithubPublisher implements PublisherBase { owner, }); - if (this.config.accessType === 'githubApp') { - await initRepoAndPush({ + await initRepoAndPush({ dir: path.join(workspacePath, 'result'), remoteUrl, - auth: { - username: 'x-access-token', - password: this.config.token, - }, + auth, logger, }); - } else if (this.config.accessType === 'oAuth'){ - await initRepoAndPush({ - dir: path.join(workspacePath, 'result'), - remoteUrl, - auth: { - username: this.config.token, - password: 'x-oauth-basic', - }, - logger, - }); - } const catalogInfoUrl = remoteUrl.replace( /\.git$/, From 579a5456498862d4ddd1c3590dcc5dea677a3870 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 15 Feb 2021 11:57:27 +0100 Subject: [PATCH 050/270] Refactor scaffolder routes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- packages/app/src/App.tsx | 19 +++++++++++++- packages/core-api/src/app/types.ts | 2 +- plugins/catalog/package.json | 1 - .../components/CatalogPage/CatalogPage.tsx | 7 ++--- plugins/catalog/src/plugin.ts | 4 +++ plugins/catalog/src/routes.ts | 21 +++++++++++++++ .../components/TemplateCard/TemplateCard.tsx | 9 +++---- .../TemplatePage/TemplatePage.test.tsx | 3 +-- .../components/TemplatePage/TemplatePage.tsx | 15 ++++++----- plugins/scaffolder/src/index.ts | 1 - plugins/scaffolder/src/plugin.ts | 26 ++++++++----------- plugins/scaffolder/src/routes.ts | 10 +++---- 12 files changed, 77 insertions(+), 41 deletions(-) create mode 100644 plugins/catalog/src/routes.ts diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 70fceb8a01..d0f248c78b 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -22,12 +22,21 @@ import { OAuthRequestDialog, SignInPage, } from '@backstage/core'; -import { Router as CatalogRouter } from '@backstage/plugin-catalog'; +import { + catalogPlugin, + Router as CatalogRouter, +} from '@backstage/plugin-catalog'; import { CatalogImportPage } from '@backstage/plugin-catalog-import'; import { ExplorePage } from '@backstage/plugin-explore'; import { Router as GraphiQLRouter } from '@backstage/plugin-graphiql'; import { Router as LighthouseRouter } from '@backstage/plugin-lighthouse'; import { Router as RegisterComponentRouter } from '@backstage/plugin-register-component'; +import { + TemplateIndexPage, + TemplatePage, + TaskPage, + scaffolderPlugin, +} from '@backstage/plugin-scaffolder'; import { Router as TechRadarRouter } from '@backstage/plugin-tech-radar'; import { Router as DocsRouter } from '@backstage/plugin-techdocs'; import { Router as SettingsRouter } from '@backstage/plugin-user-settings'; @@ -55,6 +64,11 @@ const app = createApp({ ); }, }, + bindRoutes({ bind }) { + bind(catalogPlugin.externalRoutes, { + createComponent: scaffolderPlugin.routes.templateIndex, + }); + }, }); const AppProvider = app.getProvider(); @@ -75,6 +89,9 @@ const routes = ( element={} /> } /> + } /> + } /> + } /> } /> { CatalogFilterType >(); const orgName = configApi.getOptionalString('organization.name') ?? 'Company'; - + const createComponentLink = useRouteRef(createComponentRouteRef); const addMockData = useCallback(async () => { try { const promises: Promise[] = []; @@ -166,7 +167,7 @@ const CatalogPageContents = () => { component={RouterLink} variant="contained" color="primary" - to={scaffolderRootRoute.path} + to={createComponentLink()} > Create Component diff --git a/plugins/catalog/src/plugin.ts b/plugins/catalog/src/plugin.ts index c6944a318a..a2d7fe3c31 100644 --- a/plugins/catalog/src/plugin.ts +++ b/plugins/catalog/src/plugin.ts @@ -29,6 +29,7 @@ import { entityRouteRef, } from '@backstage/plugin-catalog-react'; import { CatalogClientWrapper } from './CatalogClientWrapper'; +import { createComponentRouteRef } from './routes'; export const catalogPlugin = createPlugin({ id: 'catalog', @@ -47,6 +48,9 @@ export const catalogPlugin = createPlugin({ catalogIndex: catalogRouteRef, catalogEntity: entityRouteRef, }, + externalRoutes: { + createComponent: createComponentRouteRef, + }, }); export const CatalogIndexPage = catalogPlugin.provide( diff --git a/plugins/catalog/src/routes.ts b/plugins/catalog/src/routes.ts new file mode 100644 index 0000000000..40e1784235 --- /dev/null +++ b/plugins/catalog/src/routes.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { createExternalRouteRef } from '@backstage/core'; + +export const createComponentRouteRef = createExternalRouteRef({ + id: 'create-component', +}); diff --git a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx index 732b5f3e44..328da7e371 100644 --- a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx +++ b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Button } from '@backstage/core'; +import { Button, useRouteRef } from '@backstage/core'; import { BackstageTheme, pageTheme } from '@backstage/theme'; import { Card, @@ -23,8 +23,7 @@ import { useTheme, } from '@material-ui/core'; import React from 'react'; -import { generatePath } from 'react-router-dom'; -import { templateRoute } from '../../routes'; +import { templateRouteRef } from '../../routes'; const useStyles = makeStyles(theme => ({ header: { @@ -68,7 +67,7 @@ export const TemplateCard = ({ const themeId = pageTheme[type] ? type : 'other'; const theme = backstageTheme.getPageTheme({ themeId }); const classes = useStyles({ backgroundImage: theme.backgroundImage }); - const href = generatePath(templateRoute.path, { templateName: name }); + const templateLink = useRouteRef(templateRouteRef); return ( @@ -84,7 +83,7 @@ export const TemplateCard = ({ {description}
-
diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx index f3f97be877..c5c8367994 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx @@ -22,7 +22,6 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { MemoryRouter, Route } from 'react-router'; import { ScaffolderApi, scaffolderApiRef } from '../../api'; -import { rootRoute } from '../../routes'; import { TemplatePage } from './TemplatePage'; const templateMock = { @@ -134,7 +133,7 @@ describe('TemplatePage', () => { - This is root} /> + This is root} /> , diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 7c25829e5e..ad19af2c7c 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -29,11 +29,11 @@ import { LinearProgress } from '@material-ui/core'; import { IChangeEvent } from '@rjsf/core'; import parseGitUrl from 'git-url-parse'; import React, { useCallback, useState } from 'react'; -import { Navigate, useNavigate } from 'react-router'; +import { useNavigate } from 'react-router'; import { useParams } from 'react-router-dom'; import { useAsync } from 'react-use'; import { scaffolderApiRef } from '../../api'; -import { rootRoute, taskRoute } from '../../routes'; +import { taskRouteRef, templateIndexRouteRef } from '../../routes'; import { MultistepJsonForm } from '../MultistepJsonForm'; const useTemplate = ( @@ -78,7 +78,8 @@ export const TemplatePage = () => { const scaffolderApi = useApi(scaffolderApiRef); const { templateName } = useParams(); const navigate = useNavigate(); - const tasks = useRouteRef(taskRoute); + const tasksLink = useRouteRef(taskRouteRef); + const templateIndexLink = useRouteRef(templateIndexRouteRef); const { template, loading } = useTemplate(templateName, catalogApi); const [formState, setFormState] = useState({}); const handleFormReset = () => setFormState({}); @@ -91,7 +92,7 @@ export const TemplatePage = () => { const handleCreate = async () => { try { const id = await scaffolderApi.scaffold(templateName, formState); - navigate(tasks({ taskId: id })); + navigate(tasksLink({ taskId: id })); } catch (e) { errorApi.post(e); } @@ -99,7 +100,8 @@ export const TemplatePage = () => { if (!loading && !template) { errorApi.post(new Error('Template was not found.')); - return ; + navigate(templateIndexLink()); + return <>{null}; } if (template && !template?.spec?.schema) { @@ -108,7 +110,8 @@ export const TemplatePage = () => { 'Template schema is corrupted, please check the template.yaml file.', ), ); - return ; + navigate(templateIndexLink()); + return <>{null}; } return ( diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index 4a86c2e935..f731ebde40 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -23,4 +23,3 @@ export { } from './plugin'; export type { ScaffolderApi } from './api'; export { ScaffolderClient, scaffolderApiRef } from './api'; -export { rootRoute, templateRoute, taskRoute } from './routes'; diff --git a/plugins/scaffolder/src/plugin.ts b/plugins/scaffolder/src/plugin.ts index abb75355d0..c62117c996 100644 --- a/plugins/scaffolder/src/plugin.ts +++ b/plugins/scaffolder/src/plugin.ts @@ -21,10 +21,11 @@ import { identityApiRef, createRoutableExtension, } from '@backstage/core'; -import { ScaffolderPage as ScaffolderPageComponent } from './components/ScaffolderPage'; -import { TemplatePage as TemplatePageComponent } from './components/TemplatePage'; -import { TaskPage as TaskPageComponent } from './components/TaskPage'; -import { rootRoute, templateRoute, taskRoute } from './routes'; +import { + templateIndexRouteRef, + templateRouteRef, + taskRouteRef, +} from './routes'; import { scaffolderApiRef, ScaffolderClient } from './api'; export const scaffolderPlugin = createPlugin({ @@ -37,15 +38,10 @@ export const scaffolderPlugin = createPlugin({ new ScaffolderClient({ discoveryApi, identityApi }), }), ], - register({ router }) { - router.addRoute(rootRoute, ScaffolderPageComponent); - router.addRoute(templateRoute, TemplatePageComponent); - router.addRoute(taskRoute, TaskPageComponent); - }, routes: { - templateIndex: rootRoute, - template: templateRoute, - task: taskRoute, + templateIndex: templateIndexRouteRef, + template: templateRouteRef, + task: taskRouteRef, }, }); @@ -53,7 +49,7 @@ export const TemplateIndexPage = scaffolderPlugin.provide( createRoutableExtension({ component: () => import('./components/ScaffolderPage').then(m => m.ScaffolderPage), - mountPoint: rootRoute, + mountPoint: templateIndexRouteRef, }), ); @@ -61,13 +57,13 @@ export const TemplatePage = scaffolderPlugin.provide( createRoutableExtension({ component: () => import('./components/TemplatePage').then(m => m.TemplatePage), - mountPoint: templateRoute, + mountPoint: templateRouteRef, }), ); export const TaskPage = scaffolderPlugin.provide( createRoutableExtension({ component: () => import('./components/TaskPage').then(m => m.TaskPage), - mountPoint: taskRoute, + mountPoint: taskRouteRef, }), ); diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts index 08f9f3bfa3..4884c1f83d 100644 --- a/plugins/scaffolder/src/routes.ts +++ b/plugins/scaffolder/src/routes.ts @@ -15,18 +15,16 @@ */ import { createRouteRef } from '@backstage/core'; -export const rootRoute = createRouteRef({ - path: '/create', +export const templateIndexRouteRef = createRouteRef({ title: 'Create new entity', }); -export const templateRoute = createRouteRef({ - path: '/create/:templateName', +export const templateRouteRef = createRouteRef({ title: 'Entity creation', + params: ['templateName'], }); -export const taskRoute = createRouteRef({ - path: '/scaffolder/tasks/:taskId', +export const taskRouteRef = createRouteRef({ title: 'Task information', params: ['taskId'], }); From fb9e95f75bf0b5156d8144d5fd2b358291dc036f Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 15 Feb 2021 14:02:02 +0100 Subject: [PATCH 051/270] Export catalog and scaffolder directly Co-authored-by: Patrik Oldsberg --- packages/app/src/plugins.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index c62c0435a2..862d821ed6 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -14,8 +14,8 @@ * limitations under the License. */ export { plugin as LighthousePlugin } from '@backstage/plugin-lighthouse'; -export { plugin as CatalogPlugin } from '@backstage/plugin-catalog'; -export { plugin as ScaffolderPlugin } from '@backstage/plugin-scaffolder'; +export { catalogPlugin } from '@backstage/plugin-catalog'; +export { scaffolderPlugin } from '@backstage/plugin-scaffolder'; export { plugin as TechRadar } from '@backstage/plugin-tech-radar'; export { explorePlugin } from '@backstage/plugin-explore'; export { plugin as Circleci } from '@backstage/plugin-circleci'; From 8a557a494ae6a26fa2ef71a9898872d03ec3d87e Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 15 Feb 2021 14:02:46 +0100 Subject: [PATCH 052/270] Update scaffolder installation instructions Co-authored-by: Patrik Oldsberg --- .../software-templates/installation.md | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/docs/features/software-templates/installation.md b/docs/features/software-templates/installation.md index a02755c9c7..4d128d4c20 100644 --- a/docs/features/software-templates/installation.md +++ b/docs/features/software-templates/installation.md @@ -33,7 +33,28 @@ it doesn't. Add the following entry to the head of your `packages/app/src/plugins.ts`: ```ts -export { plugin as ScaffolderPlugin } from '@backstage/plugin-scaffolder'; +export { scaffolderPlugin } from '@backstage/plugin-scaffolder'; +``` + +Next we need to install the three pages that the scaffolder plugin provides. You +can choose any name for these routes, but we recommend the following: + +```tsx +import { TemplateIndexPage, TemplatePage, TaskPage } from '@backstage/plugin-scaffolder'; + +// Add to the top-level routes, directly within +} /> +} /> +} /> +``` + +You may also want to add a link to the template index page to your sidebar: + +```tsx +import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; + +// Somewhere within the +; ``` This is all that is needed for the frontend part of the Scaffolder plugin to @@ -66,29 +87,26 @@ following contents to get you up and running quickly. import { CookieCutter, createRouter, - FilePreparer, - GithubPreparer, - GitlabPreparer, Preparers, Publishers, - GithubPublisher, - GitlabPublisher, CreateReactAppTemplater, Templaters, - RepoVisibilityOptions, + CatalogEntityClient, } from '@backstage/plugin-scaffolder-backend'; -import { Octokit } from '@octokit/rest'; -import { Gitlab } from '@gitbeaker/node'; +import { SingleHostDiscovery } from '@backstage/backend-common'; import type { PluginEnvironment } from '../types'; import Docker from 'dockerode'; +import { CatalogClient } from '@backstage/catalog-client'; export default async function createPlugin({ logger, config, + database, }: PluginEnvironment) { const cookiecutterTemplater = new CookieCutter(); const craTemplater = new CreateReactAppTemplater(); const templaters = new Templaters(); + templaters.register('cookiecutter', cookiecutterTemplater); templaters.register('cra', craTemplater); @@ -96,12 +114,21 @@ export default async function createPlugin({ const publishers = await Publishers.fromConfig(config, { logger }); const dockerClient = new Docker(); + + const discovery = SingleHostDiscovery.fromConfig(config); + const entityClient = new CatalogEntityClient({ discovery }); + const catalogClient = new CatalogClient({ discoveryApi: discovery }); + return await createRouter({ preparers, templaters, publishers, logger, + config, dockerClient, + entityClient, + database, + catalogClient, }); } ``` From 536b6b5207c5fab3699283e702ce8556b3c61178 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 15 Feb 2021 14:03:37 +0100 Subject: [PATCH 053/270] Update catalog install instructions Co-authored-by: Patrik Oldsberg --- .../features/software-catalog/installation.md | 64 +++++++++++++------ 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/docs/features/software-catalog/installation.md b/docs/features/software-catalog/installation.md index b2afd62878..3b48760c2f 100644 --- a/docs/features/software-catalog/installation.md +++ b/docs/features/software-catalog/installation.md @@ -30,33 +30,59 @@ it doesn't. Add the following entry to the head of your `packages/app/src/plugins.ts`: ```ts -export { plugin as CatalogPlugin } from '@backstage/plugin-catalog'; +export { catalogPlugin } from '@backstage/plugin-catalog'; ``` -Add the following to your `packages/app/src/apis.ts`: +Next we need to install the two pages that the catalog plugin provides. You can +choose any name for these routes, but we recommend the following: + +```tsx +import { + catalogPlugin, + CatalogIndexPage, + CatalogEntityPage, +} from '@backstage/plugin-catalog'; + +// Add to the top-level routes, directly within +} /> +}> + {/* + This is the root of the custom entity pages for your app, refer to the example app + in the main repo or the output of @backstage/create-app for an example + */} + + +``` + +The catalog plugin also has one external route that needs to be bound for it to +functions, the `createComponent` route which should link to the page where the +user can create components. In a typical setup the create component route will +be linked to the Scaffolder plugins template index page: ```ts -import { catalogApiRef, CatalogClient } from '@backstage/plugin-catalog'; +import { catalogPlugin } from '@backstage/plugin-catalog'; +import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -// Inside the ApiRegistry builder function ... - -builder.add( - catalogApiRef, - new CatalogClient({ - apiOrigin: backendUrl, - basePath: '/catalog', - }), -); +const app = createApp({ + // ... + bindRoutes({ bind }) { + bind(catalogPlugin.externalRoutes, { + createComponent: scaffolderPlugin.routes.templateIndex, + }); + }, +}); ``` -Where `backendUrl` is the `backend.baseUrl` from config, i.e. -`const backendUrl = config.getString('backend.baseUrl')`. +You may also want to add a link to the catalog index page to your sidebar: -The catalog components depend on a number of other -[Utility APIs](../../api/utility-apis.md) to function, including at least the -`ErrorApi` and `StorageApi`. You can find an example of how to install these in -your app -[here](https://github.com/backstage/backstage/blob/61c3a7e5b750dc7c059ef16b188594d31b2c04c2/packages/app/src/apis.ts#L80). +```tsx +import HomeIcon from '@material-ui/icons/Home'; + +// Somewhere within the +; +``` + +This is all that is needed for the frontend part of the Catalog plugin to work! ## Gotchas that we will fix From fe08d59921fa28fb4db18cc2add76efe938ff132 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 15 Feb 2021 14:05:07 +0100 Subject: [PATCH 054/270] Replace deprecated catalog routing Co-authored-by: Patrik Oldsberg --- packages/app/src/App.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 9350106287..1cb83da41c 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -24,7 +24,8 @@ import { } from '@backstage/core'; import { catalogPlugin, - Router as CatalogRouter, + CatalogIndexPage, + CatalogEntityPage, } from '@backstage/plugin-catalog'; import { CatalogImportPage } from '@backstage/plugin-catalog-import'; import { ExplorePage } from '@backstage/plugin-explore'; @@ -88,11 +89,14 @@ const catalogRouteRef = createRouteRef({ const routes = ( - } /> + } /> } - /> + path="/catalog/:namespace/:kind/:name" + element={} + > + + + } /> } /> } /> } /> From 18ed99c0e5f6d376f780d8098ead15ec98837242 Mon Sep 17 00:00:00 2001 From: ebarrios Date: Mon, 15 Feb 2021 14:45:40 +0100 Subject: [PATCH 055/270] Used credentialsProvider as to determine which kind of authentication is going to be used instead of adding an accessType top the GitHubIntegrationConfig --- packages/integration/package.json | 1 + packages/integration/src/github/config.ts | 7 ++++--- .../src/scaffolder/stages/publish/github.ts | 12 ++++-------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/integration/package.json b/packages/integration/package.json index b4134ab00e..254b4d3fe5 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -30,6 +30,7 @@ }, "dependencies": { "@backstage/config": "^0.1.2", + "@backstage/integration": "^0.4.0", "cross-fetch": "^3.0.6", "git-url-parse": "^11.4.4", "@octokit/rest": "^18.0.12", diff --git a/packages/integration/src/github/config.ts b/packages/integration/src/github/config.ts index 4b80a9aa60..2a998ef18b 100644 --- a/packages/integration/src/github/config.ts +++ b/packages/integration/src/github/config.ts @@ -16,6 +16,7 @@ import { Config } from '@backstage/config'; import { isValidHost } from '../helpers'; +import { GithubCredentialsProvider } from '@backstage/integration'; const GITHUB_HOST = 'github.com'; const GITHUB_API_BASE_URL = 'https://api.github.com'; @@ -60,11 +61,11 @@ export type GitHubIntegrationConfig = { token?: string; /** - * The accessType (oAuth|githubApp) to use for requests to this provider. + * The credentialsProvider to use for requests to this provider. * - * If no user is specified, oAuth access is used. + * If no credentialsProvider is created, undefined is used. */ - accessType?: string; + credentialsProvider?: GithubCredentialsProvider | undefined; /** * The GitHub Apps configuration to use for requests to this provider. diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts index 4dd872ad8f..4d038ed5b9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts @@ -31,15 +31,13 @@ export class GithubPublisher implements PublisherBase { config: GitHubIntegrationConfig, { repoVisibility }: { repoVisibility: RepoVisibilityOptions }, ) { - config.accessType = 'oAuth'; - - const credentialsProvider = GithubCredentialsProvider.create(config); + let credentialsProvider: GithubCredentialsProvider | undefined = undefined; if (!config.token) { if (!config.apps) { return undefined; } - config.accessType = 'githubApp'; + credentialsProvider = GithubCredentialsProvider.create(config); } const githubClient = new Octokit({ @@ -49,7 +47,6 @@ export class GithubPublisher implements PublisherBase { return new GithubPublisher({ token: config.token || '', - accessType: config.accessType, credentialsProvider, client: githubClient, repoVisibility, @@ -60,8 +57,7 @@ export class GithubPublisher implements PublisherBase { constructor( private readonly config: { token: string; - accessType: string; - credentialsProvider: GithubCredentialsProvider; + credentialsProvider: GithubCredentialsProvider | undefined; client: Octokit; repoVisibility: RepoVisibilityOptions; apiBaseUrl: string | undefined; @@ -80,7 +76,7 @@ export class GithubPublisher implements PublisherBase { password: 'x-oauth-basic', }; - if (this.config.accessType === 'githubApp') { + if (this.config.credentialsProvider) { this.config.token = ( await this.config.credentialsProvider.getCredentials({ From 09e8baf31b617c20ee1d85ff5e1887762fa96334 Mon Sep 17 00:00:00 2001 From: ebarrios Date: Mon, 15 Feb 2021 14:57:58 +0100 Subject: [PATCH 056/270] Removed the githubCredentials from the github config because it is not needed there --- packages/integration/src/github/config.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/integration/src/github/config.ts b/packages/integration/src/github/config.ts index 2a998ef18b..94ed00731e 100644 --- a/packages/integration/src/github/config.ts +++ b/packages/integration/src/github/config.ts @@ -16,7 +16,6 @@ import { Config } from '@backstage/config'; import { isValidHost } from '../helpers'; -import { GithubCredentialsProvider } from '@backstage/integration'; const GITHUB_HOST = 'github.com'; const GITHUB_API_BASE_URL = 'https://api.github.com'; @@ -60,13 +59,6 @@ export type GitHubIntegrationConfig = { */ token?: string; - /** - * The credentialsProvider to use for requests to this provider. - * - * If no credentialsProvider is created, undefined is used. - */ - credentialsProvider?: GithubCredentialsProvider | undefined; - /** * The GitHub Apps configuration to use for requests to this provider. * From de093269598aeee47c21d8106601232f23278c4e Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 15 Feb 2021 16:15:38 +0100 Subject: [PATCH 057/270] Make sure createdAt is properly formated Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- plugins/scaffolder-backend/package.json | 1 + .../src/scaffolder/tasks/DatabaseTaskStore.ts | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 02f2f9e976..4a6b455539 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -56,6 +56,7 @@ "isomorphic-git": "^1.8.0", "jsonschema": "^1.2.6", "knex": "^0.21.6", + "luxon": "^1.26.0", "morgan": "^1.10.0", "uuid": "^8.2.0", "winston": "^3.2.1", diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index 1dee11ba20..d069008282 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -32,6 +32,7 @@ import { TaskStoreEmitOptions, TaskStoreGetEventsOptions, } from './types'; +import { DateTime } from 'luxon'; const migrationsDir = resolvePackagePath( '@backstage/plugin-scaffolder-backend', @@ -259,7 +260,10 @@ export class DatabaseTaskStore implements TaskStore { taskId, body, type: event.event_type, - createdAt: event.created_at, + createdAt: + typeof event.created_at === 'string' + ? DateTime.fromSQL(event.created_at, { zone: 'UTC' }).toISO() + : event.created_at, }; } catch (error) { throw new Error( From 285deed5de42dd399cdbd74c1d0ccedf1f0cf7a5 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 15 Feb 2021 16:20:24 +0100 Subject: [PATCH 058/270] undo derp comment --- packages/core-api/src/app/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts index 17a6312e66..1970868ca6 100644 --- a/packages/core-api/src/app/types.ts +++ b/packages/core-api/src/app/types.ts @@ -159,7 +159,7 @@ export type AppOptions = { * } * ``` */ - bindRoutes?(context: { /** le derp */ bind: AppRouteBinder }): void; + bindRoutes?(context: { bind: AppRouteBinder }): void; }; export type BackstageApp = { From c2dfd1cf0135ddef22a1dc647a0b7e75cc6e133d Mon Sep 17 00:00:00 2001 From: Taras Date: Mon, 15 Feb 2021 12:18:25 -0500 Subject: [PATCH 059/270] Added fail test for error reporting when accountKey is missing --- .../stages/publish/azureBlobStorage.test.ts | 95 +++++++++++++++---- 1 file changed, 76 insertions(+), 19 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts index 80f3cb0cf7..18724a84f9 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts @@ -19,6 +19,7 @@ import { getVoidLogger } from '@backstage/backend-common'; import { AzureBlobStoragePublish } from './azureBlobStorage'; import { PublisherBase } from './types'; import type { Entity } from '@backstage/catalog-model'; +import type { Logger } from 'winston'; const createMockEntity = (annotations = {}) => { return { @@ -43,33 +44,40 @@ const getEntityRootDir = (entity: Entity) => { return entityRootDir; }; -const logger = getVoidLogger(); -jest.spyOn(logger, 'info').mockReturnValue(logger); -jest.spyOn(logger, 'error').mockReturnValue(logger); +function createLogger() { + const logger = getVoidLogger(); + jest.spyOn(logger, 'info').mockReturnValue(logger); + jest.spyOn(logger, 'error').mockReturnValue(logger); + return logger; +} let publisher: PublisherBase; -beforeEach(async () => { - const mockConfig = new ConfigReader({ - techdocs: { - requestUrl: 'http://localhost:7000', - publisher: { - type: 'azureBlobStorage', - azureBlobStorage: { - credentials: { - accountName: 'accountName', - accountKey: 'accountKey', +describe('publishing with valid credentials', () => { + let logger: Logger; + + beforeEach(async () => { + const mockConfig = new ConfigReader({ + techdocs: { + requestUrl: 'http://localhost:7000', + publisher: { + type: 'azureBlobStorage', + azureBlobStorage: { + credentials: { + accountName: 'accountName', + accountKey: 'accountKey', + }, + containerName: 'containerName', }, - containerName: 'containerName', }, }, - }, + }); + + logger = createLogger(); + + publisher = await AzureBlobStoragePublish.fromConfig(mockConfig, logger); }); - publisher = await AzureBlobStoragePublish.fromConfig(mockConfig, logger); -}); - -describe('AzureBlobStoragePublish', () => { describe('publish', () => { it('should publish a directory', async () => { const entity = createMockEntity(); @@ -145,3 +153,52 @@ describe('AzureBlobStoragePublish', () => { }); }); }); + +describe('attempting to publish with invalid credentials', () => { + let logger: Logger; + + describe('accountKey is not specified', () => { + beforeEach(async () => { + const mockConfig = new ConfigReader({ + techdocs: { + requestUrl: 'http://localhost:7000', + publisher: { + type: 'azureBlobStorage', + azureBlobStorage: { + credentials: { + accountName: 'accountName', + }, + containerName: 'containerName', + }, + }, + }, + }); + + logger = createLogger(); + + publisher = await AzureBlobStoragePublish.fromConfig(mockConfig, logger); + }); + + describe('without Azure environment variables', () => { + it('should log an error', async () => { + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); + + mockFs({ + [entityRootDir]: { + 'index.html': '', + }, + }); + + await publisher.publish({ + entity, + directory: entityRootDir, + }); + + expect(logger.error).toHaveBeenCalled(); + + mockFs.restore(); + }); + }); + }); +}); From c7e8e7cf058a5b0bed942c6b0eaddaab48aed08c Mon Sep 17 00:00:00 2001 From: Esteban Barrios Date: Mon, 15 Feb 2021 23:28:00 +0100 Subject: [PATCH 060/270] Removed unused import Removed unused import --- packages/integration/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/integration/package.json b/packages/integration/package.json index 254b4d3fe5..b4134ab00e 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -30,7 +30,6 @@ }, "dependencies": { "@backstage/config": "^0.1.2", - "@backstage/integration": "^0.4.0", "cross-fetch": "^3.0.6", "git-url-parse": "^11.4.4", "@octokit/rest": "^18.0.12", From ef5a18a9f9be74cdcdccf9ae3e93522dec7a0bfb Mon Sep 17 00:00:00 2001 From: Taras Date: Mon, 15 Feb 2021 18:30:55 -0500 Subject: [PATCH 061/270] Remove unnecessary array of promises --- .../src/stages/publish/azureBlobStorage.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts index a02639e63f..e36254b164 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts @@ -122,8 +122,6 @@ export class AzureBlobStoragePublish implements PublisherBase { // So collecting path of only the files is good enough. const allFilesToUpload = await getFileTreeRecursively(directory); - const uploadPromises: Array> = []; - // Bound the number of concurrent batches. We want a bit of concurrency for // performance reasons, but not so much that we starve the connection pool // or start thrashing. @@ -139,13 +137,11 @@ export class AzureBlobStoragePublish implements PublisherBase { `${entityRootDir}/${relativeFilePath}`, ); // Azure Blob Storage Container file relative path - return limiter(async () => { - await uploadPromises.push( - this.storageClient - .getContainerClient(this.containerName) - .getBlockBlobClient(destination) - .uploadFile(filePath), - ); + return limiter(() => { + return this.storageClient + .getContainerClient(this.containerName) + .getBlockBlobClient(destination) + .uploadFile(filePath); }); }); From 4d483cbc10a827df2b1f66dbf6d5ad81c179907c Mon Sep 17 00:00:00 2001 From: Taras Date: Mon, 15 Feb 2021 21:52:54 -0500 Subject: [PATCH 062/270] Add tests for error reporting --- .../__mocks__/@azure/storage-blob.ts | 72 ++++++++++---- .../stages/publish/azureBlobStorage.test.ts | 96 +++++++++++-------- .../src/stages/publish/azureBlobStorage.ts | 49 +++++----- 3 files changed, 141 insertions(+), 76 deletions(-) diff --git a/packages/techdocs-common/__mocks__/@azure/storage-blob.ts b/packages/techdocs-common/__mocks__/@azure/storage-blob.ts index 60a705f6a7..29f3178efb 100644 --- a/packages/techdocs-common/__mocks__/@azure/storage-blob.ts +++ b/packages/techdocs-common/__mocks__/@azure/storage-blob.ts @@ -14,6 +14,10 @@ * limitations under the License. */ import fs from 'fs'; +import type { + BlobUploadCommonResponse, + ContainerGetPropertiesResponse, +} from '@azure/storage-blob'; export class BlockBlobClient { private readonly blobName; @@ -22,23 +26,29 @@ export class BlockBlobClient { this.blobName = blobName; } - uploadFile(source: string) { - return new Promise((resolve, reject) => { - if (!fs.existsSync(source)) { - reject(''); - } else { - resolve(''); - } + uploadFile(source: string): Promise { + return Promise.resolve({ + _response: { + request: {} as any, + status: 200, + headers: {} as any, + }, }); } exists() { - return new Promise((resolve, reject) => { - if (fs.existsSync(this.blobName)) { - resolve(true); - } else { - reject({ message: 'The object doest not exist !' }); - } + return Promise.resolve(fs.existsSync(this.blobName)); + } +} + +class BlockBlobClientFailUpload extends BlockBlobClient { + uploadFile(source: string): Promise { + return Promise.resolve({ + _response: { + request: {} as any, + status: 500, + headers: {} as any, + }, }); } } @@ -50,9 +60,14 @@ export class ContainerClient { this.containerName = containerName; } - getProperties() { - return new Promise(resolve => { - resolve(''); + getProperties(): Promise { + return Promise.resolve({ + _response: { + request: {} as any, + status: 200, + headers: {} as any, + parsedHeaders: {}, + }, }); } @@ -61,6 +76,25 @@ export class ContainerClient { } } +class ContainerClientFailGetProperties extends ContainerClient { + getProperties(): Promise { + return Promise.resolve({ + _response: { + request: {} as any, + status: 404, + headers: {} as any, + parsedHeaders: {}, + }, + }); + } +} + +class ContainerClientFailUpload extends ContainerClient { + getBlockBlobClient(blobName: string) { + return new BlockBlobClientFailUpload(blobName); + } +} + export class BlobServiceClient { private readonly url; private readonly credential; @@ -71,6 +105,12 @@ export class BlobServiceClient { } getContainerClient(containerName: string) { + if (containerName === 'bad_container') { + return new ContainerClientFailGetProperties(containerName); + } + if (this.credential.accountName === 'failupload') { + return new ContainerClientFailUpload(containerName); + } return new ContainerClient(containerName); } } diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts index 18724a84f9..b37d6c9379 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts @@ -154,51 +154,71 @@ describe('publishing with valid credentials', () => { }); }); -describe('attempting to publish with invalid credentials', () => { - let logger: Logger; - - describe('accountKey is not specified', () => { - beforeEach(async () => { - const mockConfig = new ConfigReader({ - techdocs: { - requestUrl: 'http://localhost:7000', - publisher: { - type: 'azureBlobStorage', - azureBlobStorage: { - credentials: { - accountName: 'accountName', - }, - containerName: 'containerName', +describe('error reporting', () => { + it('reports an error when unable to read container properties', async () => { + const mockConfig = new ConfigReader({ + techdocs: { + requestUrl: 'http://localhost:7000', + publisher: { + type: 'azureBlobStorage', + azureBlobStorage: { + credentials: { + accountName: 'accountName', }, + containerName: 'bad_container', }, }, - }); - - logger = createLogger(); - - publisher = await AzureBlobStoragePublish.fromConfig(mockConfig, logger); + }, }); - describe('without Azure environment variables', () => { - it('should log an error', async () => { - const entity = createMockEntity(); - const entityRootDir = getEntityRootDir(entity); + const logger = createLogger(); - mockFs({ - [entityRootDir]: { - 'index.html': '', + publisher = await AzureBlobStoragePublish.fromConfig(mockConfig, logger); + + expect(logger.error).toHaveBeenCalledWith( + `Could not read Azure Blob Storage container properties`, + ); + }); + + it('reports an error when bad account credentials', async () => { + const mockConfig = new ConfigReader({ + techdocs: { + requestUrl: 'http://localhost:7000', + publisher: { + type: 'azureBlobStorage', + azureBlobStorage: { + credentials: { + accountName: 'failupload', + accountKey: 'accountKey', + }, + containerName: 'containerName', }, - }); - - await publisher.publish({ - entity, - directory: entityRootDir, - }); - - expect(logger.error).toHaveBeenCalled(); - - mockFs.restore(); - }); + }, + }, }); + + const logger = createLogger(); + + publisher = await AzureBlobStoragePublish.fromConfig(mockConfig, logger); + + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); + + mockFs({ + [entityRootDir]: { + 'index.html': '', + }, + }); + + await publisher.publish({ + entity, + directory: entityRootDir, + }); + + expect(logger.error).toHaveBeenCalledWith( + `Unable to upload 1 file(s) to Azure Blob Storage.`, + ); + + mockFs.restore(); }); }); diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts index e36254b164..b0b2bfd855 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts @@ -82,10 +82,16 @@ export class AzureBlobStoragePublish implements PublisherBase { await storageClient .getContainerClient(containerName) .getProperties() - .then(() => { - logger.info( - `Successfully connected to the Azure Blob Storage container ${containerName}.`, - ); + .then(result => { + if (result?._response?.status >= 400) { + logger.error( + 'Could not read Azure Blob Storage container properties', + ); + } else { + logger.info( + `Successfully connected to the Azure Blob Storage container ${containerName}.`, + ); + } }) .catch(reason => { logger.error( @@ -145,16 +151,23 @@ export class AzureBlobStoragePublish implements PublisherBase { }); }); - await Promise.all(promises).then(() => { + let responses: BlobUploadCommonResponse[] = []; + + responses = await Promise.all(promises); + + const failed = responses.filter(r => r?._response?.status >= 400); + if (failed.length === 0) { this.logger.info( `Successfully uploaded all the generated files for Entity ${entity.metadata.name}. Total number of files: ${allFilesToUpload.length}`, ); - }); - return; + } else { + const errorMessage = `Unable to upload ${failed.length} file(s) to Azure Blob Storage.`; + this.logger.error(errorMessage); + } } catch (e) { const errorMessage = `Unable to upload file(s) to Azure Blob Storage. Error ${e.message}`; this.logger.error(errorMessage); - throw new Error(errorMessage); + return; } } @@ -237,19 +250,11 @@ export class AzureBlobStoragePublish implements PublisherBase { * A helper function which checks if index.html of an Entity's docs site is available. This * can be used to verify if there are any pre-generated docs available to serve. */ - async hasDocsBeenGenerated(entity: Entity): Promise { - return new Promise(resolve => { - const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`; - this.storageClient - .getContainerClient(this.containerName) - .getBlockBlobClient(`${entityRootDir}/index.html`) - .exists() - .then((response: boolean) => { - resolve(response); - }) - .catch(() => { - resolve(false); - }); - }); + hasDocsBeenGenerated(entity: Entity): Promise { + const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`; + return this.storageClient + .getContainerClient(this.containerName) + .getBlockBlobClient(`${entityRootDir}/index.html`) + .exists(); } } From e518093dceac901d2cf9c6e68af54c7124da75c6 Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Mon, 15 Feb 2021 21:57:11 -0500 Subject: [PATCH 063/270] Create quiet-dots-mix.md --- .changeset/quiet-dots-mix.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/quiet-dots-mix.md diff --git a/.changeset/quiet-dots-mix.md b/.changeset/quiet-dots-mix.md new file mode 100644 index 0000000000..b1186b320f --- /dev/null +++ b/.changeset/quiet-dots-mix.md @@ -0,0 +1,5 @@ +--- +"@backstage/techdocs-common": patch +--- + +Changed AzureBlobStorage to show errors when upload fails From 79f7bb4665d950104783a82203a6052b507cf671 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Tue, 16 Feb 2021 08:23:08 +0100 Subject: [PATCH 064/270] Update .changeset/quiet-dots-mix.md --- .changeset/quiet-dots-mix.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/quiet-dots-mix.md b/.changeset/quiet-dots-mix.md index b1186b320f..a83278671e 100644 --- a/.changeset/quiet-dots-mix.md +++ b/.changeset/quiet-dots-mix.md @@ -1,5 +1,5 @@ --- -"@backstage/techdocs-common": patch +'@backstage/techdocs-common': patch --- Changed AzureBlobStorage to show errors when upload fails From 3a15d2941ebae2a23a8bead606a55971efc21990 Mon Sep 17 00:00:00 2001 From: ebarrios Date: Tue, 16 Feb 2021 10:18:14 +0100 Subject: [PATCH 065/270] Addresed all coments from Rugvip to remove all mutable objects from the fromConfig function --- .../src/scaffolder/stages/publish/github.ts | 55 ++++++++----------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts index 98d16e6bb7..1846cb90d1 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts @@ -31,24 +31,16 @@ export class GithubPublisher implements PublisherBase { config: GitHubIntegrationConfig, { repoVisibility }: { repoVisibility: RepoVisibilityOptions }, ) { - let credentialsProvider: GithubCredentialsProvider | undefined = undefined; - if (!config.token) { if (!config.apps) { return undefined; } - credentialsProvider = GithubCredentialsProvider.create(config); } - const githubClient = new Octokit({ - auth: config.token, - baseUrl: config.apiBaseUrl, - }); + const credentialsProvider = GithubCredentialsProvider.create(config); return new GithubPublisher({ - token: config.token || '', credentialsProvider, - client: githubClient, repoVisibility, apiBaseUrl: config.apiBaseUrl, }); @@ -56,9 +48,7 @@ export class GithubPublisher implements PublisherBase { constructor( private readonly config: { - token: string; - credentialsProvider: GithubCredentialsProvider | undefined; - client: Octokit; + credentialsProvider: GithubCredentialsProvider; repoVisibility: RepoVisibilityOptions; apiBaseUrl: string | undefined; }, @@ -70,23 +60,23 @@ export class GithubPublisher implements PublisherBase { logger, }: PublisherOptions): Promise { const { owner, name } = parseGitUrl(values.storePath); - - if (this.config.credentialsProvider) { - this.config.token = - ( - await this.config.credentialsProvider.getCredentials({ - url: values.storePath, - }) - ).token || ''; - this.config.client = new Octokit({ - auth: this.config.token, - baseUrl: this.config.apiBaseUrl, - }); - } + + const token = + ( + await this.config.credentialsProvider.getCredentials({ + url: values.storePath, + }) + ).token || ''; + + const client = new Octokit({ + auth: token, + baseUrl: this.config.apiBaseUrl, + }); const description = values.description as string; const access = values.access as string; const remoteUrl = await this.createRemote({ + client, description, access, name, @@ -98,7 +88,7 @@ export class GithubPublisher implements PublisherBase { remoteUrl, auth: { username: 'x-access-token', - password: this.config.token, + password: token, }, logger, }); @@ -111,27 +101,28 @@ export class GithubPublisher implements PublisherBase { } private async createRemote(opts: { + client: Octokit; access: string; name: string; owner: string; description: string; }) { - const { access, description, owner, name } = opts; + const { client, access, description, owner, name } = opts; - const user = await this.config.client.users.getByUsername({ + const user = await client.users.getByUsername({ username: owner, }); const repoCreationPromise = user.data.type === 'Organization' - ? this.config.client.repos.createInOrg({ + ? client.repos.createInOrg({ name, org: owner, private: this.config.repoVisibility !== 'public', visibility: this.config.repoVisibility, description, }) - : this.config.client.repos.createForAuthenticatedUser({ + : client.repos.createForAuthenticatedUser({ name, private: this.config.repoVisibility === 'private', description, @@ -141,7 +132,7 @@ export class GithubPublisher implements PublisherBase { if (access?.startsWith(`${owner}/`)) { const [, team] = access.split('/'); - await this.config.client.teams.addOrUpdateRepoPermissionsInOrg({ + await client.teams.addOrUpdateRepoPermissionsInOrg({ org: owner, team_slug: team, owner, @@ -150,7 +141,7 @@ export class GithubPublisher implements PublisherBase { }); // no need to add access if it's the person who own's the personal account } else if (access && access !== owner) { - await this.config.client.repos.addCollaborator({ + await client.repos.addCollaborator({ owner, repo: name, username: access, From edbc27bfdc1c0ba4b3f93cf3f109f49804170853 Mon Sep 17 00:00:00 2001 From: ebarrios Date: Tue, 16 Feb 2021 10:59:04 +0100 Subject: [PATCH 066/270] Added changeset --- .changeset/loud-owls-beam.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/loud-owls-beam.md diff --git a/.changeset/loud-owls-beam.md b/.changeset/loud-owls-beam.md new file mode 100644 index 0000000000..f17b23223b --- /dev/null +++ b/.changeset/loud-owls-beam.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Added githubApp authentication to the scaffolder-backend plugin From 0b7cb922587d33667b76edf48ea2e36a73c07ace Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 16 Feb 2021 11:06:20 +0100 Subject: [PATCH 067/270] Fix routes Co-authored-by: Patrik Oldsberg --- .../features/software-catalog/installation.md | 2 +- .../software-templates/installation.md | 10 +++--- packages/app/src/App.tsx | 13 ++------ plugins/scaffolder/src/components/Router.tsx | 29 +++++++++++++++++ .../components/TemplateCard/TemplateCard.tsx | 10 ++++-- .../components/TemplatePage/TemplatePage.tsx | 14 ++++---- plugins/scaffolder/src/index.ts | 4 +-- plugins/scaffolder/src/plugin.ts | 32 +++---------------- plugins/scaffolder/src/routes.ts | 12 +------ 9 files changed, 58 insertions(+), 68 deletions(-) create mode 100644 plugins/scaffolder/src/components/Router.tsx diff --git a/docs/features/software-catalog/installation.md b/docs/features/software-catalog/installation.md index 3b48760c2f..309951d58c 100644 --- a/docs/features/software-catalog/installation.md +++ b/docs/features/software-catalog/installation.md @@ -67,7 +67,7 @@ const app = createApp({ // ... bindRoutes({ bind }) { bind(catalogPlugin.externalRoutes, { - createComponent: scaffolderPlugin.routes.templateIndex, + createComponent: scaffolderPlugin.routes.root, }); }, }); diff --git a/docs/features/software-templates/installation.md b/docs/features/software-templates/installation.md index 4d128d4c20..d5bd805c10 100644 --- a/docs/features/software-templates/installation.md +++ b/docs/features/software-templates/installation.md @@ -36,16 +36,14 @@ Add the following entry to the head of your `packages/app/src/plugins.ts`: export { scaffolderPlugin } from '@backstage/plugin-scaffolder'; ``` -Next we need to install the three pages that the scaffolder plugin provides. You -can choose any name for these routes, but we recommend the following: +Next we need to install the root page that the Scaffolder plugin provides. You +can choose any path for the route, but we recommend the following: ```tsx -import { TemplateIndexPage, TemplatePage, TaskPage } from '@backstage/plugin-scaffolder'; +import { ScaffolderPage } from '@backstage/plugin-scaffolder'; // Add to the top-level routes, directly within -} /> -} /> -} /> +} />; ``` You may also want to add a link to the template index page to your sidebar: diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 1cb83da41c..4a49db02d5 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -32,12 +32,7 @@ import { ExplorePage } from '@backstage/plugin-explore'; import { Router as GraphiQLRouter } from '@backstage/plugin-graphiql'; import { Router as LighthouseRouter } from '@backstage/plugin-lighthouse'; import { Router as RegisterComponentRouter } from '@backstage/plugin-register-component'; -import { - TemplateIndexPage, - TemplatePage, - TaskPage, - scaffolderPlugin, -} from '@backstage/plugin-scaffolder'; +import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; import { Router as TechRadarRouter } from '@backstage/plugin-tech-radar'; import { Router as DocsRouter } from '@backstage/plugin-techdocs'; import { Router as SettingsRouter } from '@backstage/plugin-user-settings'; @@ -72,7 +67,7 @@ const app = createApp({ }, bindRoutes({ bind }) { bind(catalogPlugin.externalRoutes, { - createComponent: scaffolderPlugin.routes.templateIndex, + createComponent: scaffolderPlugin.routes.root, }); }, }); @@ -98,9 +93,7 @@ const routes = ( } /> } /> - } /> - } /> - } /> + } /> } /> ( + + } /> + } /> + } /> + +); diff --git a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx index 7d65bd1307..c38f4e09a5 100644 --- a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx +++ b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx @@ -25,7 +25,8 @@ import { useTheme, } from '@material-ui/core'; import React from 'react'; -import { templateRouteRef } from '../../routes'; +import { generatePath } from 'react-router'; +import { rootRouteRef } from '../../routes'; const useStyles = makeStyles(theme => ({ header: { @@ -58,11 +59,14 @@ export const TemplateCard = ({ name, }: TemplateCardProps) => { const backstageTheme = useTheme(); + const rootLink = useRouteRef(rootRouteRef); const themeId = pageTheme[type] ? type : 'other'; const theme = backstageTheme.getPageTheme({ themeId }); const classes = useStyles({ backgroundImage: theme.backgroundImage }); - const templateLink = useRouteRef(templateRouteRef); + const href = generatePath(`${rootLink()}/templates/:templateName`, { + templateName: name, + }); return ( @@ -79,7 +83,7 @@ export const TemplateCard = ({ - diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index c29f73a629..030cd5dccb 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -29,11 +29,11 @@ import { LinearProgress } from '@material-ui/core'; import { IChangeEvent } from '@rjsf/core'; import parseGitUrl from 'git-url-parse'; import React, { useCallback, useState } from 'react'; -import { useNavigate } from 'react-router'; +import { generatePath, useNavigate } from 'react-router'; import { useParams } from 'react-router-dom'; import { useAsync } from 'react-use'; import { scaffolderApiRef } from '../../api'; -import { taskRouteRef, templateIndexRouteRef } from '../../routes'; +import { rootRouteRef } from '../../routes'; import { MultistepJsonForm } from '../MultistepJsonForm'; const useTemplate = ( @@ -78,8 +78,7 @@ export const TemplatePage = () => { const scaffolderApi = useApi(scaffolderApiRef); const { templateName } = useParams(); const navigate = useNavigate(); - const tasksLink = useRouteRef(taskRouteRef); - const templateIndexLink = useRouteRef(templateIndexRouteRef); + const rootLink = useRouteRef(rootRouteRef); const { template, loading } = useTemplate(templateName, catalogApi); const [formState, setFormState] = useState({}); const handleFormReset = () => setFormState({}); @@ -92,7 +91,8 @@ export const TemplatePage = () => { const handleCreate = async () => { try { const id = await scaffolderApi.scaffold(templateName, formState); - navigate(tasksLink({ taskId: id })); + + navigate(generatePath(`${rootLink()}/tasks/:taskId`, { taskId: id })); } catch (e) { errorApi.post(e); } @@ -100,7 +100,7 @@ export const TemplatePage = () => { if (!loading && !template) { errorApi.post(new Error('Template was not found.')); - navigate(templateIndexLink()); + navigate(rootLink()); return <>{null}; } @@ -110,7 +110,7 @@ export const TemplatePage = () => { 'Template schema is corrupted, please check the template.yaml file.', ), ); - navigate(templateIndexLink()); + navigate(rootLink()); return <>{null}; } diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index f731ebde40..e0b574e6c6 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -17,9 +17,7 @@ export { scaffolderPlugin, scaffolderPlugin as plugin, - TemplateIndexPage, - TemplatePage, - TaskPage, + ScaffolderPage, } from './plugin'; export type { ScaffolderApi } from './api'; export { ScaffolderClient, scaffolderApiRef } from './api'; diff --git a/plugins/scaffolder/src/plugin.ts b/plugins/scaffolder/src/plugin.ts index c62117c996..a6ba1a9899 100644 --- a/plugins/scaffolder/src/plugin.ts +++ b/plugins/scaffolder/src/plugin.ts @@ -21,11 +21,7 @@ import { identityApiRef, createRoutableExtension, } from '@backstage/core'; -import { - templateIndexRouteRef, - templateRouteRef, - taskRouteRef, -} from './routes'; +import { rootRouteRef } from './routes'; import { scaffolderApiRef, ScaffolderClient } from './api'; export const scaffolderPlugin = createPlugin({ @@ -39,31 +35,13 @@ export const scaffolderPlugin = createPlugin({ }), ], routes: { - templateIndex: templateIndexRouteRef, - template: templateRouteRef, - task: taskRouteRef, + root: rootRouteRef, }, }); -export const TemplateIndexPage = scaffolderPlugin.provide( +export const ScaffolderPage = scaffolderPlugin.provide( createRoutableExtension({ - component: () => - import('./components/ScaffolderPage').then(m => m.ScaffolderPage), - mountPoint: templateIndexRouteRef, - }), -); - -export const TemplatePage = scaffolderPlugin.provide( - createRoutableExtension({ - component: () => - import('./components/TemplatePage').then(m => m.TemplatePage), - mountPoint: templateRouteRef, - }), -); - -export const TaskPage = scaffolderPlugin.provide( - createRoutableExtension({ - component: () => import('./components/TaskPage').then(m => m.TaskPage), - mountPoint: taskRouteRef, + component: () => import('./components/Router').then(m => m.Router), + mountPoint: rootRouteRef, }), ); diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts index 4884c1f83d..413e8f4194 100644 --- a/plugins/scaffolder/src/routes.ts +++ b/plugins/scaffolder/src/routes.ts @@ -15,16 +15,6 @@ */ import { createRouteRef } from '@backstage/core'; -export const templateIndexRouteRef = createRouteRef({ +export const rootRouteRef = createRouteRef({ title: 'Create new entity', }); - -export const templateRouteRef = createRouteRef({ - title: 'Entity creation', - params: ['templateName'], -}); - -export const taskRouteRef = createRouteRef({ - title: 'Task information', - params: ['taskId'], -}); From 17d1d909e4f4f5f14161b580f0b1fde4f3d8e023 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 16 Feb 2021 11:23:06 +0100 Subject: [PATCH 068/270] Replace EntityClient with CatalogClient Co-authored-by: Patrik Oldsberg --- docs/features/software-templates/installation.md | 3 --- packages/backend/package.json | 1 + packages/backend/src/plugins/scaffolder.ts | 3 --- .../src/lib/catalog/CatalogEntityClient.ts | 14 ++------------ plugins/scaffolder-backend/src/service/router.ts | 3 +-- yarn.lock | 7 +++++-- 6 files changed, 9 insertions(+), 22 deletions(-) diff --git a/docs/features/software-templates/installation.md b/docs/features/software-templates/installation.md index d5bd805c10..9aa1d3bc8f 100644 --- a/docs/features/software-templates/installation.md +++ b/docs/features/software-templates/installation.md @@ -89,7 +89,6 @@ import { Publishers, CreateReactAppTemplater, Templaters, - CatalogEntityClient, } from '@backstage/plugin-scaffolder-backend'; import { SingleHostDiscovery } from '@backstage/backend-common'; import type { PluginEnvironment } from '../types'; @@ -114,7 +113,6 @@ export default async function createPlugin({ const dockerClient = new Docker(); const discovery = SingleHostDiscovery.fromConfig(config); - const entityClient = new CatalogEntityClient({ discovery }); const catalogClient = new CatalogClient({ discoveryApi: discovery }); return await createRouter({ @@ -124,7 +122,6 @@ export default async function createPlugin({ logger, config, dockerClient, - entityClient, database, catalogClient, }); diff --git a/packages/backend/package.json b/packages/backend/package.json index e5271f0109..e585d20035 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -28,6 +28,7 @@ }, "dependencies": { "@backstage/backend-common": "^0.5.3", + "@backstage/catalog-client": "^0.3.6", "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", "@backstage/plugin-app-backend": "^0.3.7", diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts index ae4b22759b..10da5f0e15 100644 --- a/packages/backend/src/plugins/scaffolder.ts +++ b/packages/backend/src/plugins/scaffolder.ts @@ -21,7 +21,6 @@ import { Publishers, CreateReactAppTemplater, Templaters, - CatalogEntityClient, } from '@backstage/plugin-scaffolder-backend'; import { SingleHostDiscovery } from '@backstage/backend-common'; import type { PluginEnvironment } from '../types'; @@ -46,7 +45,6 @@ export default async function createPlugin({ const dockerClient = new Docker(); const discovery = SingleHostDiscovery.fromConfig(config); - const entityClient = new CatalogEntityClient({ discovery }); const catalogClient = new CatalogClient({ discoveryApi: discovery }); return await createRouter({ @@ -56,7 +54,6 @@ export default async function createPlugin({ logger, config, dockerClient, - entityClient, database, catalogClient, }); diff --git a/plugins/scaffolder-backend/src/lib/catalog/CatalogEntityClient.ts b/plugins/scaffolder-backend/src/lib/catalog/CatalogEntityClient.ts index 827ed0559a..13cfd32f6a 100644 --- a/plugins/scaffolder-backend/src/lib/catalog/CatalogEntityClient.ts +++ b/plugins/scaffolder-backend/src/lib/catalog/CatalogEntityClient.ts @@ -16,23 +16,13 @@ import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { CatalogClient } from '@backstage/catalog-client'; -import { - ConflictError, - NotFoundError, - PluginEndpointDiscovery, -} from '@backstage/backend-common'; +import { ConflictError, NotFoundError } from '@backstage/backend-common'; /** * A catalog client tailored for reading out entity data from the catalog. */ export class CatalogEntityClient { - private readonly catalogClient: CatalogClient; - - constructor(options: { discovery: PluginEndpointDiscovery }) { - this.catalogClient = new CatalogClient({ - discoveryApi: options.discovery, - }); - } + constructor(private readonly catalogClient: CatalogClient) {} /** * Looks up a single template using a template name. diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index f5b52006e6..bb5fec306a 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -58,7 +58,6 @@ export interface RouterOptions { logger: Logger; config: Config; dockerClient: Docker; - entityClient: CatalogEntityClient; database: PluginDatabaseManager; catalogClient: CatalogClient; } @@ -76,7 +75,6 @@ export async function createRouter( logger: parentLogger, config, dockerClient, - entityClient, database, catalogClient, } = options; @@ -84,6 +82,7 @@ export async function createRouter( const logger = parentLogger.child({ plugin: 'scaffolder' }); const workingDirectory = await getWorkingDirectory(config, logger); const jobProcessor = await JobProcessor.fromConfig({ config, logger }); + const entityClient = new CatalogEntityClient(catalogClient); const databaseTaskStore = await DatabaseTaskStore.create( await database.getClient(), diff --git a/yarn.lock b/yarn.lock index da8cb59368..f4168c3778 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1872,7 +1872,6 @@ "@backstage/catalog-model" "^0.7.1" "@backstage/core" "^0.6.1" "@backstage/plugin-catalog-react" "^0.0.3" - "@backstage/plugin-scaffolder" "^0.5.0" "@backstage/theme" "^0.2.3" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" @@ -1895,7 +1894,6 @@ "@backstage/catalog-model" "^0.7.1" "@backstage/core" "^0.6.1" "@backstage/plugin-catalog-react" "^0.0.3" - "@backstage/plugin-scaffolder" "^0.5.0" "@backstage/theme" "^0.2.3" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" @@ -17525,6 +17523,11 @@ luxon@1.25.0, luxon@^1.25.0: resolved "https://registry.npmjs.org/luxon/-/luxon-1.25.0.tgz#d86219e90bc0102c0eb299d65b2f5e95efe1fe72" integrity sha512-hEgLurSH8kQRjY6i4YLey+mcKVAWXbDNlZRmM6AgWDJ1cY3atl8Ztf5wEY7VBReFbmGnwQPz7KYJblL8B2k0jQ== +luxon@^1.26.0: + version "1.26.0" + resolved "https://registry.npmjs.org/luxon/-/luxon-1.26.0.tgz#d3692361fda51473948252061d0f8561df02b578" + integrity sha512-+V5QIQ5f6CDXQpWNICELwjwuHdqeJM1UenlZWx5ujcRMc9venvluCjFb4t5NYLhb6IhkbMVOxzVuOqkgMxee2A== + macos-release@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" From a5f42cf66f5e7f580d17bfe07b069ae44d929418 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 16 Feb 2021 11:30:35 +0100 Subject: [PATCH 069/270] Add changesets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- .changeset/clever-tomatoes-change.md | 53 ++++++++++++++++++++++++++++ .changeset/dingo-dongo.md | 41 +++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 .changeset/clever-tomatoes-change.md create mode 100644 .changeset/dingo-dongo.md diff --git a/.changeset/clever-tomatoes-change.md b/.changeset/clever-tomatoes-change.md new file mode 100644 index 0000000000..f1041f28c6 --- /dev/null +++ b/.changeset/clever-tomatoes-change.md @@ -0,0 +1,53 @@ +--- +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-scaffolder-backend': minor +--- + +# Stateless scaffolding + +The scaffolder has been redesigned to be horizontally scalable and to persistently store task state and execution logs in the database. + +Each scaffolder task is given a unique task ID which is persisted in the database. +Tasks are then picked up by a `TaskWorker` which performs the scaffolding steps. +Execution logs are also peristed in the database meaning you can now refresh the scaffolder task status page without losing information. + +The task status page is now dynamically created based on the step information stored in the database. +This allows for custom steps to be displayed once the next version of the scaffolder template schema is available. + +The task page is updated to display links to both the git repository and to the newly created catalog entity. + +Component registration has moved from the frontend into a separate registration step executed by the `TaskWorker`. This requires that a `CatalogClient` is passed to the scaffoler backend instead of the old `CatalogEntityClient`. + +Make sure to update `plugins/scaffolder.ts` + +```diff + import { + CookieCutter, + createRouter, + Preparers, + Publishers, + CreateReactAppTemplater, + Templaters, +- CatalogEntityClient, + } from '@backstage/plugin-scaffolder-backend'; + ++import { CatalogClient } from '@backstage/catalog-client'; + + const discovery = SingleHostDiscovery.fromConfig(config); +-const entityClient = new CatalogEntityClient({ discovery }); ++const catalogClient = new CatalogClient({ discoveryApi: discovery }) + + return await createRouter({ + preparers, + templaters, + publishers, + logger, + config, + dockerClient, +- entityClient, + database, ++ catalogClient, + }); +``` + +As well as adding the `@backstage/catalog-client` packages as a dependency of your backend package. diff --git a/.changeset/dingo-dongo.md b/.changeset/dingo-dongo.md new file mode 100644 index 0000000000..87c4172448 --- /dev/null +++ b/.changeset/dingo-dongo.md @@ -0,0 +1,41 @@ +--- +'@backstage/plugin-catalog': minor +'@backstage/plugin-scaffolder': minor +--- + +The Scaffolder and Catalog plugins have been migrated to partially require use of the [new composability API](https://backstage.io/docs/plugins/composability). The Scaffolder used to register its pages using the deprecated route registration plugin API, but those registrations have been removed. This means you now need to add the Scaffolder plugin page to the app directly. + +The page is imported from the Scaffolder plugin and added to the `` component: + +```tsx +} /> +``` + +You can choose your own paths if you wish, as long as the `templateName` and `taskId` parameters are present. + +The Catalog plugin has also been migrated to use an [external route reference](https://backstage.io/docs/plugins/composability#binding-external-routes-in-the-app) to dynamically link to the create component page. This means you need to migrate the catalog plugin to use the new extension components, as well as bind the external route. + +To use the new extension components, replace existing usage of the `CatalogRouter` with the following: + +```tsx +} /> +}> + + +``` + +And to bind the external route from the catalog plugin to the scaffolder template index page, make sure you have the appropriate imports and add the following to the `createApp` call: + +```ts +import { catalogPlugin } from '@backstage/plugin-catalog'; +import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; + +const app = createApp({ + // ... + bindRoutes({ bind }) { + bind(catalogPlugin.externalRoutes, { + createComponent: scaffolderPlugin.routes.root, + }); + }, +}); +``` From f6c81bb8d54f4039ba3b3f9bb9beddfb0770dc19 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 16 Feb 2021 11:33:29 +0100 Subject: [PATCH 070/270] create-app: Update scaffolder routes --- .../default-app/packages/app/src/App.tsx | 22 +++++++++++++++---- .../backend/src/plugins/scaffolder.ts | 6 ++--- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/packages/create-app/templates/default-app/packages/app/src/App.tsx b/packages/create-app/templates/default-app/packages/app/src/App.tsx index 711e501224..cf31647ce0 100644 --- a/packages/create-app/templates/default-app/packages/app/src/App.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/App.tsx @@ -10,7 +10,11 @@ import { apis } from './apis'; import * as plugins from './plugins'; import { AppSidebar } from './sidebar'; import { Route, Navigate } from 'react-router'; -import { Router as CatalogRouter } from '@backstage/plugin-catalog'; +import { + catalogPlugin, + CatalogIndexPage, + CatalogEntityPage, +} from '@backstage/plugin-catalog'; import { Router as DocsRouter } from '@backstage/plugin-techdocs'; import { CatalogImportPage } from '@backstage/plugin-catalog-import'; import { Router as TechRadarRouter } from '@backstage/plugin-tech-radar'; @@ -18,10 +22,16 @@ import { SearchPage as SearchRouter } from '@backstage/plugin-search'; import { Router as SettingsRouter } from '@backstage/plugin-user-settings'; import { EntityPage } from './components/catalog/EntityPage'; +import { scaffolderPlugin, ScaffolderPage } from '@backstage/plugin-scaffolder'; const app = createApp({ apis, plugins: Object.values(plugins), + bindRoutes({ bind }) { + bind(catalogPlugin.externalRoutes, { + createComponent: scaffolderPlugin.routes.root, + }); + } }); const AppProvider = app.getProvider(); @@ -37,11 +47,15 @@ const App = () => ( + } /> } - /> + path="/catalog/:namespace/:kind/:name" + element={} + > + + } /> + } /> } diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts index d68f90ce08..84657948e5 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts @@ -5,11 +5,11 @@ import { Publishers, CreateReactAppTemplater, Templaters, - CatalogEntityClient, } from '@backstage/plugin-scaffolder-backend'; import { SingleHostDiscovery } from '@backstage/backend-common'; import type { PluginEnvironment } from '../types'; import Docker from 'dockerode'; +import { CatalogClient } from '@backstage/catalog-client'; export default async function createPlugin({ logger, @@ -29,7 +29,7 @@ export default async function createPlugin({ const dockerClient = new Docker(); const discovery = SingleHostDiscovery.fromConfig(config); - const entityClient = new CatalogEntityClient({ discovery }); + const catalogClient = new CatalogClient({ discoveryApi: discovery }) return await createRouter({ preparers, @@ -38,7 +38,7 @@ export default async function createPlugin({ logger, config, dockerClient, - entityClient, database, + catalogClient, }); } From 3fc738a55c494da8c577da1e9b021ad8c517c041 Mon Sep 17 00:00:00 2001 From: ebarrios Date: Tue, 16 Feb 2021 12:58:15 +0100 Subject: [PATCH 071/270] modify change set to path, simplify to one expresion the check for config.token and config.apps and deconstructed the response fron the credentialProvider.getCredentials function --- .changeset/loud-owls-beam.md | 2 +- .../src/scaffolder/stages/publish/github.ts | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.changeset/loud-owls-beam.md b/.changeset/loud-owls-beam.md index f17b23223b..764bfdf72a 100644 --- a/.changeset/loud-owls-beam.md +++ b/.changeset/loud-owls-beam.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-scaffolder-backend': minor +'@backstage/plugin-scaffolder-backend': patch --- Added githubApp authentication to the scaffolder-backend plugin diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts index 1846cb90d1..365b3f14cd 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts @@ -31,10 +31,8 @@ export class GithubPublisher implements PublisherBase { config: GitHubIntegrationConfig, { repoVisibility }: { repoVisibility: RepoVisibilityOptions }, ) { - if (!config.token) { - if (!config.apps) { - return undefined; - } + if (!config.token && !config.apps) { + return undefined; } const credentialsProvider = GithubCredentialsProvider.create(config); @@ -61,12 +59,13 @@ export class GithubPublisher implements PublisherBase { }: PublisherOptions): Promise { const { owner, name } = parseGitUrl(values.storePath); - const token = - ( - await this.config.credentialsProvider.getCredentials({ - url: values.storePath, - }) - ).token || ''; + const { token } = await this.config.credentialsProvider.getCredentials({ + url: values.storePath, + }); + + if (!token) { + return { remoteUrl: '', catalogInfoUrl: undefined }; + } const client = new Octokit({ auth: token, From 831b65502ba35570d0c7170dc7d5092bf25b5e40 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 16 Feb 2021 13:09:26 +0100 Subject: [PATCH 072/270] Fix typos --- .changeset/clever-tomatoes-change.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/clever-tomatoes-change.md b/.changeset/clever-tomatoes-change.md index f1041f28c6..c57953efb9 100644 --- a/.changeset/clever-tomatoes-change.md +++ b/.changeset/clever-tomatoes-change.md @@ -9,14 +9,14 @@ The scaffolder has been redesigned to be horizontally scalable and to persistent Each scaffolder task is given a unique task ID which is persisted in the database. Tasks are then picked up by a `TaskWorker` which performs the scaffolding steps. -Execution logs are also peristed in the database meaning you can now refresh the scaffolder task status page without losing information. +Execution logs are also persisted in the database meaning you can now refresh the scaffolder task status page without losing information. The task status page is now dynamically created based on the step information stored in the database. This allows for custom steps to be displayed once the next version of the scaffolder template schema is available. The task page is updated to display links to both the git repository and to the newly created catalog entity. -Component registration has moved from the frontend into a separate registration step executed by the `TaskWorker`. This requires that a `CatalogClient` is passed to the scaffoler backend instead of the old `CatalogEntityClient`. +Component registration has moved from the frontend into a separate registration step executed by the `TaskWorker`. This requires that a `CatalogClient` is passed to the scaffolder backend instead of the old `CatalogEntityClient`. Make sure to update `plugins/scaffolder.ts` From 86225bae7ed3f12677366c6c5d61e55ea00b0551 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 16 Feb 2021 13:27:39 +0100 Subject: [PATCH 073/270] Fix scaffolder dev routes Co-authored-by: Patrik Oldsberg --- plugins/scaffolder/dev/index.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/plugins/scaffolder/dev/index.tsx b/plugins/scaffolder/dev/index.tsx index 2bc7c76a3a..1501a1cd5e 100644 --- a/plugins/scaffolder/dev/index.tsx +++ b/plugins/scaffolder/dev/index.tsx @@ -19,7 +19,7 @@ import { createDevApp } from '@backstage/dev-utils'; import { discoveryApiRef, identityApiRef } from '@backstage/core'; import { CatalogClient } from '@backstage/catalog-client'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import { TemplateIndexPage, TemplatePage, TaskPage } from '../src/plugin'; +import { ScaffolderPage } from '../src/plugin'; import { ScaffolderClient, scaffolderApiRef } from '../src'; createDevApp() @@ -37,14 +37,6 @@ createDevApp() .addPage({ path: '/create', title: 'Create', - element: , - }) - .addPage({ - path: '/create/:templateName', - element: , - }) - .addPage({ - path: '/scaffolder/tasks/:taskId', - element: , + element: , }) .render(); From 478d87e6129cef8f00c4af838fc4a25b11eeffc6 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 16 Feb 2021 13:28:31 +0100 Subject: [PATCH 074/270] Pass CatalogApi instead of CatalogClient Co-authored-by: Patrik Oldsberg --- .../src/lib/catalog/CatalogEntityClient.ts | 4 +-- .../src/service/router.test.ts | 25 +++++++------------ .../scaffolder-backend/src/service/router.ts | 4 +-- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/plugins/scaffolder-backend/src/lib/catalog/CatalogEntityClient.ts b/plugins/scaffolder-backend/src/lib/catalog/CatalogEntityClient.ts index 13cfd32f6a..cea0e85148 100644 --- a/plugins/scaffolder-backend/src/lib/catalog/CatalogEntityClient.ts +++ b/plugins/scaffolder-backend/src/lib/catalog/CatalogEntityClient.ts @@ -15,14 +15,14 @@ */ import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; -import { CatalogClient } from '@backstage/catalog-client'; +import { CatalogApi } from '@backstage/catalog-client'; import { ConflictError, NotFoundError } from '@backstage/backend-common'; /** * A catalog client tailored for reading out entity data from the catalog. */ export class CatalogEntityClient { - constructor(private readonly catalogClient: CatalogClient) {} + constructor(private readonly catalogClient: CatalogApi) {} /** * Looks up a single template using a template name. diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index b5de493680..d8a236a213 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -39,16 +39,14 @@ import request from 'supertest'; import { createRouter } from './router'; import { Templaters, Preparers, Publishers } from '../scaffolder'; import Docker from 'dockerode'; -import { CatalogClient } from '@backstage/catalog-client'; - -jest.mock('@backstage/catalog-client'); -const MockedCatalogClient = CatalogClient as jest.Mock; +import { CatalogApi } from '@backstage/catalog-client'; jest.mock('dockerode'); -const generateEntityClient: any = (template: any) => ({ - findTemplate: () => Promise.resolve(template), -}); +const createCatalogClient = (templates: any[] = []) => + ({ + getEntities: async () => ({ items: templates }), + } as CatalogApi); function createDatabase(): PluginDatabaseManager { return SingleConnectionDatabaseManager.fromConfig( @@ -99,7 +97,6 @@ describe('createRouter - working directory', () => { }, }; - const mockedEntityClient = generateEntityClient(template); it('should throw an error when working directory does not exist or is not writable', async () => { mockAccess.mockImplementation(() => { throw new Error('access error'); @@ -113,9 +110,8 @@ describe('createRouter - working directory', () => { publishers: new Publishers(), config: new ConfigReader(workDirConfig('/path')), dockerClient: new Docker(), - entityClient: mockedEntityClient, database: createDatabase(), - catalogClient: new MockedCatalogClient(), + catalogClient: createCatalogClient([template]), }), ).rejects.toThrow('access error'); }); @@ -128,9 +124,8 @@ describe('createRouter - working directory', () => { publishers: new Publishers(), config: new ConfigReader(workDirConfig('/path')), dockerClient: new Docker(), - entityClient: mockedEntityClient, database: createDatabase(), - catalogClient: new MockedCatalogClient(), + catalogClient: createCatalogClient([template]), }); const app = express().use(router); @@ -158,9 +153,8 @@ describe('createRouter - working directory', () => { publishers: new Publishers(), config: new ConfigReader({}), dockerClient: new Docker(), - entityClient: mockedEntityClient, database: createDatabase(), - catalogClient: new MockedCatalogClient(), + catalogClient: createCatalogClient([template]), }); const app = express().use(router); @@ -229,9 +223,8 @@ describe('createRouter', () => { publishers: new Publishers(), config: new ConfigReader({}), dockerClient: new Docker(), - entityClient: generateEntityClient(template), database: createDatabase(), - catalogClient: new MockedCatalogClient(), + catalogClient: createCatalogClient([template]), }); app = express().use(router); }); diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index bb5fec306a..5adfee275e 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -48,7 +48,7 @@ import { NotFoundError, PluginDatabaseManager, } from '@backstage/backend-common'; -import { CatalogClient } from '@backstage/catalog-client'; +import { CatalogApi } from '@backstage/catalog-client'; export interface RouterOptions { preparers: PreparerBuilder; @@ -59,7 +59,7 @@ export interface RouterOptions { config: Config; dockerClient: Docker; database: PluginDatabaseManager; - catalogClient: CatalogClient; + catalogClient: CatalogApi; } export async function createRouter( From 08fa2176a30e62c325609c443ff56a3cf6f90958 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 16 Feb 2021 14:20:30 +0100 Subject: [PATCH 075/270] Update changesets Co-authored-by: Patrik Oldsberg --- .changeset/dingo-dongo.md | 2 - .changeset/weak-foxes-explain.md | 86 ++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 .changeset/weak-foxes-explain.md diff --git a/.changeset/dingo-dongo.md b/.changeset/dingo-dongo.md index 87c4172448..424c88f61c 100644 --- a/.changeset/dingo-dongo.md +++ b/.changeset/dingo-dongo.md @@ -11,8 +11,6 @@ The page is imported from the Scaffolder plugin and added to the `` } /> ``` -You can choose your own paths if you wish, as long as the `templateName` and `taskId` parameters are present. - The Catalog plugin has also been migrated to use an [external route reference](https://backstage.io/docs/plugins/composability#binding-external-routes-in-the-app) to dynamically link to the create component page. This means you need to migrate the catalog plugin to use the new extension components, as well as bind the external route. To use the new extension components, replace existing usage of the `CatalogRouter` with the following: diff --git a/.changeset/weak-foxes-explain.md b/.changeset/weak-foxes-explain.md new file mode 100644 index 0000000000..5536e1982c --- /dev/null +++ b/.changeset/weak-foxes-explain.md @@ -0,0 +1,86 @@ +--- +'@backstage/create-app': patch +--- + +**BREAKING CHANGE** + +The Scaffolder and Catalog plugins have been migrated to partially require use of the [new composability API](https://backstage.io/docs/plugins/composability). The Scaffolder used to register its pages using the deprecated route registration plugin API, but those registrations have been removed. This means you now need to add the Scaffolder plugin page to the app directly. + +The Catalog plugin has also been migrated to use an [external route reference](https://backstage.io/docs/plugins/composability#binding-external-routes-in-the-app) to dynamically link to the create component page. This means you need to migrate the catalog plugin to use the new extension components, as well as bind the external route. + +Apply the following changes to `packages/app/src/App.tsx`: + +```diff +-import { Router as CatalogRouter } from '@backstage/plugin-catalog'; ++import { ++ catalogPlugin, ++ CatalogIndexPage, ++ CatalogEntityPage, ++} from '@backstage/plugin-catalog'; ++import { scaffolderPlugin, ScaffolderPage } from '@backstage/plugin-scaffolder'; + +# The following addition to the app config allows the catalog plugin to link to the +# component creation page, i.e. the scaffolder. You can chose a different target if you want to. + const app = createApp({ + apis, + plugins: Object.values(plugins), ++ bindRoutes({ bind }) { ++ bind(catalogPlugin.externalRoutes, { ++ createComponent: scaffolderPlugin.routes.root, ++ }); ++ } + }); + +# Apply these changes within FlatRoutes. It is important to have migrated to using FlatRoutes +# for this to work, if you haven't done that yet, see the previous entries in this changelog. +- } +- /> ++ } /> ++ } ++ > ++ ++ + } /> ++ } /> +``` + +The scaffolder has been redesigned to be horizontally scalable and to persistently store task state and execution logs in the database. Component registration has moved from the frontend into a separate registration step executed by the `TaskWorker`. This requires that a `CatalogClient` is passed to the scaffolder backend instead of the old `CatalogEntityClient`. + +The default catalog client comes from the `@backstage/catalog-client`, which you need to add as a dependency in `packages/backend/package.json`. + +Once the dependency has been added, apply the following changes to`packages/backend/src/plugins/scaffolder.ts`: + +```diff + import { + CookieCutter, + createRouter, + Preparers, + Publishers, + CreateReactAppTemplater, + Templaters, +- CatalogEntityClient, + } from '@backstage/plugin-scaffolder-backend'; ++import { CatalogClient } from '@backstage/catalog-client'; + + const discovery = SingleHostDiscovery.fromConfig(config); +-const entityClient = new CatalogEntityClient({ discovery }); ++const catalogClient = new CatalogClient({ discoveryApi: discovery }) + + return await createRouter({ + preparers, + templaters, + publishers, + logger, + config, + dockerClient, +- entityClient, + database, ++ catalogClient, + }); +``` + +See the `@backstage/scaffolder-backend` changelog for more information about this change. From d934a66e3ecfb589d1abc0e012527b1ce7750a53 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 16 Feb 2021 14:21:34 +0100 Subject: [PATCH 076/270] core-api: Export ExternalRouteRef Co-authored-by: Patrik Oldsberg --- packages/core-api/src/routing/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/core-api/src/routing/index.ts b/packages/core-api/src/routing/index.ts index ad88f8ff02..100158c3c5 100644 --- a/packages/core-api/src/routing/index.ts +++ b/packages/core-api/src/routing/index.ts @@ -21,6 +21,10 @@ export type { MutableRouteRef, } from './types'; export { FlatRoutes } from './FlatRoutes'; -export { createRouteRef, createExternalRouteRef } from './RouteRef'; +export { + createRouteRef, + createExternalRouteRef, + ExternalRouteRef, +} from './RouteRef'; export type { RouteRefConfig } from './RouteRef'; export { useRouteRef } from './hooks'; From 0b94e83ba4297138f4824bbbb92b75cf66591984 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 16 Feb 2021 14:22:08 +0100 Subject: [PATCH 077/270] create-app: Add catalog-client dependency Co-authored-by: Patrik Oldsberg --- packages/create-app/package.json | 1 + packages/create-app/src/lib/versions.ts | 2 ++ .../templates/default-app/packages/backend/package.json.hbs | 1 + 3 files changed, 4 insertions(+) diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 5e4cf8ae11..acf61ab76a 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -53,6 +53,7 @@ "@backstage/plugin-app-backend": "^0.3.7", "@backstage/plugin-auth-backend": "^0.3.0", "@backstage/plugin-catalog": "^0.3.1", + "@backstage/catalog-client": "^0.3.6", "@backstage/plugin-catalog-backend": "^0.6.1", "@backstage/plugin-catalog-import": "^0.4.0", "@backstage/plugin-circleci": "^0.2.8", diff --git a/packages/create-app/src/lib/versions.ts b/packages/create-app/src/lib/versions.ts index e6a047921c..9a05a95ac0 100644 --- a/packages/create-app/src/lib/versions.ts +++ b/packages/create-app/src/lib/versions.ts @@ -31,6 +31,7 @@ leaving any imports in place. */ import { version as backendCommon } from '../../../backend-common/package.json'; +import { version as catalogClient } from '../../../catalog-client/package.json'; import { version as catalogModel } from '../../../catalog-model/package.json'; import { version as cli } from '../../../cli/package.json'; import { version as config } from '../../../config/package.json'; @@ -61,6 +62,7 @@ import { version as pluginUserSettings } from '../../../../plugins/user-settings export const packageVersions = { '@backstage/backend-common': backendCommon, + '@backstage/catalog-client': catalogClient, '@backstage/catalog-model': catalogModel, '@backstage/cli': cli, '@backstage/config': config, diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs index 3fed72f07b..daee44d8c7 100644 --- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs @@ -20,6 +20,7 @@ "app": "0.0.0", "@backstage/backend-common": "^{{version '@backstage/backend-common'}}", "@backstage/catalog-model": "^{{version '@backstage/catalog-model'}}", + "@backstage/catalog-client": "^{{version '@backstage/catalog-client'}}", "@backstage/config": "^{{version '@backstage/config'}}", "@backstage/plugin-app-backend": "^{{version '@backstage/plugin-app-backend'}}", "@backstage/plugin-auth-backend": "^{{version '@backstage/plugin-auth-backend'}}", From dc12852c9e78e9b9884f2de5420eed50596a25d0 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 16 Feb 2021 14:54:35 +0100 Subject: [PATCH 078/270] test-utils: Allow ExternalRouteRef on mountedRoutes Co-authored-by: Patrik Oldsberg --- .changeset/cyan-dingos-watch.md | 5 +++++ packages/test-utils/src/testUtils/appWrappers.tsx | 3 ++- .../catalog/src/components/CatalogPage/CatalogPage.test.tsx | 6 ++++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 .changeset/cyan-dingos-watch.md diff --git a/.changeset/cyan-dingos-watch.md b/.changeset/cyan-dingos-watch.md new file mode 100644 index 0000000000..7b15d30112 --- /dev/null +++ b/.changeset/cyan-dingos-watch.md @@ -0,0 +1,5 @@ +--- +'@backstage/test-utils': patch +--- + +Allow `ExternalRouteRef` instances to be passed as a route ref to `mountedRoutes`. diff --git a/packages/test-utils/src/testUtils/appWrappers.tsx b/packages/test-utils/src/testUtils/appWrappers.tsx index cc67a5b707..56d6e07805 100644 --- a/packages/test-utils/src/testUtils/appWrappers.tsx +++ b/packages/test-utils/src/testUtils/appWrappers.tsx @@ -22,6 +22,7 @@ import privateExports, { defaultSystemIcons, BootErrorPageProps, RouteRef, + ExternalRouteRef, createPlugin, createRoutableExtension, } from '@backstage/core-api'; @@ -62,7 +63,7 @@ type TestAppOptions = { * // ... * const link = useRouteRef(myRouteRef) */ - mountedRoutes?: { [path: string]: RouteRef }; + mountedRoutes?: { [path: string]: RouteRef | ExternalRouteRef }; }; /** diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index 6d4d18e4f2..accfe6eda4 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -33,6 +33,7 @@ import { MockStorageApi, wrapInTestApp } from '@backstage/test-utils'; import { fireEvent, render, waitFor } from '@testing-library/react'; import React from 'react'; import { EntityFilterGroupsProvider } from '../../filter'; +import { createComponentRouteRef } from '../../routes'; import { CatalogPage } from './CatalogPage'; describe('CatalogPage', () => { @@ -116,6 +117,11 @@ describe('CatalogPage', () => { > {children}, , + { + mountedRoutes: { + '/create': createComponentRouteRef, + }, + }, ), ); From 4de00ead3887a1dd8307745b1ab46d3e40a1f58a Mon Sep 17 00:00:00 2001 From: Taras Date: Tue, 16 Feb 2021 09:00:17 -0500 Subject: [PATCH 079/270] Report errors when creating AzureBlobPublisher and requirements are not satisfied --- .../__mocks__/@azure/storage-blob.ts | 16 ++++++-- .../stages/publish/azureBlobStorage.test.ts | 13 +++++- .../src/stages/publish/azureBlobStorage.ts | 40 ++++++++----------- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/packages/techdocs-common/__mocks__/@azure/storage-blob.ts b/packages/techdocs-common/__mocks__/@azure/storage-blob.ts index 29f3178efb..6633a71b2c 100644 --- a/packages/techdocs-common/__mocks__/@azure/storage-blob.ts +++ b/packages/techdocs-common/__mocks__/@azure/storage-blob.ts @@ -29,7 +29,9 @@ export class BlockBlobClient { uploadFile(source: string): Promise { return Promise.resolve({ _response: { - request: {} as any, + request: { + url: `https://example.blob.core.windows.net`, + } as any, status: 200, headers: {} as any, }, @@ -45,7 +47,9 @@ class BlockBlobClientFailUpload extends BlockBlobClient { uploadFile(source: string): Promise { return Promise.resolve({ _response: { - request: {} as any, + request: { + url: `https://example.blob.core.windows.net`, + } as any, status: 500, headers: {} as any, }, @@ -63,7 +67,9 @@ export class ContainerClient { getProperties(): Promise { return Promise.resolve({ _response: { - request: {} as any, + request: { + url: `https://example.blob.core.windows.net`, + } as any, status: 200, headers: {} as any, parsedHeaders: {}, @@ -80,7 +86,9 @@ class ContainerClientFailGetProperties extends ContainerClient { getProperties(): Promise { return Promise.resolve({ _response: { - request: {} as any, + request: { + url: `https://example.blob.core.windows.net`, + } as any, status: 404, headers: {} as any, parsedHeaders: {}, diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts index b37d6c9379..baeb644301 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts @@ -173,10 +173,19 @@ describe('error reporting', () => { const logger = createLogger(); - publisher = await AzureBlobStoragePublish.fromConfig(mockConfig, logger); + let error; + try { + publisher = await AzureBlobStoragePublish.fromConfig(mockConfig, logger); + } catch (e) { + error = e; + } + + expect(error).toBeInstanceOf(Error); expect(logger.error).toHaveBeenCalledWith( - `Could not read Azure Blob Storage container properties`, + expect.stringContaining( + `Could not retrieve metadata about the Azure Blob Storage container bad_container.`, + ), ); }); diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts index b0b2bfd855..f73417156a 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts @@ -79,31 +79,25 @@ export class AzureBlobStoragePublish implements PublisherBase { credential, ); - await storageClient - .getContainerClient(containerName) - .getProperties() - .then(result => { - if (result?._response?.status >= 400) { - logger.error( - 'Could not read Azure Blob Storage container properties', - ); - } else { - logger.info( - `Successfully connected to the Azure Blob Storage container ${containerName}.`, - ); - } - }) - .catch(reason => { - logger.error( - `Could not retrieve metadata about the Azure Blob Storage container ${containerName}. ` + - 'Make sure that the Azure project and container exist and the access key is setup correctly ' + - 'techdocs.publisher.azureBlobStorage.credentials defined in app config has correct permissions. ' + - 'Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage', - ); + try { + const metadata = await storageClient + .getContainerClient(containerName) + .getProperties(); + + if (metadata._response.status >= 400) { throw new Error( - `from Azure Blob Storage client library: ${reason.message}`, + `Failed to retrieve metadata from ${metadata._response.request.url} with status code ${metadata._response.status}.`, ); - }); + } + } catch (e) { + logger.error( + `Could not retrieve metadata about the Azure Blob Storage container ${containerName}. ` + + 'Make sure that the Azure project and container exist and the access key is setup correctly ' + + 'techdocs.publisher.azureBlobStorage.credentials defined in app config has correct permissions. ' + + 'Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage', + ); + throw new Error(`from Azure Blob Storage client library: ${e.message}`); + } return new AzureBlobStoragePublish(storageClient, containerName, logger); } From fdd33af1cf79e71d35f550f353a7d5cac126eddb Mon Sep 17 00:00:00 2001 From: ebarrios Date: Tue, 16 Feb 2021 15:10:00 +0100 Subject: [PATCH 080/270] Added githubApp authentication for the scaffolder prepare stage --- .../src/scaffolder/stages/prepare/github.ts | 28 ++++++++++++------- .../src/scaffolder/stages/publish/github.ts | 1 + 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.ts index 5b74ee81ca..d2b29b5d0b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.ts @@ -18,14 +18,20 @@ import path from 'path'; import { Git } from '@backstage/backend-common'; import { PreparerBase, PreparerOptions } from './types'; import parseGitUrl from 'git-url-parse'; -import { GitHubIntegrationConfig } from '@backstage/integration'; +import { + GitHubIntegrationConfig, + GithubCredentialsProvider, +} from '@backstage/integration'; export class GithubPreparer implements PreparerBase { static fromConfig(config: GitHubIntegrationConfig) { - return new GithubPreparer({ token: config.token }); + const credentialsProvider = GithubCredentialsProvider.create(config); + return new GithubPreparer({ credentialsProvider }); } - constructor(private readonly config: { token?: string }) {} + constructor( + private readonly config: { credentialsProvider: GithubCredentialsProvider }, + ) {} async prepare({ url, workspacePath, logger }: PreparerOptions) { const parsedGitUrl = parseGitUrl(url); @@ -36,13 +42,15 @@ export class GithubPreparer implements PreparerBase { parsedGitUrl.filepath ?? '', ); - const git = this.config.token - ? Git.fromAuth({ - username: 'x-access-token', - password: this.config.token, - logger, - }) - : Git.fromAuth({ logger }); + const { token } = await this.config.credentialsProvider.getCredentials({ + url, + }); + + const git = Git.fromAuth({ + username: 'x-access-token', + password: token, + logger, + }); await git.clone({ url: parsedGitUrl.toString('https'), diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts index 365b3f14cd..e71da7fb84 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts @@ -64,6 +64,7 @@ export class GithubPublisher implements PublisherBase { }); if (!token) { + logger.error(`Unable to adquire credentials for ${owner}/${name}`); return { remoteUrl: '', catalogInfoUrl: undefined }; } From d7abf2a2db57b7a38db419c0f71bfc8816ad24d0 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 16 Feb 2021 15:19:31 +0100 Subject: [PATCH 081/270] chore: fix some other wording --- docs/getting-started/contributors.md | 7 +++++++ docs/getting-started/create-an-app.md | 18 ++---------------- .../running-backstage-locally.md | 1 - microsite/sidebars.json | 2 +- mkdocs.yml | 2 +- 5 files changed, 11 insertions(+), 19 deletions(-) diff --git a/docs/getting-started/contributors.md b/docs/getting-started/contributors.md index e7e1986170..a0c3f3dc4c 100644 --- a/docs/getting-started/contributors.md +++ b/docs/getting-started/contributors.md @@ -18,6 +18,13 @@ grab the one for the origin like so: git clone git@github.com/backstage/backstage --depth 1 ``` +If you cloned a fork, you can add the upstream dependency like so: + +```bash +git remote add upstream git@github.com:backstage/backstage +git pull upstream master +``` + After you have cloned the Backstage repository, you should run the following commands once to set things up for development: diff --git a/docs/getting-started/create-an-app.md b/docs/getting-started/create-an-app.md index 0663d3faa1..9abb68d21b 100644 --- a/docs/getting-started/create-an-app.md +++ b/docs/getting-started/create-an-app.md @@ -177,20 +177,6 @@ the root directory: yarn workspace backend start ``` +Now you're free to hack away on your own Backstage installation! + ### Troubleshooting - -#### Cannot find module - -You may encounter an error similar to below: - -``` -internal/modules/cjs/loader.js:968 - throw err; - ^ - -Error: Cannot find module '../build/Debug/nodegit.node' -``` - -This can occur if an npm dependency is not completely installed. Because some -dependencies run a post-install script, ensure that both your npm and yarn -configs have the `ignore-scripts` flag set to `false`. diff --git a/docs/getting-started/running-backstage-locally.md b/docs/getting-started/running-backstage-locally.md index 6de9d234e3..fe68937c4e 100644 --- a/docs/getting-started/running-backstage-locally.md +++ b/docs/getting-started/running-backstage-locally.md @@ -104,7 +104,6 @@ The value of Backstage grows with every new plugin that gets added. Here is a collection of tutorials that will guide you through setting up and extending an instance of Backstage with your own plugins. -- [Development Environment](development-environment.md) - [Create a Backstage Plugin](../plugins/create-a-plugin.md) - [Structure of a Plugin](../plugins/structure-of-a-plugin.md) - [Utility APIs](../api/utility-apis.md) diff --git a/microsite/sidebars.json b/microsite/sidebars.json index ebce8d900c..090c967ff4 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -13,7 +13,7 @@ "Getting Started": [ "getting-started/index", "getting-started/running-backstage-locally", - "getting-started/development-environment", + "getting-started/contributing", "getting-started/create-an-app", { "type": "subcategory", diff --git a/mkdocs.yml b/mkdocs.yml index a303fd9978..37f7aa2e62 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -16,7 +16,7 @@ nav: - Getting started: - Getting Started: 'getting-started/index.md' - Running Backstage locally: 'getting-started/running-backstage-locally.md' - - Local development: 'getting-started/development-environment.md' + - Contributors: 'getting-started/contributors.md' - Demo deployment: https://backstage-demo.roadie.io - Production deployments: - Create an App: 'getting-started/create-an-app.md' From 549a859ac8ac201cab66ff3519fc95f310cab721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Tue, 16 Feb 2021 14:06:58 +0100 Subject: [PATCH 082/270] Improve pagerduty plugin --- .changeset/sour-shoes-perform.md | 5 + .../app/src/components/catalog/EntityPage.tsx | 6 +- .../components/Incident/IncidentListItem.tsx | 53 +++++++---- .../src/components/PagerDutyCard.tsx | 60 ++---------- .../src/components/TriggerButton/index.tsx | 94 +++++++++++++++++++ .../TriggerDialog/TriggerDialog.tsx | 18 ++-- plugins/pagerduty/src/components/constants.ts | 16 ++++ plugins/pagerduty/src/index.ts | 1 + 8 files changed, 177 insertions(+), 76 deletions(-) create mode 100644 .changeset/sour-shoes-perform.md create mode 100644 plugins/pagerduty/src/components/TriggerButton/index.tsx create mode 100644 plugins/pagerduty/src/components/constants.ts diff --git a/.changeset/sour-shoes-perform.md b/.changeset/sour-shoes-perform.md new file mode 100644 index 0000000000..e2361cb815 --- /dev/null +++ b/.changeset/sour-shoes-perform.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-pagerduty': minor +--- + +Improved the UI of the pagerduty plugin, and added a standalone TriggerButton diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index e849667c19..dcd0cb4c05 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -33,7 +33,7 @@ import { EntityLinksCard, EntityPageLayout, } from '@backstage/plugin-catalog'; -import { useEntity } from '@backstage/plugin-catalog-react'; +import { EntityProvider, useEntity } from '@backstage/plugin-catalog-react'; import { isPluginApplicableToEntity as isCircleCIAvailable, Router as CircleCIRouter, @@ -178,7 +178,9 @@ const ComponentOverviewContent = ({ entity }: { entity: Entity }) => (
{isPagerDutyAvailable(entity) && ( - + + + )} diff --git a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx index bf0d5d2587..2d4afa53cd 100644 --- a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx +++ b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx @@ -17,7 +17,6 @@ import React from 'react'; import { ListItem, - ListItemIcon, ListItemSecondaryAction, Tooltip, ListItemText, @@ -25,13 +24,16 @@ import { IconButton, Link, Typography, + Chip, } from '@material-ui/core'; -import { StatusError, StatusWarning } from '@backstage/core'; +import Done from '@material-ui/icons/Done'; +import Warning from '@material-ui/icons/Warning'; import { DateTime, Duration } from 'luxon'; import { Incident } from '../types'; import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser'; +import { BackstageTheme } from '@backstage/theme'; -const useStyles = makeStyles({ +const useStyles = makeStyles(theme => ({ denseListIcon: { marginRight: 0, display: 'flex', @@ -42,10 +44,21 @@ const useStyles = makeStyles({ listItemPrimary: { fontWeight: 'bold', }, - listItemIcon: { - minWidth: '1em', + warning: { + borderColor: theme.palette.status.warning, + color: theme.palette.status.warning, + '& *': { + color: theme.palette.status.warning, + }, }, -}); + error: { + borderColor: theme.palette.status.error, + color: theme.palette.status.error, + '& *': { + color: theme.palette.status.error, + }, + }, +})); type Props = { incident: Incident; @@ -62,19 +75,23 @@ export const IncidentListItem = ({ incident }: Props) => { return ( - - -
- {incident.status === 'triggered' ? ( - - ) : ( - - )} -
-
-
+ : } + className={ + incident.status === 'triggered' + ? classes.error + : classes.warning + } + /> + {incident.title} + + } primaryTypographyProps={{ variant: 'body1', className: classes.listItemPrimary, diff --git a/plugins/pagerduty/src/components/PagerDutyCard.tsx b/plugins/pagerduty/src/components/PagerDutyCard.tsx index e03fb0fdbe..5f2ae42aee 100644 --- a/plugins/pagerduty/src/components/PagerDutyCard.tsx +++ b/plugins/pagerduty/src/components/PagerDutyCard.tsx @@ -19,7 +19,6 @@ import { Entity } from '@backstage/catalog-model'; import { useEntity } from '@backstage/plugin-catalog-react'; import { Button, - makeStyles, Card, CardHeader, Divider, @@ -31,54 +30,31 @@ import { useAsync } from 'react-use'; import { Alert } from '@material-ui/lab'; import { pagerDutyApiRef, UnauthorizedError } from '../api'; import AlarmAddIcon from '@material-ui/icons/AlarmAdd'; -import { TriggerDialog } from './TriggerDialog'; import { MissingTokenError } from './Errors/MissingTokenError'; import WebIcon from '@material-ui/icons/Web'; - -const useStyles = makeStyles({ - triggerAlarm: { - paddingTop: 0, - paddingBottom: 0, - fontSize: '0.7rem', - textTransform: 'uppercase', - fontWeight: 600, - letterSpacing: 1.2, - lineHeight: 1.5, - '&:hover, &:focus, &.focus': { - backgroundColor: 'transparent', - textDecoration: 'none', - }, - }, -}); - -export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key'; +import { PAGERDUTY_INTEGRATION_KEY } from './constants'; +import { TriggerButton, useShowDialog } from './TriggerButton'; export const isPluginApplicableToEntity = (entity: Entity) => Boolean(entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY]); -type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; -}; - -export const PagerDutyCard = (_props: Props) => { - const classes = useStyles(); +export const PagerDutyCard = () => { const { entity } = useEntity(); const api = useApi(pagerDutyApiRef); - const [showDialog, setShowDialog] = useState(false); const [refreshIncidents, setRefreshIncidents] = useState(false); const integrationKey = entity.metadata.annotations![ PAGERDUTY_INTEGRATION_KEY ]; + const setShowDialog = useShowDialog()[1]; + + const showDialog = useCallback(() => { + setShowDialog(true); + }, [setShowDialog]); const handleRefresh = useCallback(() => { setRefreshIncidents(x => !x); }, []); - const handleDialog = useCallback(() => { - setShowDialog(x => !x); - }, []); - const { value: service, loading, error } = useAsync(async () => { const services = await api.getServiceByIntegrationKey(integrationKey); @@ -114,17 +90,8 @@ export const PagerDutyCard = (_props: Props) => { const triggerLink = { label: 'Create Incident', - action: ( - - ), - icon: , + action: , + icon: , }; return ( @@ -140,13 +107,6 @@ export const PagerDutyCard = (_props: Props) => { refreshIncidents={refreshIncidents} /> - ); diff --git a/plugins/pagerduty/src/components/TriggerButton/index.tsx b/plugins/pagerduty/src/components/TriggerButton/index.tsx new file mode 100644 index 0000000000..833e01d8aa --- /dev/null +++ b/plugins/pagerduty/src/components/TriggerButton/index.tsx @@ -0,0 +1,94 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 React, { useCallback, PropsWithChildren } from 'react'; +import { createGlobalState } from 'react-use'; +import { makeStyles, Button } from '@material-ui/core'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { BackstageTheme } from '@backstage/theme'; + +import { TriggerDialog } from '../TriggerDialog'; +import { PAGERDUTY_INTEGRATION_KEY } from '../constants'; + +export interface TriggerButtonProps { + design: 'link' | 'button'; + onIncidentCreated?: () => void; +} + +const useStyles = makeStyles(theme => ({ + buttonStyle: { + backgroundColor: theme.palette.error.main, + color: theme.palette.error.contrastText, + '&:hover': { + backgroundColor: theme.palette.error.dark, + }, + }, + triggerAlarm: { + paddingTop: 0, + paddingBottom: 0, + fontSize: '0.7rem', + textTransform: 'uppercase', + fontWeight: 600, + letterSpacing: 1.2, + lineHeight: 1.5, + '&:hover, &:focus, &.focus': { + backgroundColor: 'transparent', + textDecoration: 'none', + }, + }, +})); + +export const useShowDialog = createGlobalState(false); + +export function TriggerButton({ + design, + onIncidentCreated, + children, +}: PropsWithChildren) { + const { buttonStyle, triggerAlarm } = useStyles(); + const { entity } = useEntity(); + const [dialogShown = false, setDialogShown] = useShowDialog(); + + const showDialog = useCallback(() => { + setDialogShown(true); + }, [setDialogShown]); + const hideDialog = useCallback(() => { + setDialogShown(false); + }, [setDialogShown]); + + const integrationKey = entity.metadata.annotations![ + PAGERDUTY_INTEGRATION_KEY + ]; + + return ( + <> + + + + ); +} diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx index c390ed1f82..c2084a41ed 100644 --- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx @@ -35,7 +35,7 @@ type Props = { integrationKey: string; showDialog: boolean; handleDialog: () => void; - onIncidentCreated: () => void; + onIncidentCreated?: () => void; }; export const TriggerDialog = ({ @@ -69,11 +69,17 @@ export const TriggerDialog = ({ useEffect(() => { if (value) { - alertApi.post({ - message: `Alarm successfully triggered by ${userName}`, - }); - onIncidentCreated(); - handleDialog(); + (async () => { + alertApi.post({ + message: `Alarm successfully triggered by ${userName}`, + }); + + handleDialog(); + + // The pager duty API isn't always returning the newly created alarm immediately + await new Promise(resolve => setTimeout(resolve, 1000)); + onIncidentCreated?.(); + })(); } }, [value, alertApi, handleDialog, userName, onIncidentCreated]); diff --git a/plugins/pagerduty/src/components/constants.ts b/plugins/pagerduty/src/components/constants.ts new file mode 100644 index 0000000000..a7c32a362e --- /dev/null +++ b/plugins/pagerduty/src/components/constants.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key'; diff --git a/plugins/pagerduty/src/index.ts b/plugins/pagerduty/src/index.ts index 2dfedab164..92c3c8e73c 100644 --- a/plugins/pagerduty/src/index.ts +++ b/plugins/pagerduty/src/index.ts @@ -23,6 +23,7 @@ export { isPluginApplicableToEntity as isPagerDutyAvailable, PagerDutyCard, } from './components/PagerDutyCard'; +export { TriggerButton } from './components/TriggerButton'; export { PagerDutyClient, pagerDutyApiRef, From 34b710f0c0d796a365cf83187bd9aa22fcd09066 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 16 Feb 2021 16:08:08 +0100 Subject: [PATCH 083/270] test-utils: Attach mountPoint to Page Co-authored-by: Patrik Oldsberg --- packages/test-utils/src/testUtils/appWrappers.tsx | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/test-utils/src/testUtils/appWrappers.tsx b/packages/test-utils/src/testUtils/appWrappers.tsx index 56d6e07805..05cd0b8fd4 100644 --- a/packages/test-utils/src/testUtils/appWrappers.tsx +++ b/packages/test-utils/src/testUtils/appWrappers.tsx @@ -23,8 +23,7 @@ import privateExports, { BootErrorPageProps, RouteRef, ExternalRouteRef, - createPlugin, - createRoutableExtension, + attachComponentData, } from '@backstage/core-api'; import { RenderResult } from '@testing-library/react'; import { renderWithEffects } from '@backstage/test-utils-core'; @@ -110,16 +109,10 @@ export function wrapInTestApp( Wrapper = () => Component as React.ReactElement; } - const routePlugin = createPlugin({ id: 'mock-route-plugin' }); const routeElements = Object.entries(options.mountedRoutes ?? {}).map( ([path, routeRef]) => { - const PageComponent = () =>
Mounted at {path}
; - const Page = routePlugin.provide( - createRoutableExtension({ - component: async () => PageComponent, - mountPoint: routeRef, - }), - ); + const Page = () =>
Mounted at {path}
; + attachComponentData(Page, 'core.mountPoint', routeRef); return } />; }, ); From 8e348f05dbade1a07c94576887483db60c1a7727 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 16 Feb 2021 16:11:08 +0100 Subject: [PATCH 084/270] Update docs/features/software-catalog/installation.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw --- docs/features/software-catalog/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-catalog/installation.md b/docs/features/software-catalog/installation.md index 309951d58c..039c75391a 100644 --- a/docs/features/software-catalog/installation.md +++ b/docs/features/software-catalog/installation.md @@ -55,7 +55,7 @@ import { ``` The catalog plugin also has one external route that needs to be bound for it to -functions, the `createComponent` route which should link to the page where the +function: the `createComponent` route which should link to the page where the user can create components. In a typical setup the create component route will be linked to the Scaffolder plugins template index page: From 6ea4db0b2c115fe57b8276c6e17e34e9fd36fa7f Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 16 Feb 2021 16:11:16 +0100 Subject: [PATCH 085/270] Update docs/features/software-catalog/installation.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw --- docs/features/software-catalog/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-catalog/installation.md b/docs/features/software-catalog/installation.md index 039c75391a..ae8c671ed1 100644 --- a/docs/features/software-catalog/installation.md +++ b/docs/features/software-catalog/installation.md @@ -57,7 +57,7 @@ import { The catalog plugin also has one external route that needs to be bound for it to function: the `createComponent` route which should link to the page where the user can create components. In a typical setup the create component route will -be linked to the Scaffolder plugins template index page: +be linked to the Scaffolder plugin's template index page: ```ts import { catalogPlugin } from '@backstage/plugin-catalog'; From 02c5ce979c49aac15b6e4dde159905598d75e79a Mon Sep 17 00:00:00 2001 From: Taras Date: Tue, 16 Feb 2021 12:52:32 -0500 Subject: [PATCH 086/270] Throwing errors --- .../stages/publish/azureBlobStorage.test.ts | 23 +++++++--- .../src/stages/publish/azureBlobStorage.ts | 43 +++++++++++++------ 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts index baeb644301..62c81df5c4 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts @@ -124,7 +124,7 @@ describe('publishing with valid credentials', () => { }) .catch(error => expect(error.message).toContain( - 'Unable to upload file(s) to Azure Blob Storage. Error Failed to read template directory: ENOENT, no such file or directory', + 'Unable to upload file(s) to Azure Blob Storage. Failed to read template directory: ENOENT, no such file or directory', ), ); mockFs.restore(); @@ -219,13 +219,24 @@ describe('error reporting', () => { }, }); - await publisher.publish({ - entity, - directory: entityRootDir, - }); + let error; + try { + await publisher.publish({ + entity, + directory: entityRootDir, + }); + } catch (e) { + error = e; + } + + expect(error.message).toContain( + `Unable to upload file(s) to Azure Blob Storage.`, + ); expect(logger.error).toHaveBeenCalledWith( - `Unable to upload 1 file(s) to Azure Blob Storage.`, + expect.stringContaining( + `Unable to upload file(s) to Azure Blob Storage. Upload failed for test-namespace/TestKind/test-component-name/index.html with status code 500`, + ), ); mockFs.restore(); diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts index f73417156a..07e1053b99 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts @@ -80,13 +80,13 @@ export class AzureBlobStoragePublish implements PublisherBase { ); try { - const metadata = await storageClient + const response = await storageClient .getContainerClient(containerName) .getProperties(); - if (metadata._response.status >= 400) { + if (response._response.status >= 400) { throw new Error( - `Failed to retrieve metadata from ${metadata._response.request.url} with status code ${metadata._response.status}.`, + `Failed to retrieve metadata from ${response._response.request.url} with status code ${response._response.status}.`, ); } } catch (e) { @@ -137,31 +137,46 @@ export class AzureBlobStoragePublish implements PublisherBase { `${entityRootDir}/${relativeFilePath}`, ); // Azure Blob Storage Container file relative path - return limiter(() => { - return this.storageClient + return limiter(async () => { + const response = await this.storageClient .getContainerClient(this.containerName) .getBlockBlobClient(destination) .uploadFile(filePath); + + if (response._response.status >= 400) { + return { + ...response, + error: new Error( + `Upload failed for ${filePath} with status code ${response._response.status}`, + ), + }; + } + return { + ...response, + error: undefined, + }; }); }); - let responses: BlobUploadCommonResponse[] = []; + const responses = await Promise.all(promises); - responses = await Promise.all(promises); - - const failed = responses.filter(r => r?._response?.status >= 400); + const failed = responses.filter(r => r.error); if (failed.length === 0) { this.logger.info( - `Successfully uploaded all the generated files for Entity ${entity.metadata.name}. Total number of files: ${allFilesToUpload.length}`, + `Successfully uploaded the ${responses.length} generated file(s) for Entity ${entity.metadata.name}. Total number of files: ${allFilesToUpload.length}`, ); } else { - const errorMessage = `Unable to upload ${failed.length} file(s) to Azure Blob Storage.`; - this.logger.error(errorMessage); + throw new Error( + failed + .map(r => r.error?.message) + .filter(Boolean) + .join(' '), + ); } } catch (e) { - const errorMessage = `Unable to upload file(s) to Azure Blob Storage. Error ${e.message}`; + const errorMessage = `Unable to upload file(s) to Azure Blob Storage. ${e.message}`; this.logger.error(errorMessage); - return; + throw new Error(errorMessage); } } From d1af7b4fbc71622c2cdb7e0903e5805a8191b345 Mon Sep 17 00:00:00 2001 From: Taras Date: Tue, 16 Feb 2021 12:58:26 -0500 Subject: [PATCH 087/270] Remove unnecessary import --- packages/techdocs-common/src/stages/publish/azureBlobStorage.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts index 07e1053b99..7000e9f28c 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts @@ -17,7 +17,6 @@ import platformPath from 'path'; import express from 'express'; import { BlobServiceClient, - BlobUploadCommonResponse, StorageSharedKeyCredential, } from '@azure/storage-blob'; import { DefaultAzureCredential } from '@azure/identity'; From 76db06fa5d730ef6002edaed7a40af0972124449 Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Tue, 16 Feb 2021 13:00:14 -0500 Subject: [PATCH 088/270] Update quiet-dots-mix.md --- .changeset/quiet-dots-mix.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/quiet-dots-mix.md b/.changeset/quiet-dots-mix.md index a83278671e..b847c040c5 100644 --- a/.changeset/quiet-dots-mix.md +++ b/.changeset/quiet-dots-mix.md @@ -2,4 +2,4 @@ '@backstage/techdocs-common': patch --- -Changed AzureBlobStorage to show errors when upload fails +Improved error reporting in AzureBlobStorage to better surface failures From 8721f06ebea5ef979bd15698e52a244b2feebce8 Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Tue, 16 Feb 2021 13:01:35 -0500 Subject: [PATCH 089/270] Update quiet-dots-mix.md --- .changeset/quiet-dots-mix.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.changeset/quiet-dots-mix.md b/.changeset/quiet-dots-mix.md index b847c040c5..97926c9d0f 100644 --- a/.changeset/quiet-dots-mix.md +++ b/.changeset/quiet-dots-mix.md @@ -2,4 +2,5 @@ '@backstage/techdocs-common': patch --- -Improved error reporting in AzureBlobStorage to better surface failures +Improved error reporting in AzureBlobStorage to surface errors when fetching metadata and uploading files fails. + From a2518e3dbe4aab77dfb4e050a21c376d976d38ee Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Tue, 16 Feb 2021 19:03:07 +0100 Subject: [PATCH 090/270] docs(TechDocs): Update docs around AWS SDK downgrade to v2 --- docs/features/techdocs/using-cloud-storage.md | 26 ++++++++++--------- .../src/stages/publish/awsS3.ts | 15 ++++++----- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md index de2d505c87..7d735c72ab 100644 --- a/docs/features/techdocs/using-cloud-storage.md +++ b/docs/features/techdocs/using-cloud-storage.md @@ -142,6 +142,8 @@ variables** You should follow the [AWS security best practices guide for authentication](https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html). +TechDocs needs access to read files and metadata of the S3 bucket. + If the environment variables - `AWS_ACCESS_KEY_ID` @@ -149,15 +151,21 @@ If the environment variables - `AWS_REGION` are set and can be used to access the bucket you created in step 2, they will be -used by the AWS SDK v3 Node.js client for authentication. -[Refer to the official documentation.](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-environment.html) +used by the AWS SDK V2 Node.js client for authentication. +[Refer to the official documentation.](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html) If the environment variables are missing, the AWS SDK tries to read the `~/.aws/credentials` file for credentials. -[Refer to the official documentation.](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-shared.html) +[Refer to the official documentation.](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-shared.html) -Note that the region of the bucket has to be set for the AWS SDK to work. -[See this](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-region.html). +If you are using Amazon EC2 instance to deploy Backstage, you do not need to +obtain the access keys separately. They can be made available in the environment +automatically by defining appropriate IAM role with access to the bucket. Read +more +[here](https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html#use-roles). + +The AWS Region of the bucket is optional since TechDocs uses AWS SDK V2 and not +V3. **3b. Authentication using app-config.yaml** @@ -181,13 +189,7 @@ techdocs: ``` Refer to the -[official AWS documentation for obtaining the credentials](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/getting-your-credentials.html). - -Note: If you are using Amazon EC2 instance to deploy Backstage, you do not need -to obtain the access keys separately. They can be made available in the -environment automatically by defining appropriate IAM role with access to the -bucket. Read more -[here](https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html#use-roles). +[official AWS documentation for obtaining the credentials](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/getting-your-credentials.html). **4. That's it!** diff --git a/packages/techdocs-common/src/stages/publish/awsS3.ts b/packages/techdocs-common/src/stages/publish/awsS3.ts index 6c0cadcfae..b0b29e8654 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.ts @@ -52,10 +52,13 @@ export class AwsS3Publish implements PublisherBase { ); } - // Credentials is an optional config. If missing, default AWS environment variables - // or AWS shared credentials file at ~/.aws/credentials will be used to authenticate - // https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-environment.html - // https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-shared.html + // Credentials is an optional config. If missing, the default ways of authenticating AWS SDK V2 will be used. + // 1. AWS environment variables + // https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html + // 2. AWS shared credentials file at ~/.aws/credentials + // https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-shared.html + // 3. IAM Roles for EC2 + // https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-iam.html const credentials = config.getOptionalConfig( 'techdocs.publisher.awsS3.credentials', ); @@ -67,9 +70,7 @@ export class AwsS3Publish implements PublisherBase { } // AWS Region is an optional config. If missing, default AWS env variable AWS_REGION - // or AWS shared credentials file at ~/.aws/credentials will be used. Any way, AWS SDK v3 client needs - // to have the AWS Region information for it to work. - // https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-region.html + // or AWS shared credentials file at ~/.aws/credentials will be used. const region = config.getOptionalString('techdocs.publisher.awsS3.region'); const storageClient = new aws.S3({ From 874864e38c486e9c45c74aafe639763a48715fc7 Mon Sep 17 00:00:00 2001 From: Taras Date: Tue, 16 Feb 2021 13:12:14 -0500 Subject: [PATCH 091/270] Fix formatting of changeset --- .changeset/quiet-dots-mix.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/quiet-dots-mix.md b/.changeset/quiet-dots-mix.md index 97926c9d0f..fc4e550afd 100644 --- a/.changeset/quiet-dots-mix.md +++ b/.changeset/quiet-dots-mix.md @@ -3,4 +3,3 @@ --- Improved error reporting in AzureBlobStorage to surface errors when fetching metadata and uploading files fails. - From c6655413db29c70e0984177b0c1f675c582f05ef Mon Sep 17 00:00:00 2001 From: Taras Date: Tue, 16 Feb 2021 13:15:04 -0500 Subject: [PATCH 092/270] Renamed changeset to aply code owners --- .../{quiet-dots-mix.md => techdocs-improve-error-reporting.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .changeset/{quiet-dots-mix.md => techdocs-improve-error-reporting.md} (100%) diff --git a/.changeset/quiet-dots-mix.md b/.changeset/techdocs-improve-error-reporting.md similarity index 100% rename from .changeset/quiet-dots-mix.md rename to .changeset/techdocs-improve-error-reporting.md From cc2c047f15791f90c5635acd81a190a5d7c62f5f Mon Sep 17 00:00:00 2001 From: ebarrios Date: Tue, 16 Feb 2021 19:50:56 +0100 Subject: [PATCH 093/270] Reverted change to preparer function to use Git.fromAuth({ logger }) when the credentials provider can not get a token --- .../src/scaffolder/stages/prepare/github.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.ts index d2b29b5d0b..81ccdeb026 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.ts @@ -46,11 +46,13 @@ export class GithubPreparer implements PreparerBase { url, }); - const git = Git.fromAuth({ - username: 'x-access-token', - password: token, - logger, - }); + const git = token + ? Git.fromAuth({ + username: 'x-access-token', + password: token, + logger, + }) + : Git.fromAuth({ logger }); await git.clone({ url: parsedGitUrl.toString('https'), From 36a4ede27516becd0683ef59393bcd82dd3ea39c Mon Sep 17 00:00:00 2001 From: ebarrios Date: Tue, 16 Feb 2021 19:56:18 +0100 Subject: [PATCH 094/270] Change error message in the publish func for when the credentials provider can not get a token --- .../scaffolder-backend/src/scaffolder/stages/publish/github.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts index e71da7fb84..4de928c32b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts @@ -64,7 +64,7 @@ export class GithubPublisher implements PublisherBase { }); if (!token) { - logger.error(`Unable to adquire credentials for ${owner}/${name}`); + logger.error(`No token could be acquired for URL: ${values.storePath}`); return { remoteUrl: '', catalogInfoUrl: undefined }; } From 92d7a8eedfbed52a81174aac5d795d3b263a2c39 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 16 Feb 2021 23:48:03 +0100 Subject: [PATCH 095/270] switch docker build to use backend:bundle --- .dockerignore | 9 ++++++--- package.json | 2 +- packages/backend/Dockerfile | 17 +++++++++++++---- packages/backend/package.json | 4 ++-- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/.dockerignore b/.dockerignore index 7f7dee96c5..e34ae2c37d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,8 @@ .git +docs +cypress +microsite node_modules -packages/*/node_modules -plugins/*/node_modules -plugins/*/dist +packages +!packages/backend/dist +plugins diff --git a/package.json b/package.json index 59fe9d8f18..6c39cf2953 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "lint:all": "lerna run lint --", "lint:type-deps": "node scripts/check-type-dependencies.js", "docgen": "lerna run docgen", - "docker-build": "yarn tsc && yarn workspace example-backend build-image", + "docker-build": "yarn tsc && yarn workspace example-backend build --build-dependencies && yarn workspace example-backend build-image", "create-plugin": "backstage-cli create-plugin --scope backstage --no-private", "remove-plugin": "backstage-cli remove-plugin", "release": "changeset version && yarn diff --yes && yarn prettier --write '{packages,plugins}/*/{package.json,CHANGELOG.md}' && yarn install --frozen-lockfile", diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile index f1bc764fd0..51ebf2980d 100644 --- a/packages/backend/Dockerfile +++ b/packages/backend/Dockerfile @@ -1,16 +1,25 @@ -FROM node:14-buster +# This dockerfile builds an image for the backend package. +# It should be executed with the root of the repo as docker context. +# +# Before building this image, be sure to have run the following commands in the repo root: +# +# yarn install +# yarn tsc +# yarn build -WORKDIR /usr/src/app +FROM node:14-buster-slim + +WORKDIR /app # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. # The skeleton contains the package.json of each package in the monorepo, # and along with yarn.lock and the root package.json, that's enough to run yarn install. -ADD yarn.lock package.json skeleton.tar ./ +ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" # This will copy the contents of the dist-workspace when running the build-image command. # Do not use this Dockerfile outside of that command, as it will copy in the source code instead. -COPY . . +ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./ CMD ["node", "packages/backend"] diff --git a/packages/backend/package.json b/packages/backend/package.json index e5271f0109..a4765baa47 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -18,8 +18,8 @@ "backstage" ], "scripts": { - "build": "backstage-cli backend:build", - "build-image": "backstage-cli backend:build-image --build --tag example-backend", + "build": "backstage-cli backend:bundle", + "build-image": "docker build ../.. -f Dockerfile --tag example-backend", "start": "backstage-cli backend:dev", "lint": "backstage-cli lint", "test": "backstage-cli test", From d50e9b81e30d1d41825e01a1a9e38b260e4accea Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 16 Feb 2021 23:48:55 +0100 Subject: [PATCH 096/270] create-app: switch to using backend:bundle --- .changeset/metal-spoons-change.md | 58 +++++++++++++++++++ .../templates/default-app/.dockerignore | 5 ++ .../default-app/packages/backend/Dockerfile | 19 ++++-- .../packages/backend/package.json.hbs | 4 +- 4 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 .changeset/metal-spoons-change.md create mode 100644 packages/create-app/templates/default-app/.dockerignore diff --git a/.changeset/metal-spoons-change.md b/.changeset/metal-spoons-change.md new file mode 100644 index 0000000000..4e1dddd82e --- /dev/null +++ b/.changeset/metal-spoons-change.md @@ -0,0 +1,58 @@ +--- +'@backstage/create-app': patch +--- + +Updated docker build to use `backstage-cli backend:bundle` instead of `backstage-cli backend:build-image`. + +To apply this change to an existing applications, change the following in `packages/backend/package.json`: + +```diff +- "build": "backstage-cli backend:build", +- "build-image": "backstage-cli backend:build-image --build --tag backstage", ++ "build": "backstage-cli backend:bundle", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage", +``` + +Note that the backend build is switched to `backend:bundle`, and the `build-image` script simply calls `docker build`. This means the `build-image` script no longer builds all packages, so you have to run `yarn build` in the root first. + +On order to work with the new build method, the `Dockerfile` at `packages/backend/Dockerfile` have been updated with the following contents: + +```dockerfile +# This dockerfile builds an image for the backend package. +# It should be executed with the root of the repo as docker context. +# +# Before building this image, be sure to have run the following commands in the repo root: +# +# yarn install +# yarn tsc +# yarn build + +FROM node:14-buster-slim + +WORKDIR /app + +# Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +# The skeleton contains the package.json of each package in the monorepo, +# and along with yarn.lock and the root package.json, that's enough to run yarn install. +ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + +RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" + +# This will copy the contents of the dist-workspace when running the build-image command. +# Do not use this Dockerfile outside of that command, as it will copy in the source code instead. +ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./ + +CMD ["node", "packages/backend"] +``` + +Note that the base image has been switched from `node:14-buster` to `node:14-buster-slim`, significantly reducing the image size. This is enabled by the removal of the `nodegit` dependency, so if you are still using this in your project you will have to stick with the `node:14-buster` base image. + +A `.dockerignore` file has been added to the root of the repo as well, in order to keep the docker context upload small. It lives in the root of the repo with the following contents: + +```gitignore +.git +node_modules +packages +!packages/backend/dist +plugins +``` diff --git a/packages/create-app/templates/default-app/.dockerignore b/packages/create-app/templates/default-app/.dockerignore new file mode 100644 index 0000000000..63c9c34286 --- /dev/null +++ b/packages/create-app/templates/default-app/.dockerignore @@ -0,0 +1,5 @@ +.git +node_modules +packages +!packages/backend/dist +plugins diff --git a/packages/create-app/templates/default-app/packages/backend/Dockerfile b/packages/create-app/templates/default-app/packages/backend/Dockerfile index 50514713d3..51ebf2980d 100644 --- a/packages/create-app/templates/default-app/packages/backend/Dockerfile +++ b/packages/create-app/templates/default-app/packages/backend/Dockerfile @@ -1,16 +1,25 @@ -FROM node:12-buster +# This dockerfile builds an image for the backend package. +# It should be executed with the root of the repo as docker context. +# +# Before building this image, be sure to have run the following commands in the repo root: +# +# yarn install +# yarn tsc +# yarn build -WORKDIR /usr/src/app +FROM node:14-buster-slim + +WORKDIR /app # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. # The skeleton contains the package.json of each package in the monorepo, # and along with yarn.lock and the root package.json, that's enough to run yarn install. -ADD yarn.lock package.json skeleton.tar ./ +ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" # This will copy the contents of the dist-workspace when running the build-image command. # Do not use this Dockerfile outside of that command, as it will copy in the source code instead. -COPY . . +ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./ -CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"] +CMD ["node", "packages/backend"] diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs index 3fed72f07b..1af8276316 100644 --- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs @@ -8,8 +8,8 @@ "node": "12 || 14" }, "scripts": { - "build": "backstage-cli backend:build", - "build-image": "backstage-cli backend:build-image --build --tag backstage", + "build": "backstage-cli backend:bundle", + "build-image": "docker build ../.. -f Dockerfile --tag backstage", "start": "backstage-cli backend:dev", "lint": "backstage-cli lint", "test": "backstage-cli test", From d9685d0b1e62b3bc0df0a686edf0f7025abbfb45 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 17 Feb 2021 08:59:57 +0100 Subject: [PATCH 097/270] Update plugins/scaffolder/src/api.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw --- plugins/scaffolder/src/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index 4978f7fafa..20fca5b3f2 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -45,7 +45,7 @@ export interface 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 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 */ scaffold(templateName: string, values: Record): Promise; From b6cf3cfc3c72200583d6519d6f8cd3eeec6b1855 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 17 Feb 2021 09:06:03 +0100 Subject: [PATCH 098/270] Update plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts Co-authored-by: Adam Harvey --- plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 6eac36b3f3..e717fcae1b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -53,7 +53,7 @@ export class TaskWorker { ); await fs.ensureDir(workspacePath); await task.emitLog( - `Starting up work with ${task.spec.steps.length} steps`, + `Starting up task with ${task.spec.steps.length} steps`, ); const templateCtx: { From 7c5b0e206faf6f32ed84fed9dbd3a75c621866d1 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 17 Feb 2021 09:08:12 +0100 Subject: [PATCH 099/270] Update plugins/scaffolder-backend/src/service/router.ts Co-authored-by: Adam Harvey --- plugins/scaffolder-backend/src/service/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 5adfee275e..3d10bbd101 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -259,7 +259,7 @@ export async function createRouter( const { taskId } = req.params; const task = await taskBroker.get(taskId); if (!task) { - throw new NotFoundError(`task with id ${taskId} does not exist`); + throw new NotFoundError(`Task with id ${taskId} does not exist`); } res.status(200).json(task); }) From 75e40350fa94672baad0aa37a053dacce25a6583 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 17 Feb 2021 09:16:24 +0100 Subject: [PATCH 100/270] scaffolder: Use default ErrorPage --- .../components/TaskNotFound/TaskNotFound.tsx | 59 ------------------- .../src/components/TaskPage/TaskPage.tsx | 9 ++- 2 files changed, 6 insertions(+), 62 deletions(-) delete mode 100644 plugins/scaffolder/src/components/TaskNotFound/TaskNotFound.tsx diff --git a/plugins/scaffolder/src/components/TaskNotFound/TaskNotFound.tsx b/plugins/scaffolder/src/components/TaskNotFound/TaskNotFound.tsx deleted file mode 100644 index 24ac8c357d..0000000000 --- a/plugins/scaffolder/src/components/TaskNotFound/TaskNotFound.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 React from 'react'; -import { Grid, Typography } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; -import { BackstageTheme } from '@backstage/theme'; - -const useStyles = makeStyles(theme => ({ - container: { - paddingTop: theme.spacing(24), - paddingLeft: theme.spacing(8), - [theme.breakpoints.down('xs')]: { - padding: theme.spacing(2), - }, - }, - title: { - paddingBottom: theme.spacing(2), - [theme.breakpoints.down('xs')]: { - fontSize: 32, - }, - }, - body: { - paddingBottom: theme.spacing(6), - [theme.breakpoints.down('xs')]: { - paddingBottom: theme.spacing(5), - }, - }, -})); - -export const TaskNotFound = () => { - const classes = useStyles(); - - return ( - - - - Task not found - - - No task found with this ID - - - - ); -}; diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index fbf2a09a21..b2f1773d0b 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Page, Header, Lifecycle, Content } from '@backstage/core'; +import { Page, Header, Lifecycle, Content, ErrorPage } from '@backstage/core'; import React, { useState, useEffect, memo, useMemo } from 'react'; import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'; import Stepper from '@material-ui/core/Stepper'; @@ -43,7 +43,6 @@ import Cancel from '@material-ui/icons/Cancel'; import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord'; import { entityRoute } from '@backstage/plugin-catalog-react'; import { parseEntityName } from '@backstage/catalog-model'; -import { TaskNotFound } from '../TaskNotFound/TaskNotFound'; // typings are wrong for this library, so fallback to not parsing types. const humanizeDuration = require('humanize-duration'); @@ -278,7 +277,11 @@ export const TaskPage = () => { /> {taskNotFound ? ( - + ) : (
From fd7e3ae8a21fb4ca2919250e63fb1cae831b887e Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 17 Feb 2021 09:30:42 +0100 Subject: [PATCH 101/270] scaffolder: replace clsx with classNames --- plugins/scaffolder/package.json | 1 - plugins/scaffolder/src/components/TaskPage/TaskPage.tsx | 4 ++-- yarn.lock | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index f602f01b5b..9290d1d4bc 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -42,7 +42,6 @@ "@rjsf/core": "^2.4.0", "@rjsf/material-ui": "^2.4.0", "classnames": "^2.2.6", - "clsx": "^1.1.1", "git-url-parse": "^11.4.4", "humanize-duration": "^3.25.1", "luxon": "^1.25.0", diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index b2f1773d0b..d4f75e51af 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -37,12 +37,12 @@ import { import { Status } from '../../types'; import { DateTime, Interval } from 'luxon'; import { useInterval } from 'react-use'; -import clsx from 'clsx'; import Check from '@material-ui/icons/Check'; import Cancel from '@material-ui/icons/Cancel'; import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord'; import { entityRoute } from '@backstage/plugin-catalog-react'; import { parseEntityName } from '@backstage/catalog-model'; +import classNames from 'classnames'; // typings are wrong for this library, so fallback to not parsing types. const humanizeDuration = require('humanize-duration'); @@ -143,7 +143,7 @@ function TaskStepIconComponent(props: StepIconProps) { return (
Date: Wed, 17 Feb 2021 09:31:38 +0100 Subject: [PATCH 102/270] create-app: add missing semicolon --- .../default-app/packages/backend/src/plugins/scaffolder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts index 84657948e5..6f42aaa327 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts @@ -29,7 +29,7 @@ export default async function createPlugin({ const dockerClient = new Docker(); const discovery = SingleHostDiscovery.fromConfig(config); - const catalogClient = new CatalogClient({ discoveryApi: discovery }) + const catalogClient = new CatalogClient({ discoveryApi: discovery }); return await createRouter({ preparers, From f358d119107f639b1824306e62244d3f1a639428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Wed, 17 Feb 2021 10:16:52 +0100 Subject: [PATCH 103/270] Removed unused import --- plugins/pagerduty/src/components/PagerDutyCard.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/plugins/pagerduty/src/components/PagerDutyCard.tsx b/plugins/pagerduty/src/components/PagerDutyCard.tsx index 5f2ae42aee..7ec425a062 100644 --- a/plugins/pagerduty/src/components/PagerDutyCard.tsx +++ b/plugins/pagerduty/src/components/PagerDutyCard.tsx @@ -17,13 +17,7 @@ import React, { useState, useCallback } from 'react'; import { useApi, Progress, HeaderIconLinkRow } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; import { useEntity } from '@backstage/plugin-catalog-react'; -import { - Button, - Card, - CardHeader, - Divider, - CardContent, -} from '@material-ui/core'; +import { Card, CardHeader, Divider, CardContent } from '@material-ui/core'; import { Incidents } from './Incident'; import { EscalationPolicy } from './Escalation'; import { useAsync } from 'react-use'; From 010bfcca4388b7b2df4a665d82541e7bc84efed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Wed, 17 Feb 2021 11:01:16 +0100 Subject: [PATCH 104/270] Added missing dep on @types/react --- plugins/pagerduty/package.json | 1 + yarn.lock | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index c430075b95..b009a90e03 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -37,6 +37,7 @@ "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", + "@types/react": "^17.0.2", "classnames": "^2.2.6", "luxon": "1.25.0", "react": "^16.13.1", diff --git a/yarn.lock b/yarn.lock index e2c8103d8e..042bf77d46 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6620,6 +6620,14 @@ dependencies: csstype "^2.2.0" +"@types/react@^17.0.2": + version "17.0.2" + resolved "https://registry.npmjs.org/@types/react/-/react-17.0.2.tgz#3de24c4efef902dd9795a49c75f760cbe4f7a5a8" + integrity sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + "@types/reactcss@*": version "1.2.3" resolved "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.3.tgz#af28ae11bbb277978b99d04d1eedfd068ca71834" @@ -10695,6 +10703,11 @@ csstype@^2.2.0, csstype@^2.5.2, csstype@^2.5.5, csstype@^2.5.7, csstype@^2.6.5, resolved "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098" integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q== +csstype@^3.0.2: + version "3.0.6" + resolved "https://registry.npmjs.org/csstype/-/csstype-3.0.6.tgz#865d0b5833d7d8d40f4e5b8a6d76aea3de4725ef" + integrity sha512-+ZAmfyWMT7TiIlzdqJgjMb7S4f1beorDbWbsocyK4RaiqA5RTX3K14bnBWmmA9QEM0gRdsjyyrEmcyga8Zsxmw== + csv-generate@^3.2.4: version "3.2.4" resolved "https://registry.npmjs.org/csv-generate/-/csv-generate-3.2.4.tgz#440dab9177339ee0676c9e5c16f50e2b3463c019" From b0501f8348f5988f93175ff1ce8ea81eed087dbd Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Wed, 17 Feb 2021 11:08:27 +0100 Subject: [PATCH 105/270] docs(TechDocs): Add more context with AWS docs hyperlinks --- docs/features/techdocs/using-cloud-storage.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md index 7d735c72ab..32c44e9595 100644 --- a/docs/features/techdocs/using-cloud-storage.md +++ b/docs/features/techdocs/using-cloud-storage.md @@ -152,7 +152,7 @@ If the environment variables are set and can be used to access the bucket you created in step 2, they will be used by the AWS SDK V2 Node.js client for authentication. -[Refer to the official documentation.](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html) +[Refer to the official documentation for loading credentials in Node.js from environment variables](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html). If the environment variables are missing, the AWS SDK tries to read the `~/.aws/credentials` file for credentials. @@ -161,8 +161,8 @@ If the environment variables are missing, the AWS SDK tries to read the If you are using Amazon EC2 instance to deploy Backstage, you do not need to obtain the access keys separately. They can be made available in the environment automatically by defining appropriate IAM role with access to the bucket. Read -more -[here](https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html#use-roles). +more in +[official AWS documentation for using IAM roles.](https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html#use-roles). The AWS Region of the bucket is optional since TechDocs uses AWS SDK V2 and not V3. From f4c2bcf54bb70dd39325ff63f38050016162a04f Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 17 Feb 2021 11:41:39 +0100 Subject: [PATCH 106/270] Use a more strict type for `variant` of cards Signed-off-by: Oliver Sand --- .changeset/quick-ways-develop.md | 13 ++++++++ .../src/components/ProgressBars/GaugeCard.tsx | 4 +-- .../core/src/layout/InfoCard/InfoCard.tsx | 4 ++- packages/core/src/layout/InfoCard/index.ts | 1 + .../ImportStepper/ImportStepper.tsx | 9 +++-- .../src/components/AboutCard/AboutCard.tsx | 2 +- .../src/components/Cards/Cards.tsx | 33 ++++++++++--------- .../Cards/RecentWorkflowRunsCard.tsx | 3 +- .../jenkins/src/components/Cards/Cards.tsx | 16 +++++---- .../Cards/LastLighthouseAuditCard.tsx | 7 ++-- .../Group/GroupProfile/GroupProfileCard.tsx | 6 ++-- .../Cards/OwnershipCard/OwnershipCard.tsx | 4 +-- .../User/UserProfileCard/UserProfileCard.tsx | 4 +-- .../SentryIssuesWidget/SentryIssuesWidget.tsx | 9 ++--- .../SonarQubeCard/SonarQubeCard.tsx | 3 +- 15 files changed, 74 insertions(+), 44 deletions(-) create mode 100644 .changeset/quick-ways-develop.md diff --git a/.changeset/quick-ways-develop.md b/.changeset/quick-ways-develop.md new file mode 100644 index 0000000000..f3c8087bff --- /dev/null +++ b/.changeset/quick-ways-develop.md @@ -0,0 +1,13 @@ +--- +'@backstage/core': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-github-actions': patch +'@backstage/plugin-jenkins': patch +'@backstage/plugin-lighthouse': patch +'@backstage/plugin-org': patch +'@backstage/plugin-sentry': patch +'@backstage/plugin-sonarqube': patch +--- + +Use a more strict type for `variant` of cards. diff --git a/packages/core/src/components/ProgressBars/GaugeCard.tsx b/packages/core/src/components/ProgressBars/GaugeCard.tsx index 7f281c67a5..2154619b01 100644 --- a/packages/core/src/components/ProgressBars/GaugeCard.tsx +++ b/packages/core/src/components/ProgressBars/GaugeCard.tsx @@ -16,14 +16,14 @@ import React from 'react'; import { makeStyles } from '@material-ui/core'; -import { InfoCard } from '../../layout/InfoCard'; +import { InfoCard, InfoCardVariants } from '../../layout/InfoCard'; import { BottomLinkProps } from '../../layout/BottomLink'; import { Gauge } from './Gauge'; type Props = { title: string; subheader?: string; - variant?: string; + variant?: InfoCardVariants; /** Progress in % specified as decimal, e.g. "0.23" */ progress: number; deepLink?: BottomLinkProps; diff --git a/packages/core/src/layout/InfoCard/InfoCard.tsx b/packages/core/src/layout/InfoCard/InfoCard.tsx index af10010d14..cf06a5f0a1 100644 --- a/packages/core/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core/src/layout/InfoCard/InfoCard.tsx @@ -87,6 +87,8 @@ const VARIANT_STYLES = { }, }; +export type InfoCardVariants = 'flex' | 'fullHeight' | 'gridItem'; + /** * InfoCard is used to display a paper-styled block on the screen, similar to a panel. * @@ -111,7 +113,7 @@ type Props = { divider?: boolean; deepLink?: BottomLinkProps; slackChannel?: string; - variant?: string; + variant?: InfoCardVariants; style?: object; cardStyle?: object; children?: ReactNode; diff --git a/packages/core/src/layout/InfoCard/index.ts b/packages/core/src/layout/InfoCard/index.ts index d77fcf93c4..35829662d0 100644 --- a/packages/core/src/layout/InfoCard/index.ts +++ b/packages/core/src/layout/InfoCard/index.ts @@ -15,3 +15,4 @@ */ export { InfoCard } from './InfoCard'; +export type { InfoCardVariants } from './InfoCard'; diff --git a/plugins/catalog-import/src/components/ImportStepper/ImportStepper.tsx b/plugins/catalog-import/src/components/ImportStepper/ImportStepper.tsx index f92f03c5cb..49fce69b5a 100644 --- a/plugins/catalog-import/src/components/ImportStepper/ImportStepper.tsx +++ b/plugins/catalog-import/src/components/ImportStepper/ImportStepper.tsx @@ -14,7 +14,12 @@ * limitations under the License. */ -import { configApiRef, InfoCard, useApi } from '@backstage/core'; +import { + configApiRef, + InfoCard, + InfoCardVariants, + useApi, +} from '@backstage/core'; import { Step, StepContent, Stepper } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React, { useMemo } from 'react'; @@ -39,7 +44,7 @@ type Props = { flow: ImportFlows, defaults: StepperProvider, ) => StepperProvider; - variant?: string; + variant?: InfoCardVariants; opts?: StepperProviderOpts; }; diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index 04c2a78869..2d6cd6bca8 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -79,7 +79,7 @@ function getCodeLinkInfo(entity: Entity): CodeLinkInfo { type AboutCardProps = { /** @deprecated The entity is now grabbed from context instead */ entity?: Entity; - variant?: string; + variant?: 'gridItem'; }; export function AboutCard({ variant }: AboutCardProps) { diff --git a/plugins/github-actions/src/components/Cards/Cards.tsx b/plugins/github-actions/src/components/Cards/Cards.tsx index 202bf5aa92..4ce667eb93 100644 --- a/plugins/github-actions/src/components/Cards/Cards.tsx +++ b/plugins/github-actions/src/components/Cards/Cards.tsx @@ -13,29 +13,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useEffect } from 'react'; -import { useWorkflowRuns } from '../useWorkflowRuns'; -import { WorkflowRun, WorkflowRunsTable } from '../WorkflowRunsTable'; import { Entity } from '@backstage/catalog-model'; -import { useEntity } from '@backstage/plugin-catalog-react'; -import { readGitHubIntegrationConfigs } from '@backstage/integration'; -import { WorkflowRunStatus } from '../WorkflowRunStatus'; import { - Link, - Theme, - makeStyles, - LinearProgress, - Typography, -} from '@material-ui/core'; -import { - InfoCard, - StructuredMetadataTable, configApiRef, errorApiRef, + InfoCard, + InfoCardVariants, + StructuredMetadataTable, useApi, } from '@backstage/core'; +import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { + LinearProgress, + Link, + makeStyles, + Theme, + Typography, +} from '@material-ui/core'; import ExternalLinkIcon from '@material-ui/icons/Launch'; +import React, { useEffect } from 'react'; import { GITHUB_ACTIONS_ANNOTATION } from '../useProjectName'; +import { useWorkflowRuns } from '../useWorkflowRuns'; +import { WorkflowRun, WorkflowRunsTable } from '../WorkflowRunsTable'; +import { WorkflowRunStatus } from '../WorkflowRunStatus'; const useStyles = makeStyles({ externalLinkIcon: { @@ -125,7 +126,7 @@ type Props = { /** @deprecated The entity is now grabbed from context instead */ entity?: Entity; branch: string; - variant?: string; + variant?: InfoCardVariants; }; export const LatestWorkflowsForBranchCard = ({ diff --git a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx index 4425bc8364..30807482a4 100644 --- a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx +++ b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx @@ -19,6 +19,7 @@ import { EmptyState, errorApiRef, InfoCard, + InfoCardVariants, Table, useApi, } from '@backstage/core'; @@ -39,7 +40,7 @@ export type Props = { branch?: string; dense?: boolean; limit?: number; - variant?: string; + variant?: InfoCardVariants; }; export const RecentWorkflowRunsCard = ({ diff --git a/plugins/jenkins/src/components/Cards/Cards.tsx b/plugins/jenkins/src/components/Cards/Cards.tsx index 6fc544bcea..0d7c2fe919 100644 --- a/plugins/jenkins/src/components/Cards/Cards.tsx +++ b/plugins/jenkins/src/components/Cards/Cards.tsx @@ -13,13 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { DateTime, Duration } from 'luxon'; -import { Link, Theme, makeStyles, LinearProgress } from '@material-ui/core'; -import { InfoCard, StructuredMetadataTable } from '@backstage/core'; +import { + InfoCard, + InfoCardVariants, + StructuredMetadataTable, +} from '@backstage/core'; +import { LinearProgress, Link, makeStyles, Theme } from '@material-ui/core'; import ExternalLinkIcon from '@material-ui/icons/Launch'; -import { useBuilds } from '../useBuilds'; +import { DateTime, Duration } from 'luxon'; +import React from 'react'; import { JenkinsRunStatus } from '../BuildsPage/lib/Status'; +import { useBuilds } from '../useBuilds'; import { useProjectSlugFromEntity } from '../useProjectSlugFromEntity'; const useStyles = makeStyles({ @@ -76,7 +80,7 @@ export const LatestRunCard = ({ variant, }: { branch: string; - variant?: string; + variant?: InfoCardVariants; }) => { const { owner, repo } = useProjectSlugFromEntity(); const [{ builds, loading }] = useBuilds(owner, repo, branch); diff --git a/plugins/lighthouse/src/components/Cards/LastLighthouseAuditCard.tsx b/plugins/lighthouse/src/components/Cards/LastLighthouseAuditCard.tsx index d30b9a3312..693a8b6ec6 100644 --- a/plugins/lighthouse/src/components/Cards/LastLighthouseAuditCard.tsx +++ b/plugins/lighthouse/src/components/Cards/LastLighthouseAuditCard.tsx @@ -13,16 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { Audit, AuditCompleted, LighthouseCategoryId } from '../../api'; import { InfoCard, + InfoCardVariants, Progress, StatusError, StatusOK, StatusWarning, StructuredMetadataTable, } from '@backstage/core'; +import React from 'react'; +import { Audit, AuditCompleted, LighthouseCategoryId } from '../../api'; import { useWebsiteForEntity } from '../../hooks/useWebsiteForEntity'; import AuditStatusIcon from '../AuditStatusIcon'; @@ -96,7 +97,7 @@ export const LastLighthouseAuditCard = ({ variant, }: { dense?: boolean; - variant?: string; + variant?: InfoCardVariants; }) => { const { value: website, loading, error } = useWebsiteForEntity(); diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index 1682192880..ce13f8391f 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -20,10 +20,10 @@ import { RELATION_CHILD_OF, RELATION_PARENT_OF, } from '@backstage/catalog-model'; -import { Avatar, InfoCard } from '@backstage/core'; +import { Avatar, InfoCard, InfoCardVariants } from '@backstage/core'; import { - getEntityRelations, entityRouteParams, + getEntityRelations, useEntity, } from '@backstage/plugin-catalog-react'; import { @@ -81,7 +81,7 @@ export const GroupProfileCard = ({ }: { /** @deprecated The entity is now grabbed from context instead */ entity?: GroupEntity; - variant: string; + variant?: InfoCardVariants; }) => { const group = useEntity().entity as GroupEntity; const { diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index 80e80bddcc..30f3bf0967 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -15,7 +15,7 @@ */ import { Entity } from '@backstage/catalog-model'; -import { InfoCard, Progress, useApi } from '@backstage/core'; +import { InfoCard, InfoCardVariants, Progress, useApi } from '@backstage/core'; import { catalogApiRef, isOwnerOf, @@ -121,7 +121,7 @@ export const OwnershipCard = ({ }: { /** @deprecated The entity is now grabbed from context instead */ entity?: Entity; - variant: string; + variant?: InfoCardVariants; }) => { const { entity } = useEntity(); const catalogApi = useApi(catalogApiRef); diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx index 4dfa543d95..cf558fd55d 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx @@ -18,7 +18,7 @@ import { RELATION_MEMBER_OF, UserEntity, } from '@backstage/catalog-model'; -import { Avatar, InfoCard } from '@backstage/core'; +import { Avatar, InfoCard, InfoCardVariants } from '@backstage/core'; import { entityRouteParams, useEntity } from '@backstage/plugin-catalog-react'; import { Box, @@ -73,7 +73,7 @@ export const UserProfileCard = ({ }: { /** @deprecated The entity is now grabbed from context instead */ entity?: UserEntity; - variant: string; + variant?: InfoCardVariants; }) => { const user = useEntity().entity as UserEntity; const { diff --git a/plugins/sentry/src/components/SentryIssuesWidget/SentryIssuesWidget.tsx b/plugins/sentry/src/components/SentryIssuesWidget/SentryIssuesWidget.tsx index c52b76c4bc..b1ba8c7cc2 100644 --- a/plugins/sentry/src/components/SentryIssuesWidget/SentryIssuesWidget.tsx +++ b/plugins/sentry/src/components/SentryIssuesWidget/SentryIssuesWidget.tsx @@ -14,24 +14,25 @@ * limitations under the License. */ -import React, { useEffect } from 'react'; +import { Entity } from '@backstage/catalog-model'; import { EmptyState, ErrorApi, errorApiRef, InfoCard, + InfoCardVariants, MissingAnnotationEmptyState, Progress, useApi, } from '@backstage/core'; -import SentryIssuesTable from '../SentryIssuesTable/SentryIssuesTable'; +import React, { useEffect } from 'react'; import { useAsync } from 'react-use'; import { sentryApiRef } from '../../api'; +import SentryIssuesTable from '../SentryIssuesTable/SentryIssuesTable'; import { SENTRY_PROJECT_SLUG_ANNOTATION, useProjectSlug, } from '../useProjectSlug'; -import { Entity } from '@backstage/catalog-model'; export const SentryIssuesWidget = ({ entity, @@ -40,7 +41,7 @@ export const SentryIssuesWidget = ({ }: { entity: Entity; statsFor?: '24h' | '12h'; - variant?: string; + variant?: InfoCardVariants; }) => { const errorApi = useApi(errorApiRef); const sentryApi = useApi(sentryApiRef); diff --git a/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx b/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx index 5577048fa9..12ed5b3bf4 100644 --- a/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx +++ b/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx @@ -18,6 +18,7 @@ import { Entity } from '@backstage/catalog-model'; import { EmptyState, InfoCard, + InfoCardVariants, MissingAnnotationEmptyState, Progress, useApi, @@ -88,7 +89,7 @@ export const SonarQubeCard = ({ duplicationRatings = defaultDuplicationRatings, }: { entity?: Entity; - variant?: string; + variant?: InfoCardVariants; duplicationRatings?: DuplicationRating[]; }) => { const { entity } = useEntity(); From db6ff2daa24109843e6ae4a6af2cd0eafacf7ebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Wed, 17 Feb 2021 12:11:42 +0100 Subject: [PATCH 107/270] @types/react 16 not 17 --- plugins/pagerduty/package.json | 2 +- yarn.lock | 13 ------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index b009a90e03..93a9ce3e4b 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -37,7 +37,7 @@ "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", - "@types/react": "^17.0.2", + "@types/react": "^16.9", "classnames": "^2.2.6", "luxon": "1.25.0", "react": "^16.13.1", diff --git a/yarn.lock b/yarn.lock index 042bf77d46..e2c8103d8e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6620,14 +6620,6 @@ dependencies: csstype "^2.2.0" -"@types/react@^17.0.2": - version "17.0.2" - resolved "https://registry.npmjs.org/@types/react/-/react-17.0.2.tgz#3de24c4efef902dd9795a49c75f760cbe4f7a5a8" - integrity sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA== - dependencies: - "@types/prop-types" "*" - csstype "^3.0.2" - "@types/reactcss@*": version "1.2.3" resolved "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.3.tgz#af28ae11bbb277978b99d04d1eedfd068ca71834" @@ -10703,11 +10695,6 @@ csstype@^2.2.0, csstype@^2.5.2, csstype@^2.5.5, csstype@^2.5.7, csstype@^2.6.5, resolved "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098" integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q== -csstype@^3.0.2: - version "3.0.6" - resolved "https://registry.npmjs.org/csstype/-/csstype-3.0.6.tgz#865d0b5833d7d8d40f4e5b8a6d76aea3de4725ef" - integrity sha512-+ZAmfyWMT7TiIlzdqJgjMb7S4f1beorDbWbsocyK4RaiqA5RTX3K14bnBWmmA9QEM0gRdsjyyrEmcyga8Zsxmw== - csv-generate@^3.2.4: version "3.2.4" resolved "https://registry.npmjs.org/csv-generate/-/csv-generate-3.2.4.tgz#440dab9177339ee0676c9e5c16f50e2b3463c019" From 233b78a42ef566aa10df6b6a08397ec7dbe54682 Mon Sep 17 00:00:00 2001 From: ebarrios Date: Wed, 17 Feb 2021 12:23:16 +0100 Subject: [PATCH 108/270] Replace logging erro and return undefined for a throw new Error --- .../src/scaffolder/stages/publish/github.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts index 4de928c32b..a449eddd5d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts @@ -64,8 +64,9 @@ export class GithubPublisher implements PublisherBase { }); if (!token) { - logger.error(`No token could be acquired for URL: ${values.storePath}`); - return { remoteUrl: '', catalogInfoUrl: undefined }; + throw new Error( + `No token could be acquired for URL: ${values.storePath}`, + ); } const client = new Octokit({ From 5795f2dc936abd68daababba1ed119d73bf9152e Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Wed, 17 Feb 2021 12:40:29 +0100 Subject: [PATCH 109/270] TechDocs: Pass user and group ID when invoking docker container Without this, files generated by Docker container will be restricted to the root user --- .../src/stages/generate/helpers.test.ts | 29 +++++++++++++++++-- .../src/stages/generate/helpers.ts | 18 ++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/techdocs-common/src/stages/generate/helpers.test.ts b/packages/techdocs-common/src/stages/generate/helpers.test.ts index c4bf102610..fbca6d1ed2 100644 --- a/packages/techdocs-common/src/stages/generate/helpers.test.ts +++ b/packages/techdocs-common/src/stages/generate/helpers.test.ts @@ -30,6 +30,7 @@ import { patchMkdocsYmlPreBuild, runDockerContainer, storeEtagMetadata, + UserOptions, } from './helpers'; const mockEntity = { @@ -114,7 +115,7 @@ describe('helpers', () => { imageName, args, expect.any(Stream), - { + expect.objectContaining({ Volumes: { '/content': {}, '/result': {}, @@ -123,7 +124,7 @@ describe('helpers', () => { HostConfig: { Binds: [`${docsDir}:/content`, `${outputDir}:/result`], }, - }, + }), ); }); @@ -139,6 +140,30 @@ describe('helpers', () => { expect(mockDocker.ping).toHaveBeenCalled(); }); + it('should pass through the user and group id from the host machine and set the home dir', async () => { + await runDockerContainer({ + imageName, + args, + docsDir, + outputDir, + dockerClient: mockDocker, + }); + + const userOptions: UserOptions = {}; + if (process.getuid && process.getgid) { + userOptions.User = `${process.getuid()}:${process.getgid()}`; + } + + expect(mockDocker.run).toHaveBeenCalledWith( + imageName, + args, + expect.any(Stream), + expect.objectContaining({ + ...userOptions, + }), + ); + }); + describe('where docker is unavailable', () => { const dockerError = 'a docker error'; diff --git a/packages/techdocs-common/src/stages/generate/helpers.ts b/packages/techdocs-common/src/stages/generate/helpers.ts index 32da5d19e8..a88ed13231 100644 --- a/packages/techdocs-common/src/stages/generate/helpers.ts +++ b/packages/techdocs-common/src/stages/generate/helpers.ts @@ -51,6 +51,12 @@ export type RunCommandOptions = { logStream?: Writable; }; +export type UserOptions = { + User?: string; +}; + +// To be replaced by a runDockerContainer from backend-common +// shared between Scaffolder and TechDocs and any other plugin. export async function runDockerContainer({ imageName, args, @@ -78,6 +84,17 @@ export async function runDockerContainer({ }); }); + const userOptions: UserOptions = {}; + // @ts-ignore + if (process.getuid && process.getgid) { + // Files that are created inside the Docker container will be owned by + // root on the host system on non Mac systems, because of reasons. Mainly the fact that + // volume sharing is done using NFS on Mac and actual mounts in Linux world. + // So we set the user in the container as the same user and group id as the host. + // On Windows we don't have process.getuid nor process.getgid + userOptions.User = `${process.getuid()}:${process.getgid()}`; + } + const [{ Error: error, StatusCode: statusCode }] = await dockerClient.run( imageName, args, @@ -91,6 +108,7 @@ export async function runDockerContainer({ HostConfig: { Binds: [`${docsDir}:/content`, `${outputDir}:/result`], }, + ...userOptions, ...createOptions, }, ); From 5469a9761f9f3a209def9c2b64d6c77c9c1352a4 Mon Sep 17 00:00:00 2001 From: Kiran Patel Date: Wed, 3 Feb 2021 17:33:19 +1100 Subject: [PATCH 110/270] feat(CatalogTable): truncate long description with ellipsis and tooltip --- .changeset/small-bikes-enjoy.md | 9 ++++ packages/core/package.json | 6 ++- .../OverflowTooltip.stories.tsx | 26 ++++++++++ .../OverflowTooltip/OverflowTooltip.tsx | 50 +++++++++++++++++++ .../src/components/OverflowTooltip/index.ts | 16 ++++++ packages/core/src/components/index.ts | 1 + .../ApiExplorerTable/ApiExplorerTable.tsx | 7 +++ .../components/CatalogTable/CatalogTable.tsx | 7 +++ yarn.lock | 17 ++++++- 9 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 .changeset/small-bikes-enjoy.md create mode 100644 packages/core/src/components/OverflowTooltip/OverflowTooltip.stories.tsx create mode 100644 packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx create mode 100644 packages/core/src/components/OverflowTooltip/index.ts diff --git a/.changeset/small-bikes-enjoy.md b/.changeset/small-bikes-enjoy.md new file mode 100644 index 0000000000..f6e4dd4d9b --- /dev/null +++ b/.changeset/small-bikes-enjoy.md @@ -0,0 +1,9 @@ +--- +'@backstage/core': minor +'@backstage/plugin-api-docs': minor +'@backstage/plugin-catalog': minor +--- + +Introduced generic OverflowTooltip component for cases where longer text needs to be truncated with ellipsis and show hover tooltip with full text. This is particularly useful in the cases where longer description text is rendered in table. e.g. CatalogTable and ApiExplorerTable. + +Changes made in CatalogTable and ApiExplorerTable for using the OverflowTooltip component for truncating large description and showing tooltip on hover-over. diff --git a/packages/core/package.json b/packages/core/package.json index c9045c6465..cbf5523a67 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -37,20 +37,20 @@ "@material-ui/lab": "4.0.0-alpha.45", "@testing-library/react-hooks": "^3.4.2", "@types/dagre": "^0.7.44", + "@types/prop-types": "^15.7.3", "@types/react": "^16.9", "@types/react-sparklines": "^1.7.0", - "@types/prop-types": "^15.7.3", "classnames": "^2.2.6", "clsx": "^1.1.0", "d3-selection": "^2.0.0", "d3-shape": "^2.0.0", "d3-zoom": "^2.0.0", "dagre": "^0.8.5", - "qs": "^6.9.4", "immer": "^8.0.1", "lodash": "^4.17.15", "material-table": "^1.69.1", "prop-types": "^15.7.2", + "qs": "^6.9.4", "rc-progress": "^3.0.0", "react": "^16.12.0", "react-dom": "^16.12.0", @@ -61,6 +61,7 @@ "react-router-dom": "6.0.0-beta.0", "react-sparklines": "^1.7.0", "react-syntax-highlighter": "^13.5.1", + "react-text-truncate": "^0.16.0", "react-use": "^15.3.3", "remark-gfm": "^1.0.0", "zen-observable": "^0.8.15" @@ -79,6 +80,7 @@ "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "@types/react-helmet": "^6.1.0", + "@types/react-text-truncate": "^0.14.0", "@types/zen-observable": "^0.8.0" }, "files": [ diff --git a/packages/core/src/components/OverflowTooltip/OverflowTooltip.stories.tsx b/packages/core/src/components/OverflowTooltip/OverflowTooltip.stories.tsx new file mode 100644 index 0000000000..df6aba5cad --- /dev/null +++ b/packages/core/src/components/OverflowTooltip/OverflowTooltip.stories.tsx @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 React from 'react'; +import { OverflowTooltip } from './OverflowTooltip'; + +const text = + 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.'; + +export const Default = () => ; + +export const MultiLine = () => ( + +); diff --git a/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx b/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx new file mode 100644 index 0000000000..02a45372f2 --- /dev/null +++ b/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { Tooltip, TooltipProps } from '@material-ui/core'; +import React, { useState } from 'react'; +import TextTruncate, { TextTruncateProps } from 'react-text-truncate'; + +type Props = { + title: TooltipProps['title']; + placement?: TooltipProps['placement']; + text?: TextTruncateProps['text']; + line?: TextTruncateProps['line']; + element?: TextTruncateProps['element']; +}; + +export const OverflowTooltip = (props: Props) => { + const [hover, setHover] = useState(false); + + const handleToggled = (truncated: boolean) => { + const hover = truncated ? true : false; + setHover(hover); + }; + + return ( + + + + ); +}; diff --git a/packages/core/src/components/OverflowTooltip/index.ts b/packages/core/src/components/OverflowTooltip/index.ts new file mode 100644 index 0000000000..fe51e8267f --- /dev/null +++ b/packages/core/src/components/OverflowTooltip/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { OverflowTooltip } from './OverflowTooltip'; diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 28056b33bf..58d0a8f569 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -29,6 +29,7 @@ export * from './Lifecycle'; export * from './Link'; export * from './MarkdownContent'; export * from './OAuthRequestDialog'; +export * from './OverflowTooltip'; export * from './Progress'; export * from './ProgressBars'; export * from './Select'; diff --git a/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx index 701ca457df..1660436c46 100644 --- a/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx +++ b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx @@ -23,6 +23,7 @@ import { } from '@backstage/catalog-model'; import { CodeSnippet, + OverflowTooltip, Table, TableColumn, TableFilter, @@ -92,6 +93,12 @@ const columns: TableColumn[] = [ { title: 'Description', field: 'entity.metadata.description', + render: ({ entity }) => ( + + ), }, { title: 'Tags', diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index cdc7451601..43242faedb 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -25,6 +25,7 @@ import { TableColumn, TableProps, WarningPanel, + OverflowTooltip, } from '@backstage/core'; import { EntityRefLink, @@ -91,6 +92,12 @@ const columns: TableColumn[] = [ { title: 'Description', field: 'entity.metadata.description', + render: ({ entity }) => ( + + ), }, { title: 'Tags', diff --git a/yarn.lock b/yarn.lock index ac3a9e4a28..a8848a41a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1861,6 +1861,7 @@ react-router-dom "6.0.0-beta.0" react-sparklines "^1.7.0" react-syntax-highlighter "^13.5.1" + react-text-truncate "^0.16.0" react-use "^15.3.3" remark-gfm "^1.0.0" zen-observable "^0.8.15" @@ -6605,6 +6606,13 @@ dependencies: "@types/react" "*" +"@types/react-text-truncate@^0.14.0": + version "0.14.0" + resolved "https://registry.npmjs.org/@types/react-text-truncate/-/react-text-truncate-0.14.0.tgz#588bbabbc7f2a13815e805f3a48942db73fe65fe" + integrity sha512-XZNmx8mMPFjRLFjFqIF5uLPXEZ4THKxoEv1yU63JzInV3ES42PD+DaQOK6+Rd+cRolzrNoY4YIY9rrW8kh4ikw== + dependencies: + "@types/react" "*" + "@types/react-transition-group@^4.2.0": version "4.2.4" resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.2.4.tgz#c7416225987ccdb719262766c1483da8f826838d" @@ -20939,7 +20947,7 @@ promzard@^0.3.0: dependencies: read "1" -prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -21673,6 +21681,13 @@ react-test-renderer@^16.13.1: react-is "^16.8.6" scheduler "^0.19.1" +react-text-truncate@^0.16.0: + version "0.16.0" + resolved "https://registry.npmjs.org/react-text-truncate/-/react-text-truncate-0.16.0.tgz#03bbb942437dfba5cc0faf614f1339f888926f80" + integrity sha512-hMFXhUHgIBCCDaOfOsZAFeO4DGfG/paLyaS/F+X11CXseuScpxmMBUW6Luwjk9/FlGrVJWNGy1FSfK6b4yyiIg== + dependencies: + prop-types "^15.5.7" + react-textarea-autosize@^8.1.1: version "8.2.0" resolved "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.2.0.tgz#fae38653f5ec172a855fd5fffb39e466d56aebdb" From ba070def578f4dcf1e3604e34b3e355bbe0e4ded Mon Sep 17 00:00:00 2001 From: Kiran Patel Date: Wed, 3 Feb 2021 18:01:55 +1100 Subject: [PATCH 111/270] (#2497) move devDependencies to dependencies for types ! to fix CI check --- packages/core/package.json | 2 +- yarn.lock | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/package.json b/packages/core/package.json index cbf5523a67..8655825cd8 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -40,6 +40,7 @@ "@types/prop-types": "^15.7.3", "@types/react": "^16.9", "@types/react-sparklines": "^1.7.0", + "@types/react-text-truncate": "^0.14.0", "classnames": "^2.2.6", "clsx": "^1.1.0", "d3-selection": "^2.0.0", @@ -80,7 +81,6 @@ "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "@types/react-helmet": "^6.1.0", - "@types/react-text-truncate": "^0.14.0", "@types/zen-observable": "^0.8.0" }, "files": [ diff --git a/yarn.lock b/yarn.lock index a8848a41a6..a7b426f5bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1840,6 +1840,7 @@ "@types/prop-types" "^15.7.3" "@types/react" "^16.9" "@types/react-sparklines" "^1.7.0" + "@types/react-text-truncate" "^0.14.0" classnames "^2.2.6" clsx "^1.1.0" d3-selection "^2.0.0" From c380f32c35ca908eeee06745cfdbc3c78906bb98 Mon Sep 17 00:00:00 2001 From: Kiran Patel Date: Thu, 4 Feb 2021 09:04:23 +1100 Subject: [PATCH 112/270] feat(overflowtooltip): add text as default title for tooltip --- .../OverflowTooltip.stories.tsx | 26 +++++++++++++++++-- .../OverflowTooltip/OverflowTooltip.tsx | 8 +++--- .../ApiExplorerTable/ApiExplorerTable.tsx | 2 +- .../components/CatalogTable/CatalogTable.tsx | 2 +- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/packages/core/src/components/OverflowTooltip/OverflowTooltip.stories.tsx b/packages/core/src/components/OverflowTooltip/OverflowTooltip.stories.tsx index df6aba5cad..e0cbe7c814 100644 --- a/packages/core/src/components/OverflowTooltip/OverflowTooltip.stories.tsx +++ b/packages/core/src/components/OverflowTooltip/OverflowTooltip.stories.tsx @@ -13,14 +13,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { Box } from '@material-ui/core'; import React from 'react'; import { OverflowTooltip } from './OverflowTooltip'; +export default { + title: 'Data Display/OverflowTooltip', + component: OverflowTooltip, +}; + const text = 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.'; -export const Default = () => ; +export const Default = () => ( + + + +); export const MultiLine = () => ( - + + + +); + +export const DifferentTitle = () => ( + + + ); diff --git a/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx b/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx index 02a45372f2..715f84503e 100644 --- a/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx +++ b/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx @@ -19,11 +19,11 @@ import React, { useState } from 'react'; import TextTruncate, { TextTruncateProps } from 'react-text-truncate'; type Props = { - title: TooltipProps['title']; - placement?: TooltipProps['placement']; - text?: TextTruncateProps['text']; + text: TextTruncateProps['text']; line?: TextTruncateProps['line']; element?: TextTruncateProps['element']; + title?: TooltipProps['title']; + placement?: TooltipProps['placement']; }; export const OverflowTooltip = (props: Props) => { @@ -36,7 +36,7 @@ export const OverflowTooltip = (props: Props) => { return ( diff --git a/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx index 1660436c46..729f659b56 100644 --- a/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx +++ b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx @@ -95,8 +95,8 @@ const columns: TableColumn[] = [ field: 'entity.metadata.description', render: ({ entity }) => ( ), }, diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 43242faedb..91f187855a 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -94,8 +94,8 @@ const columns: TableColumn[] = [ field: 'entity.metadata.description', render: ({ entity }) => ( ), }, From 1ec172a084a3b70f8f5de4d7810c91f1ae7f0672 Mon Sep 17 00:00:00 2001 From: Kiran Patel Date: Wed, 10 Feb 2021 11:19:26 +1100 Subject: [PATCH 113/270] modify version change from minor to patch for core, catalog and api-explore plugins --- .changeset/small-bikes-enjoy.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/small-bikes-enjoy.md b/.changeset/small-bikes-enjoy.md index f6e4dd4d9b..ffab05ec7b 100644 --- a/.changeset/small-bikes-enjoy.md +++ b/.changeset/small-bikes-enjoy.md @@ -1,7 +1,7 @@ --- -'@backstage/core': minor -'@backstage/plugin-api-docs': minor -'@backstage/plugin-catalog': minor +'@backstage/core': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-catalog': patch --- Introduced generic OverflowTooltip component for cases where longer text needs to be truncated with ellipsis and show hover tooltip with full text. This is particularly useful in the cases where longer description text is rendered in table. e.g. CatalogTable and ApiExplorerTable. From 2c1f2a7c210e2a9f42f3bcf5130b723c403f9fab Mon Sep 17 00:00:00 2001 From: Kiran Patel Date: Wed, 10 Feb 2021 11:26:26 +1100 Subject: [PATCH 114/270] made two different changeset for OverflowTooltip in core and usage in plugins --- .changeset/small-bikes-enjoy.md | 3 --- .changeset/tricky-birds-appear.md | 5 +++++ 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 .changeset/tricky-birds-appear.md diff --git a/.changeset/small-bikes-enjoy.md b/.changeset/small-bikes-enjoy.md index ffab05ec7b..2b624ece2f 100644 --- a/.changeset/small-bikes-enjoy.md +++ b/.changeset/small-bikes-enjoy.md @@ -1,9 +1,6 @@ --- -'@backstage/core': patch '@backstage/plugin-api-docs': patch '@backstage/plugin-catalog': patch --- -Introduced generic OverflowTooltip component for cases where longer text needs to be truncated with ellipsis and show hover tooltip with full text. This is particularly useful in the cases where longer description text is rendered in table. e.g. CatalogTable and ApiExplorerTable. - Changes made in CatalogTable and ApiExplorerTable for using the OverflowTooltip component for truncating large description and showing tooltip on hover-over. diff --git a/.changeset/tricky-birds-appear.md b/.changeset/tricky-birds-appear.md new file mode 100644 index 0000000000..ce1792c5da --- /dev/null +++ b/.changeset/tricky-birds-appear.md @@ -0,0 +1,5 @@ +--- +'@backstage/core': patch +--- + +Introduced generic OverflowTooltip component for cases where longer text needs to be truncated with ellipsis and show hover tooltip with full text. This is particularly useful in the cases where longer description text is rendered in table. e.g. CatalogTable and ApiExplorerTable. From 6cc77b0f7d7bfcfe7215550c2bb30e8a95d16258 Mon Sep 17 00:00:00 2001 From: Kiran Patel Date: Wed, 10 Feb 2021 11:27:18 +1100 Subject: [PATCH 115/270] refactor setHover in OverflowTooltip --- .../core/src/components/OverflowTooltip/OverflowTooltip.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx b/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx index 715f84503e..17f228d681 100644 --- a/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx +++ b/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx @@ -30,8 +30,7 @@ export const OverflowTooltip = (props: Props) => { const [hover, setHover] = useState(false); const handleToggled = (truncated: boolean) => { - const hover = truncated ? true : false; - setHover(hover); + setHover(truncated); }; return ( From f8284bed3e82ae199a533fb674f3eef63f2f1e0a Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 16 Feb 2021 10:28:33 +0100 Subject: [PATCH 116/270] chore: ignore stories codecov --- .github/codecov.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/codecov.yml b/.github/codecov.yml index d39038615f..191f22cb8a 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,5 +1,8 @@ comment: false # Ref: https://docs.codecov.io/docs/pull-request-comments +ignore: + - "**/*.stories.*" + coverage: status: project: From 8c5a6df2c85d2114974f405d87a236d898919c69 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 16 Feb 2021 10:48:10 +0100 Subject: [PATCH 117/270] chore: prettier --- .github/codecov.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/codecov.yml b/.github/codecov.yml index 191f22cb8a..a15de165d0 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,7 +1,7 @@ comment: false # Ref: https://docs.codecov.io/docs/pull-request-comments -ignore: - - "**/*.stories.*" +ignore: + - '**/*.stories.*' coverage: status: From 3469cc71cc8b55de93f2bac57eee7777bde39120 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 16 Feb 2021 11:13:12 +0100 Subject: [PATCH 118/270] chore: fix typings --- .../core/src/components/OverflowTooltip/OverflowTooltip.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx b/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx index 17f228d681..9bbe7aeb81 100644 --- a/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx +++ b/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx @@ -35,7 +35,7 @@ export const OverflowTooltip = (props: Props) => { return ( From 1743239d034d7763d89c8cc487b265af17472faa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Wed, 17 Feb 2021 13:40:36 +0100 Subject: [PATCH 119/270] Updated unit tests for the new UI --- .../src/components/Incident/IncidentListItem.tsx | 1 + .../src/components/Incident/Incidents.test.tsx | 16 +++++----------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx index 2d4afa53cd..3d2e4d0edd 100644 --- a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx +++ b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx @@ -79,6 +79,7 @@ export const IncidentListItem = ({ incident }: Props) => { primary={ <> { }, ] as Incident[], ); - const { - getByText, - getByTitle, - getAllByTitle, - getByLabelText, - queryByTestId, - } = render( + const { getByText, getAllByTitle, queryByTestId } = render( wrapInTestApp( @@ -102,10 +96,10 @@ describe('Incidents', () => { expect(getByText('title2')).toBeInTheDocument(); expect(getByText('person1')).toBeInTheDocument(); expect(getByText('person2')).toBeInTheDocument(); - expect(getByTitle('triggered')).toBeInTheDocument(); - expect(getByTitle('acknowledged')).toBeInTheDocument(); - expect(getByLabelText('Status error')).toBeInTheDocument(); - expect(getByLabelText('Status warning')).toBeInTheDocument(); + expect(getByText('triggered')).toBeInTheDocument(); + expect(getByText('acknowledged')).toBeInTheDocument(); + expect(queryByTestId('chip-triggered')).toBeInTheDocument(); + expect(queryByTestId('chip-acknowledged')).toBeInTheDocument(); // assert links, mailto and hrefs, date calculation expect(getAllByTitle('View in PagerDuty').length).toEqual(2); From 29257f31e0dae8a3bd46de68fe3b1f8456540a57 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 17 Feb 2021 13:47:23 +0100 Subject: [PATCH 120/270] Scaffolder: Revert to Navigate component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- .../TemplatePage/TemplatePage.test.tsx | 18 ++++++++++++++---- .../components/TemplatePage/TemplatePage.tsx | 10 ++++------ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx index ae4a8943ae..f0c77f2f03 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx @@ -22,6 +22,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { MemoryRouter, Route } from 'react-router'; import { ScaffolderApi, scaffolderApiRef } from '../../api'; +import { rootRouteRef } from '../../routes'; import { TemplatePage } from './TemplatePage'; const templateMock = { @@ -96,11 +97,15 @@ describe('TemplatePage', () => { , + { + mountedRoutes: { + '/create': rootRouteRef, + }, + }, ); expect(rendered.queryByText('Create a New Component')).toBeInTheDocument(); expect(rendered.queryByText('React SSR Template')).toBeInTheDocument(); - // await act(async () => await mutate('templates/test')); }); it('renders spinner while loading', async () => { @@ -113,13 +118,18 @@ describe('TemplatePage', () => { , + { + mountedRoutes: { + '/create': rootRouteRef, + }, + }, ); expect(rendered.queryByText('Create a New Component')).toBeInTheDocument(); expect(rendered.queryByTestId('loading-progress')).toBeInTheDocument(); - // Need to cleanup the promise or will timeout - act(() => { - resolve!({ items: [] }); + + await act(async () => { + resolve!({ items: [templateMock] }); }); }); diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 030cd5dccb..0fc1a3cf5d 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -29,7 +29,7 @@ import { LinearProgress } from '@material-ui/core'; import { IChangeEvent } from '@rjsf/core'; import parseGitUrl from 'git-url-parse'; import React, { useCallback, useState } from 'react'; -import { generatePath, useNavigate } from 'react-router'; +import { generatePath, useNavigate, Navigate } from 'react-router'; import { useParams } from 'react-router-dom'; import { useAsync } from 'react-use'; import { scaffolderApiRef } from '../../api'; @@ -45,7 +45,7 @@ const useTemplate = ( filter: { kind: 'Template', 'metadata.name': templateName }, }); return response.items as TemplateEntityV1alpha1[]; - }); + }, [catalogApi, templateName]); return { template: value?.[0], loading, error }; }; @@ -100,8 +100,7 @@ export const TemplatePage = () => { if (!loading && !template) { errorApi.post(new Error('Template was not found.')); - navigate(rootLink()); - return <>{null}; + return ; } if (template && !template?.spec?.schema) { @@ -110,8 +109,7 @@ export const TemplatePage = () => { 'Template schema is corrupted, please check the template.yaml file.', ), ); - navigate(rootLink()); - return <>{null}; + return ; } return ( From 44414239f2e7c57ab7626c224493d6cefdf7e4f9 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Wed, 17 Feb 2021 13:49:59 +0100 Subject: [PATCH 121/270] TechDocs: Add changeset about Docker permission fix --- .changeset/techdocs-metal-turkeys-sleep.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/techdocs-metal-turkeys-sleep.md diff --git a/.changeset/techdocs-metal-turkeys-sleep.md b/.changeset/techdocs-metal-turkeys-sleep.md new file mode 100644 index 0000000000..79cf11ce53 --- /dev/null +++ b/.changeset/techdocs-metal-turkeys-sleep.md @@ -0,0 +1,5 @@ +--- +'@backstage/techdocs-common': patch +--- + +Pass user and group ID when invoking docker container. When TechDocs invokes Docker, docker could be run as a `root` user which results in generation of files by applications run by non-root user (e.g. TechDocs) will not have access to modify. This PR passes in current user and group ID to docker so that the file permissions of the generated files and folders are correct. From 83ff0988b1675cba4e9872c8c09d5bb4c44f118e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 17 Feb 2021 13:28:44 +0100 Subject: [PATCH 122/270] a small start to the integrations section of the config --- docs/integrations/github/org.md | 71 ++++++++ docs/integrations/ldap/org.md | 280 ++++++++++++++++++++++++++++++++ microsite/sidebars.json | 12 ++ mkdocs.yml | 5 + 4 files changed, 368 insertions(+) create mode 100644 docs/integrations/github/org.md create mode 100644 docs/integrations/ldap/org.md diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md new file mode 100644 index 0000000000..848a306381 --- /dev/null +++ b/docs/integrations/github/org.md @@ -0,0 +1,71 @@ +--- +id: org +title: GitHub Organizational Data +sidebar_label: Org Data +# prettier-ignore +description: Setting up ingestion of organizational data from GitHub +--- + +The Backstage catalog can be set up to ingest organizational data - teams and +users - directly from an organization in GitHub or GitHub Enterprise. The result +is a hierarchy of +[`User`](../../features/software-catalog/descriptor-format.md#kind-user) and +[`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind +entities that mirror your org setup. + +## Installation + +The processor that performs the import, `GithubOrgReaderProcessor`, comes +installed with the default setup of Backstage. + +If you replace the set of processors in your installation using that facility of +the catalog builder class, you can import and add it as follows. + +```ts +// Typically in packages/backend/src/plugins/catalog.ts +import { GithubOrgReaderProcessor } from '@backstage/plugin-catalog-backend'; + +builder.replaceProcessors( + GithubOrgReaderProcessor.fromConfig(config, { logger }), + // ... +); +``` + +## Configuration + +The following configuration enables an import of the teams and users under the +org `https://github.com/my-org-name` on public GitHub. + +```yaml +catalog: + locations: + - type: github-org + target: https://github.com/my-org-name + processors: + githubOrg: + providers: + - target: https://github.com + apiBaseUrl: https://api.github.com + token: + $env: GITHUB_TOKEN +``` + +Locations point out the specific org(s) you want to import. The `type` of these +locations must be `github-org`, and the `target` must point to the exact URL of +some organization. You can have several such location entries if you want, but +typically you will have just one. + +The processor itself is configured in the other block, under +`catalog.processors.githubOrg`. There may be many providers, each targeting a +specific `target` which is supposed to be the address of the home page of GitHub +or your GitHub Enterprise installation. + +The example above assumes that the backend is started with an environment +variable called `GITHUB_TOKEN` that contains a Personal Access Token. The token +needs to have at least the scopes `read:org`, `read:user`, and `user:email` in +the given `target`. + +If you want to address your own GitHub Enterprise instance, replace occurrences +of `https://github.com` in the configuration above with the address of your +GitHub Enterprise home page, and the `apiBaseUrl` to where your API endpoint +lives - commonly on the form `https:///api/v3`. diff --git a/docs/integrations/ldap/org.md b/docs/integrations/ldap/org.md new file mode 100644 index 0000000000..dea33e4024 --- /dev/null +++ b/docs/integrations/ldap/org.md @@ -0,0 +1,280 @@ +--- +id: org +title: LDAP Organizational Data +sidebar_label: Org Data +# prettier-ignore +description: Setting up ingestion of organizational data from LDAP +--- + +The Backstage catalog can be set up to ingest organizational data - groups and +users - directly from an LDAP compatible service. The result is a hierarchy of +[`User`](../../features/software-catalog/descriptor-format.md#kind-user) and +[`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind +entities that mirror your org setup. + +## Installation + +The processor that performs the import, `LdapOrgReaderProcessor`, comes +installed with the default setup of Backstage. + +If you replace the set of processors in your installation using that facility of +the catalog builder class, you can import and add it as follows. + +```ts +// Typically in packages/backend/src/plugins/catalog.ts +import { LdapOrgReaderProcessor } from '@backstage/plugin-catalog-backend'; + +builder.replaceProcessors( + LdapOrgReaderProcessor.fromConfig(config, { logger }), + // ... +); +``` + +## Configuration + +The following configuration is a small example of how a setup could look for +importing groups and users from a corporate LDAP server. + +```yaml +catalog: + locations: + - type: ldap-org + target: ldaps://ds.example.net + processors: + ldapOrg: + providers: + - target: ldaps://ds.example.net + bind: + dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net + secret: + $env: LDAP_SECRET + users: + dn: ou=people,ou=example,dc=example,dc=net + options: + filter: (uid=*) + map: + description: l + set: + metadata.customField: 'hello' + groups: + dn: ou=access,ou=groups,ou=example,dc=example,dc=net + options: + filter: (&(objectClass=some-group-class)(!(groupType=email))) + map: + description: l + set: + metadata.customField: 'hello' +``` + +Locations point out the specific org(s) you want to import. The `type` of these +locations must be `ldap-org`, and the `target` must point to the exact URL +(starting with `ldap://` or `ldaps://`) of the targeted LDAP server. You can +have several such location entries if you want, but typically you will have just +one. + +The processor itself is configured in the other block, under +`catalog.processors.ldapOrg`. There may be many providers, each targeting a +specific `target` which is supposed to be on the same form as the location +`target`. + +These config blocks have a lot of options in them, so we will describe each +"root" key within the block separately. + +### target + +This is the URL of the targeted server, typically on the form +`ldaps://ds.example.net` for SSL enabled servers or `ldap://ds.example.net` +without SSL. + +### bind + +The bind block specifies how the plugin should bind (essentially, to +authenticate) towards the server. It has the following fields. + +```yaml +dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net +secret: + $env: LDAP_SECRET +``` + +The `dn` is the full LDAP DN (distinguished name) for the user that the plugin +authenticates itself as. At this point, only regular user based authentication +is supported. + +The `secret` is the password of the same user. In this example, it is given in +the form of an environment variable `LDAP_SECRET`, that has to be set when the +backend starts. + +### users + +The `users` block defines the settings that govern the reading and +interpretation of users. Its fields are explained in separate sections below. + +### users.dn + +The DN under which users are stored, e.g. +`ou=people,ou=example,dc=example,dc=net`. + +### users.options + +The search options to use when sending the query to the server, when reading all +users. All of the options are shown below, with their default values, but they +are all optional. + +```yaml +options: + # One of 'base', 'one', or 'sub'. + scope: one + # The filter is the one that you commonly will want to specify explicitly. It + # is a string on the standard LDAP query format. Use it to select out the set + # of users that are of actual interest to ingest. For example, you may want + # to filter out disabled users. + filter: (uid=*) + # The attribute selectors for each item, as passed to the LDAP server. + attributes: ['*', '+'] + # This field is either 'false' to disable paging when reading from the + # server, or an object on the form '{ pageSize: 100, pagePause: true }' that + # specifies the details of how the paging shall work. + paged: false +``` + +### users.set + +This optional piece lets you specify a number of JSON paths (on a.b.c form) and +hard coded values to set on those paths. This can be useful for example if you +want to hard code a namespace or similar on the generated entities. + +```yaml +set: + # Just an example; the key and value can be anything + metadata.namespace: 'ldap' +``` + +### users.map + +Mappings from well known entity fields, to LDAP attribute names. This is where +you are able to define how to interpret the attributes of each LDAP result item, +and to move them into the corresponding entity fields. All of the options are +shown below, with their default values, but they are all optional. + +If you leave out an optional mapping, it will still be copied using that default +value. For example, even if you do not put in the field `displayName` in your +config, the processor will still copy the attribute `cn` into the entity field +`spec.profile.displayName`. + +```yaml +map: + # The name of the attribute that holds the relative + # distinguished name of each entry. + rdn: uid + # The name of the attribute that shall be used for the value of + # the metadata.name field of the entity. + name: uid + # The name of the attribute that shall be used for the value of + # the metadata.description field of the entity. + description: description + # The name of the attribute that shall be used for the value of + # the spec.profile.displayName field of the entity. + displayName: cn + # The name of the attribute that shall be used for the value of + # the spec.profile.email field of the entity. + email: mail + # The name of the attribute that shall be used for the value of + # the spec.profile.picture field of the entity. + picture: + # The name of the attribute that shall be used for the values of + # the spec.memberOf field of the entity. + memberOf: memberOf +``` + +### groups + +The `groups` block defines the settings that govern the reading and +interpretation of groups. Its fields are explained in separate sections below. + +### groups.dn + +The DN under which groups are stored, e.g. +`ou=people,ou=example,dc=example,dc=net`. + +### groups.options + +The search options to use when sending the query to the server, when reading all +groups. All of the options are shown below, with their default values, but they +are all optional. + +```yaml +options: + # One of 'base', 'one', or 'sub'. + scope: one + # The filter is the one that you commonly will want to specify explicitly. It + # is a string on the standard LDAP query format. Use it to select out the set + # of groups that are of actual interest to ingest. For example, you may want + # to filter out disabled groups. + filter: (&(objectClass=some-group-class)(!(groupType=email))) + # The attribute selectors for each item, as passed to the LDAP server. + attributes: ['*', '+'] + # This field is either 'false' to disable paging when reading from the + # server, or an object on the form '{ pageSize: 100, pagePause: true }' that + # specifies the details of how the paging shall work. + paged: false +``` + +### groups.set + +This optional piece lets you specify a number of JSON paths (on a.b.c form) and +hard coded values to set on those paths. This can be useful for example if you +want to hard code a namespace or similar on the generated entities. + +```yaml +set: + # Just an example; the key and value can be anything + metadata.namespace: 'ldap' +``` + +### groups.map + +Mappings from well known entity fields, to LDAP attribute names. This is where +you are able to define how to interpret the attributes of each LDAP result item, +and to move them into the corresponding entity fields. All of the options are +shown below, with their default values, but they are all optional. + +If you leave out an optional mapping, it will still be copied using that default +value. For example, even if you do not put in the field `displayName` in your +config, the processor will still copy the attribute `cn` into the entity field +`spec.profile.displayName`. If the target field is optional, such as the display +name, the importer will accept missing attributes and just leave the target +field unset. If the target field is mandatory, such as the name of the entity, +validation will fail if the source attribute is missing. + +```yaml +map: + # The name of the attribute that holds the relative + # distinguished name of each entry. This value is copied into a + # well known annotation to be able to query by it later. + rdn: cn + # The name of the attribute that shall be used for the value of + # the metadata.name field of the entity. + name: cn + # The name of the attribute that shall be used for the value of + # the metadata.description field of the entity. + description: description + # The name of the attribute that shall be used for the value of + # the spec.type field of the entity. + type: groupType + # The name of the attribute that shall be used for the value of + # the spec.profile.displayName field of the entity. + displayName: cn + # The name of the attribute that shall be used for the value of + # the spec.profile.email field of the entity. + email: + # The name of the attribute that shall be used for the value of + # the spec.profile.picture field of the entity. + picture: + # The name of the attribute that shall be used for the values of + # the spec.parent field of the entity. + memberOf: memberOf + # The name of the attribute that shall be used for the values of + # the spec.children field of the entity. + members: member +``` diff --git a/microsite/sidebars.json b/microsite/sidebars.json index ebce8d900c..f010fa069f 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -101,6 +101,18 @@ ] } ], + "Integrations": [ + { + "type": "subcategory", + "label": "GitHub", + "ids": ["integrations/github/org"] + }, + { + "type": "subcategory", + "label": "LDAP", + "ids": ["integrations/ldap/org"] + } + ], "Plugins": [ "plugins/index", "plugins/existing-plugins", diff --git a/mkdocs.yml b/mkdocs.yml index a303fd9978..d046ebf4c4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -64,6 +64,11 @@ nav: - FAQ: 'features/techdocs/FAQ.md' - Kubernetes: - Overview: 'features/kubernetes/index.md' + - Integrations: + - GitHub: + - Org Data: 'integrations/github/org.md' + - LDAP: + - Org Data: 'integrations/ldap/org.md' - Plugins: - Overview: 'plugins/index.md' - Existing plugins: 'plugins/existing-plugins.md' From e308a89ade8e129fb81e34ef18d9b55cc95e689c Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 17 Feb 2021 13:53:44 +0100 Subject: [PATCH 123/270] docs: fixing custom implementations of utitiy apis --- docs/api/utility-apis.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md index 5b5656f1b1..c039f33bf5 100644 --- a/docs/api/utility-apis.md +++ b/docs/api/utility-apis.md @@ -181,7 +181,25 @@ The `IgnoringErrorApi` would then be imported in the app, and wired up like this: ```ts -builder.add(errorApiRef, new IgnoringErrorApi()); +const app = createApp({ + apis: [ + /* ApiFactories */ + createApiFactory(errorApiRef, new IgnoringErrorApi()) + + // OR + // if you have dependencies inside your additional API you can use the object form + createApiFactory({ + api: errorApiRef, + deps: { configApi: configApiRef }, + factory({ configApi }) { + return new IgnoringErrorApi({ + reportingUrl: configApi.getString('error.reportingUrl'), + }); + }, + }), + ], + // ... other options +}); ``` Note that the above line will cause an error if `IgnoreErrorApi` does not fully From 8425f6d10fc8825c68db821cfa36d0d7f7f6ba86 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 17 Feb 2021 13:56:29 +0100 Subject: [PATCH 124/270] chore: fixing syntax --- docs/api/utility-apis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md index c039f33bf5..904848b664 100644 --- a/docs/api/utility-apis.md +++ b/docs/api/utility-apis.md @@ -184,7 +184,7 @@ this: const app = createApp({ apis: [ /* ApiFactories */ - createApiFactory(errorApiRef, new IgnoringErrorApi()) + createApiFactory(errorApiRef, new IgnoringErrorApi()), // OR // if you have dependencies inside your additional API you can use the object form From 758bde62663891101bf087c50ecb51946cd8ed29 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 17 Feb 2021 13:57:32 +0100 Subject: [PATCH 125/270] chore: fix code review --- docs/api/utility-apis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md index 904848b664..308544e0f1 100644 --- a/docs/api/utility-apis.md +++ b/docs/api/utility-apis.md @@ -187,7 +187,7 @@ const app = createApp({ createApiFactory(errorApiRef, new IgnoringErrorApi()), // OR - // if you have dependencies inside your additional API you can use the object form + // If your API has dependencies, you use the object form createApiFactory({ api: errorApiRef, deps: { configApi: configApiRef }, From 2cf720547c5e8c4e8185883b6159e8cf5f7e0f8d Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 17 Feb 2021 14:14:00 +0100 Subject: [PATCH 126/270] =?UTF-8?q?Scaffolder:=20use=20colors=20from=20the?= =?UTF-8?q?=20Backstage=20theme=20Co-authored-by:=20Fredrik=20Adel=C3=B6w?= =?UTF-8?q?=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/TaskPage/TaskPage.tsx | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index d4f75e51af..96a857dbb5 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -43,6 +43,7 @@ import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord'; import { entityRoute } from '@backstage/plugin-catalog-react'; import { parseEntityName } from '@backstage/catalog-model'; import classNames from 'classnames'; +import { BackstageTheme } from '@backstage/theme'; // typings are wrong for this library, so fallback to not parsing types. const humanizeDuration = require('humanize-duration'); @@ -106,23 +107,22 @@ const StepTimeTicker = ({ step }: { step: TaskStep }) => { return {time}; }; -const useStepIconStyles = makeStyles({ - root: { - color: '#eaeaf0', - display: 'flex', - height: 22, - alignItems: 'center', - }, - active: { - color: 'gray', - }, - completed: { - color: 'green', - }, - error: { - color: 'red', - }, -}); +const useStepIconStyles = makeStyles((theme: BackstageTheme) => + createStyles({ + root: { + color: theme.palette.text.disabled, + display: 'flex', + height: 22, + alignItems: 'center', + }, + completed: { + color: theme.palette.status.ok, + }, + error: { + color: theme.palette.status.error, + }, + }), +); function TaskStepIconComponent(props: StepIconProps) { const classes = useStepIconStyles(); @@ -144,7 +144,6 @@ function TaskStepIconComponent(props: StepIconProps) { return (
Date: Wed, 17 Feb 2021 18:01:59 +0100 Subject: [PATCH 127/270] docs: add full docker deployment docs --- docs/cli/commands.md | 6 +- docs/getting-started/deployment-docker.md | 237 ++++++++++++++++++++++ docs/getting-started/deployment-other.md | 92 --------- microsite/sidebars.json | 1 + mkdocs.yml | 1 + 5 files changed, 242 insertions(+), 95 deletions(-) create mode 100644 docs/getting-started/deployment-docker.md diff --git a/docs/cli/commands.md b/docs/cli/commands.md index 726f2a922c..ea72f7e050 100644 --- a/docs/cli/commands.md +++ b/docs/cli/commands.md @@ -206,15 +206,15 @@ The following is an example of a `Dockerfile` that can be used to package the output of `backstage-cli backend:bundle` into an image: ```Dockerfile -FROM node:14-buster +FROM node:14-buster-slim WORKDIR /app ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ -RUN yarn install --production --network-timeout 600000 && rm -rf "$(yarn cache dir)" +RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./ -CMD node packages/backend +CMD ["node", "packages/backend"] ``` ```text diff --git a/docs/getting-started/deployment-docker.md b/docs/getting-started/deployment-docker.md new file mode 100644 index 0000000000..dc7e33c60d --- /dev/null +++ b/docs/getting-started/deployment-docker.md @@ -0,0 +1,237 @@ +--- +id: deployment-docker +title: Docker +description: Documentation on how to deploy Backstage as a Docker image +--- + +This section describes how to build a Backstage App into a deployable Docker +image. It is split into three sections, first covering the host build approach, +which is recommended due its speed and more efficient and often simpler caching. +The second section covers a full multi-stage Docker build, and the last section +covers how to split frontend content into a separate image. + +Something that goes for all of these docker deployment strategies is that they +are stateless, so for a production deployment you will want to set up and +connect to an external PostgreSQL instance where the backend plugins can store +their state, rather than using SQLite. + +### Host Build + +This section describes how to build a Docker image from a Backstage repo with +most of the build happening outside of Docker. This is almost always the faster +approach, as the build steps tend to execute faster, and it's possible to have +more efficient caching of dependencies on the host, where a single change won't +bust the entire cache. + +The required steps in the host build are to install dependencies with +`yarn install`, generate type definitions using `yarn tsc`, and build all +packages with `yarn build`. + +> NOTE: Using `yarn build` to build packages and bundle the backend assumes that +> you have migrated to using `backstage-cli backend:bundle` as your build script +> in the backend package. + +In a CI workflow it might look something like this: + +```bash +yarn install --frozen-lockfile + +# tsc outputs type definitions to dist-types/ in the repo root, which are then consumed by the build +yarn tsc + +# Build all packages and in the end bundle them all up into the packages/backend/dist folder. +yarn build +``` + +Once the host build is complete, we are ready to build our image. We use the +following `Dockerfile`, which is also included when creating a new app with +`@backstage/create-app`: + +```Dockerfile +# This dockerfile builds an image for the backend package. +# It should be executed with the root of the repo as docker context. +# +# Before building this image, be sure to have run the following commands in the repo root: +# +# yarn install +# yarn tsc +# yarn build + +FROM node:14-buster-slim + +WORKDIR /app + +# Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +# The skeleton contains the package.json of each package in the monorepo, +# and along with yarn.lock and the root package.json, that's enough to run yarn install. +ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + +RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" + +# This will copy the contents of the dist-workspace when running the build-image command. +# Do not use this Dockerfile outside of that command, as it will copy in the source code instead. +ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./ + +CMD ["node", "packages/backend"] +``` + +For more details on how the `backend:bundle` command and the `skeleton.tar.gz` +file works, see the +[`backend:bundle` command docs](../cli/commands.md#backendbundle) + +The `Dockerfile` is typically placed at `packages/backend/Dockerfile`, but needs +to be executed with the root of the repo as the build context, in order to get +access to the root `yarn.lock` and `package.json`, along with any other files +that might be needed, such as `.npmrc`. + +In order to speed up the build we can significantly reduce the build context +size using the following `.dockerignore` in the root of the repo: + +```text +.git +node_modules +packages +!packages/backend/dist +plugins +``` + +With the project build and the `.dockerignore` and `Dockerfile` in place, we are +now ready to build the final image. Assuming we're at the root of the repo, we +execute the build like this: + +```bash +docker image build . -f packages/backend/Dockerfile --tag backstage +``` + +To try out the image locally you can run the following: + +```sh +docker run -it -p 7000:7000 backstage +``` + +You should then start to get logs in your terminal, and then you can open your +browser at `http://localhost:7000` + +### Multistage Build + +This section describes how to set up a multi-stage Docker build that builds the +entire project within Docker. This is typically slower than a host build, but is +sometimes desired because Docker in Docker is not available in the build +environment, or due to other requirements. + +The build is split into three different stages, where the first stage finds all +of the `package.json`s that are relevant for the initial install step enabling +us to cache the initial `yarn install` that installs all dependencies. The +second stage executes the build itself, and is similar to the steps we execute +on the host in the host build. The third and final stage then packages it all +together into the final image, and is similar to the `Dockerfile` of the host +build. + +The following `Dockerfile` executes the multi-stage build and should be added to +the repo root: + +```Dockerfile +# Stage 1 - Create yarn install skeleton layer +FROM node:14-buster-slim AS packages + +WORKDIR /app +COPY package.json yarn.lock ./ + +COPY packages packages +COPY plugins plugins + +RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -print | xargs rm -rf + +# Stage 2 - Install dependencies and build packages +FROM node:14-buster-slim AS build + +WORKDIR /app +COPY --from=packages /app . + +RUN yarn install --frozen-lockfile --network-timeout 600000 && rm -rf "$(yarn cache dir)" + +COPY . . + +RUN yarn tsc +RUN yarn --cwd packages/backend backstage-cli backend:bundle --build-dependencies + +# Stage 3 - Build the actual backend image and install production dependencies +FROM node:14-buster-slim + +WORKDIR /app + +# Copy the install dependencies from the build stage and context +COPY --from=build /app/yarn.lock /app/package.json /app/packages/backend/dist/skeleton.tar.gz ./ +RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +RUN yarn install --frozen-lockfile --production --network-timeout 600000 && rm -rf "$(yarn cache dir)" + +# Copy the built packages from the build stage +COPY --from=build /app/packages/backend/dist/bundle.tar.gz . +RUN tar xzf bundle.tar.gz && rm bundle.tar.gz + +# Copy any other files that we need at runtime +COPY app-config.yaml ./ + +CMD ["node", "packages/backend", "--config", "app-config.yaml"] +``` + +Note that a newly created Backstage app will typically not have a `plugins/` +folder, so you will want to comment that line out. This build also does not work +in the main repo, since the `backstage-cli` which is used for the build doesn't +end up being properly installed. + +To speed up the build when not running in a fresh clone of the repo you should +set up a `.dockerignore`. This one is different than the host build one, because +we want to have access to the source code of all packages for the build, but can +ignore any existing build output or dependencies: + +```text +node_modules +packages/*/dist +packages/*/node_modules +plugins/*/dist +plugins/*/node_modules +``` + +Once you have added both the `Dockerfile` and `.dockerignore` to the root of +your project, run the following to build the container under a specified tag. + +```sh +docker image build -t backstage . +``` + +To try out the image locally you can run the following: + +```sh +docker run -it -p 7000:7000 backstage +``` + +You should then start to get logs in your terminal, and then you can open your +browser at `http://localhost:7000` + +### Separate Frontend + +It is sometimes desirable to serve the frontend separately from the backend, +either from a separate image or for example a static file serving provider. The +first step in doing so is to remove the `app-backend` plugin from the backend +package, which is done as follows: + +1. Delete `packages/backend/src/plugins/app.ts` +2. Remove the following lines from `packages/backend/src/index.ts`: + ```tsx + import app from './plugins/app'; + // ... + const appEnv = useHotMemoize(module, () => createEnv('app')); + // ... + .addRouter('', await app(appEnv)); + ``` +3. Remove the `@backstage/plugin-app-backend` and the app package dependency + (e.g. `app`) from `packages/backend/packages.json`. If you don't remove the + app package dependency the app will still be built and bundled with the + backend. + +Once the `app-backend` is removed from the backend, you can use your favorite +static file serving method for serving the frontend. An example of how to set up +an NGINX image is available in the +[contrib folder in the main repo](https://github.com/backstage/backstage/blob/master/contrib/docker/frontend-with-nginx/Dockerfile) diff --git a/docs/getting-started/deployment-other.md b/docs/getting-started/deployment-other.md index e92b54d08b..cc622d00f5 100644 --- a/docs/getting-started/deployment-other.md +++ b/docs/getting-started/deployment-other.md @@ -4,98 +4,6 @@ title: Other description: Documentation on different ways of Deployment --- -## Docker - -Here we have an example Dockerfile that you can use to build everything together -in one container. This Dockerfile uses multi-stage builds, and a -`backend:bundle` command from the CLI. - -It also provides caching on the `yarn install`'s so that you don't have to do it -unless absolutely necessary. - -> Note: This Dockerfile assumes that you're running SQLite, or your -> configuration is setup to connect to an external PostgreSQL Database. - -```Dockerfile -# Stage 1 - Create yarn install skeleton layer -FROM node:14-buster AS packages - -WORKDIR /app -COPY package.json yarn.lock ./ - -COPY packages packages - -# Uncomment this line if you have a local plugins folder -# COPY plugins plugins - -RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -print | xargs rm -rf - -# Stage 2 - Install dependencies and build packages -FROM node:14-buster AS build - -WORKDIR /app -COPY --from=packages /app . - -RUN yarn install --network-timeout 600000 && rm -rf "$(yarn cache dir)" - -COPY . . - -RUN yarn tsc -RUN yarn --cwd packages/backend backstage-cli backend:bundle --build-dependencies - -# Stage 3 - Build the actual backend image and install production dependencies -FROM node:14-buster - -WORKDIR /app - -# Copy from build stage -COPY --from=build /app/yarn.lock /app/package.json /app/packages/backend/dist/skeleton.tar.gz ./ -RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz - -RUN yarn install --production --network-timeout 600000 && rm -rf "$(yarn cache dir)" - -COPY --from=build /app/packages/backend/dist/bundle.tar.gz . -RUN tar xzf bundle.tar.gz && rm bundle.tar.gz - -COPY app-config.yaml app-config.production.yaml ./ - -CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"] -``` - -Before building you should also include a `.dockerignore`. This will greatly -improve the context boot up time of Docker as we are no longer sending all of -the `node_modules` into the context. It also helps us avoid some limitations and -errors that may occur when trying to share the `node_modules` folder to inside -the build. - -You can add the following contents to the root of your repository at -`.dockerignore` and it might look something like the following: - -```dockerignore -.git -node_modules -packages/*/node_modules -plugins/*/node_modules -plugins/*/dist -``` - -Once you have added both the `Dockerfile` and `.dockerignore` to the root of -your project, and run the following to build the container under a specified -tag. - -```sh -$ docker build -t example-deployment . -``` - -To run the image locally you can run: - -```sh -$ docker run -it -p 7000:7000 example-deployment -``` - -You should then start to get logs in your terminal, and then you can open your -browser at `http://localhost:7000` - ## Heroku Deploying to Heroku is relatively easy following these steps. diff --git a/microsite/sidebars.json b/microsite/sidebars.json index ebce8d900c..0ac8f2929b 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -27,6 +27,7 @@ "type": "subcategory", "label": "Deployment", "ids": [ + "getting-started/deployment-docker", "getting-started/deployment-k8s", "getting-started/deployment-helm", "getting-started/deployment-other" diff --git a/mkdocs.yml b/mkdocs.yml index a303fd9978..0e3c4494e1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -24,6 +24,7 @@ nav: - Configuring App with plugins: 'getting-started/configure-app-with-plugins.md' - Customize the look-and-feel of your App: 'getting-started/app-custom-theme.md' - Deployment scenarios: + - Docker: 'getting-started/deployment-docker.md' - Kubernetes: 'getting-started/deployment-k8s.md' - Kubernetes and Helm: 'getting-started/deployment-helm.md' - Other: 'getting-started/deployment-other.md' From 40f0c33378a3a81a082ec41cc50334c71a6cb1f1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 17 Feb 2021 20:44:40 +0100 Subject: [PATCH 128/270] update backend Dockerfile to use config example and fix comment --- .changeset/metal-spoons-change.md | 5 ++--- docs/getting-started/deployment-docker.md | 5 ++--- .../templates/default-app/packages/backend/Dockerfile | 5 ++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.changeset/metal-spoons-change.md b/.changeset/metal-spoons-change.md index 4e1dddd82e..f633c46bdc 100644 --- a/.changeset/metal-spoons-change.md +++ b/.changeset/metal-spoons-change.md @@ -38,11 +38,10 @@ ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" -# This will copy the contents of the dist-workspace when running the build-image command. -# Do not use this Dockerfile outside of that command, as it will copy in the source code instead. +# Then copy the rest of the backend bundle, along with any other files we might want. ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./ -CMD ["node", "packages/backend"] +CMD ["node", "packages/backend", "--config", "app-config.yaml"] ``` Note that the base image has been switched from `node:14-buster` to `node:14-buster-slim`, significantly reducing the image size. This is enabled by the removal of the `nodegit` dependency, so if you are still using this in your project you will have to stick with the `node:14-buster` base image. diff --git a/docs/getting-started/deployment-docker.md b/docs/getting-started/deployment-docker.md index dc7e33c60d..7e312c13dc 100644 --- a/docs/getting-started/deployment-docker.md +++ b/docs/getting-started/deployment-docker.md @@ -68,11 +68,10 @@ ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" -# This will copy the contents of the dist-workspace when running the build-image command. -# Do not use this Dockerfile outside of that command, as it will copy in the source code instead. +# Then copy the rest of the backend bundle, along with any other files we might want. ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./ -CMD ["node", "packages/backend"] +CMD ["node", "packages/backend", "--config", "app-config.yaml"] ``` For more details on how the `backend:bundle` command and the `skeleton.tar.gz` diff --git a/packages/create-app/templates/default-app/packages/backend/Dockerfile b/packages/create-app/templates/default-app/packages/backend/Dockerfile index 51ebf2980d..aba69ff97d 100644 --- a/packages/create-app/templates/default-app/packages/backend/Dockerfile +++ b/packages/create-app/templates/default-app/packages/backend/Dockerfile @@ -18,8 +18,7 @@ ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" -# This will copy the contents of the dist-workspace when running the build-image command. -# Do not use this Dockerfile outside of that command, as it will copy in the source code instead. +# Then copy the rest of the backend bundle, along with any other files we might want. ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./ -CMD ["node", "packages/backend"] +CMD ["node", "packages/backend", "--config", "app-config.yaml"] From 1ca39b75250daeb48f2c7e644df496219923e74b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 17 Feb 2021 20:45:41 +0100 Subject: [PATCH 129/270] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw --- .changeset/metal-spoons-change.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/metal-spoons-change.md b/.changeset/metal-spoons-change.md index f633c46bdc..63a3b62320 100644 --- a/.changeset/metal-spoons-change.md +++ b/.changeset/metal-spoons-change.md @@ -4,7 +4,7 @@ Updated docker build to use `backstage-cli backend:bundle` instead of `backstage-cli backend:build-image`. -To apply this change to an existing applications, change the following in `packages/backend/package.json`: +To apply this change to an existing application, change the following in `packages/backend/package.json`: ```diff - "build": "backstage-cli backend:build", @@ -15,7 +15,7 @@ To apply this change to an existing applications, change the following in `packa Note that the backend build is switched to `backend:bundle`, and the `build-image` script simply calls `docker build`. This means the `build-image` script no longer builds all packages, so you have to run `yarn build` in the root first. -On order to work with the new build method, the `Dockerfile` at `packages/backend/Dockerfile` have been updated with the following contents: +In order to work with the new build method, the `Dockerfile` at `packages/backend/Dockerfile` has been updated with the following contents: ```dockerfile # This dockerfile builds an image for the backend package. From 38fe527d9724c49551a200544f485587e782592c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 17 Feb 2021 21:06:59 +0100 Subject: [PATCH 130/270] dockerfile: mention build-image command --- .changeset/metal-spoons-change.md | 2 ++ docs/getting-started/deployment-docker.md | 2 ++ packages/backend/Dockerfile | 7 ++++--- .../templates/default-app/packages/backend/Dockerfile | 2 ++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.changeset/metal-spoons-change.md b/.changeset/metal-spoons-change.md index 63a3b62320..41828d08ae 100644 --- a/.changeset/metal-spoons-change.md +++ b/.changeset/metal-spoons-change.md @@ -26,6 +26,8 @@ In order to work with the new build method, the `Dockerfile` at `packages/backen # yarn install # yarn tsc # yarn build +# +# Once the commands have been run, you can build the image using `yarn build-image` FROM node:14-buster-slim diff --git a/docs/getting-started/deployment-docker.md b/docs/getting-started/deployment-docker.md index 7e312c13dc..9486e0aeb0 100644 --- a/docs/getting-started/deployment-docker.md +++ b/docs/getting-started/deployment-docker.md @@ -56,6 +56,8 @@ following `Dockerfile`, which is also included when creating a new app with # yarn install # yarn tsc # yarn build +# +# Once the commands have been run, you can build the image using `yarn build-image` FROM node:14-buster-slim diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile index 51ebf2980d..acef405c8a 100644 --- a/packages/backend/Dockerfile +++ b/packages/backend/Dockerfile @@ -6,6 +6,8 @@ # yarn install # yarn tsc # yarn build +# +# Once the commands have been run, you can build the image using `yarn build-image` FROM node:14-buster-slim @@ -18,8 +20,7 @@ ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" -# This will copy the contents of the dist-workspace when running the build-image command. -# Do not use this Dockerfile outside of that command, as it will copy in the source code instead. +# Then copy the rest of the backend bundle, along with any other files we might want. ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./ -CMD ["node", "packages/backend"] +CMD ["node", "packages/backend", "--config", "app-config.yaml"] diff --git a/packages/create-app/templates/default-app/packages/backend/Dockerfile b/packages/create-app/templates/default-app/packages/backend/Dockerfile index aba69ff97d..acef405c8a 100644 --- a/packages/create-app/templates/default-app/packages/backend/Dockerfile +++ b/packages/create-app/templates/default-app/packages/backend/Dockerfile @@ -6,6 +6,8 @@ # yarn install # yarn tsc # yarn build +# +# Once the commands have been run, you can build the image using `yarn build-image` FROM node:14-buster-slim From 01bbdeef381ef2884bcf082acb022878041dbcd9 Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Wed, 17 Feb 2021 12:35:47 -0800 Subject: [PATCH 131/270] address review comments, fix merge conflict --- packages/techdocs-common/src/stages/publish/awsS3.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/awsS3.ts b/packages/techdocs-common/src/stages/publish/awsS3.ts index 52d5647e71..a5857bd604 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.ts @@ -15,7 +15,7 @@ */ import { Entity, EntityName } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import aws from 'aws-sdk'; +import aws, { Credentials } from 'aws-sdk'; import { ManagedUpload } from 'aws-sdk/clients/s3'; import express from 'express'; import fs from 'fs-extra'; @@ -69,7 +69,7 @@ export class AwsS3Publish implements PublisherBase { credentials = new aws.ChainableTemporaryCredentials({ masterCredentials: aws.config.credentials as Credentials, params: { - RoleSessionName: 'backstage-aws-organization-processor', + RoleSessionName: 'backstage-aws-techdocs-s3-publisher', RoleArn: roleArn, }, }); From ba99544da63af2d114639ce8e30602619672db11 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 17 Feb 2021 22:34:20 -0500 Subject: [PATCH 132/270] Fix filename escaping --- docs/plugins/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/testing.md b/docs/plugins/testing.md index 30c3bf0ecc..bd6061be19 100644 --- a/docs/plugins/testing.md +++ b/docs/plugins/testing.md @@ -274,7 +274,7 @@ export { }; ``` -**`./\_\_mocks\_\_/MyApi.js`** +**`./__mocks__/MyApi.js`** ```js export { From a34578e4d77c0ff342a3bf34a44725b7cb05d814 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 17 Feb 2021 22:34:33 -0500 Subject: [PATCH 133/270] Update wording --- docs/support/project-structure.md | 55 +++++++++++++++---------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/docs/support/project-structure.md b/docs/support/project-structure.md index 5c8a8cd3bb..c193dd67d0 100644 --- a/docs/support/project-structure.md +++ b/docs/support/project-structure.md @@ -6,8 +6,8 @@ description: Introduction to files and folders in the Backstage Project reposito --- Backstage is a complex project, and the GitHub repository contains many -different files and folders. This document aims to clarify what purpose of those -files and folders are. +different files and folders. This document aims to clarify the purpose of those +files and folders. ## General purpose files and folders @@ -25,7 +25,7 @@ the code. Standard GitHub folder. It contains - amongst other things - our workflow definitions and templates. Worth noting is the [styles](https://github.com/backstage/backstage/tree/master/.github/styles) - folder which is used for a markdown spellchecker. + subfolder which is used for a markdown spellchecker. - [`.yarn/`](https://github.com/backstage/backstage/tree/master/.yarn) - Backstage ships with it's own `yarn` implementation. This allows us to have @@ -33,7 +33,7 @@ the code. yarn versioning differences. - [`contrib/`](https://github.com/backstage/backstage/tree/master/contrib) - - Collection of examples or resources provided by the community. We really + Collection of examples or resources contributed by the community. We really appreciate contributions in here and encourage them being kept up to date. - [`docs/`](https://github.com/backstage/backstage/tree/master/docs) - This is @@ -43,10 +43,11 @@ the code. file may be needed as sections are added/removed. - [`.editorconfig`](https://github.com/backstage/backstage/tree/master/.editorconfig) - - A configuration file used by most common code editors. + A configuration file used by most common code editors. Learn more at + [EditorConfig.org](https://editorconfig.org/). - [`.imgbotconfig`](https://github.com/backstage/backstage/tree/master/.imgbotconfig) - - Configuration for a [bot](https://imgbot.net/) + Configuration for a [bot](https://imgbot.net/) which helps reduce image sizes. ## Monorepo packages @@ -103,16 +104,16 @@ are separated out into their own folder, see further down. diff, create-plugins and more. In the early days of this project, we started out with calling tools directly - such as `eslint` - through `package.json`. But as it was tricky to have a good development experience around that when we - change named tooling, we opted for wrapping those in our own cli. That way + change named tooling, we opted for wrapping those in our own CLI. That way everything looks the same in `package.json`. Much like [react-scripts](https://github.com/facebook/create-react-app/tree/master/packages/react-scripts). - [`cli-common/`](https://github.com/backstage/backstage/tree/master/packages/cli-common) - This package mainly handles path resolving. It is a separate package to reduce bugs in - [cli](https://github.com/backstage/backstage/tree/master/packages/cli). We + [CLI](https://github.com/backstage/backstage/tree/master/packages/cli). We also want as few dependencies as possible to reduce download time when running - the cli which is another reason this is a separate package. + the CLI which is another reason this is a separate package. - [`config/`](https://github.com/backstage/backstage/tree/master/packages/config) - The way we read configuration data. This package can take a bunch of config @@ -139,14 +140,6 @@ are separated out into their own folder, see further down. detail that we try to hide from our users, and no one should have to depend on it directly. -- [`test-utils/`](https://github.com/backstage/backstage/tree/master/packages/test-utils) - - This package contains specific testing facilities used when testing - `core-api`. - -- [`test-utils-core/`](https://github.com/backstage/backstage/tree/master/packages/test-utils-core) - - This package contains more general purpose testing facilities for testing a - Backstage App. - - [`create-app/`](https://github.com/backstage/backstage/tree/master/packages/create-app) - An CLI to specifically scaffold a new Backstage App. It does so by using a [template](https://github.com/backstage/backstage/tree/master/packages/create-app/templates/default-app). @@ -162,25 +155,29 @@ are separated out into their own folder, see further down. - [`e2e-test/`](https://github.com/backstage/backstage/tree/master/packages/e2e-test) - Another CLI that can be run to try out what would happen if you built all the - packages, publish them, created a new app, and the run it. CI uses this for - e2e-tests. + packages, published them, created a new app, and then run them. CI uses this + for e2e-tests. - [`integration/`](https://github.com/backstage/backstage/tree/master/packages/integration) - Common functionalities of integrations like GitHub, GitLab, etc. - [`storybook/`](https://github.com/backstage/backstage/tree/master/packages/storybook) - - This folder contains only the storybook config. Stories are within the core - package. The Backstage Storybook is found - [here](https://backstage.io/storybook) + This folder contains only the Storybook config which helps visualize our + reusable React components. Stories are within the core package, and are + published in the [Backstage Storybook](https://backstage.io/storybook). - [`techdocs-common/`](https://github.com/backstage/backstage/tree/master/packages/techdocs-common) - Common functionalities for TechDocs, to be shared between [techdocs-backend](https://github.com/backstage/backstage/tree/master/plugins/techdocs-backend) plugin and [techdocs-cli](https://github.com/backstage/techdocs-cli). -- [`test-utils-core/`](https://github.com/backstage/backstage/tree/master/packages/test-utils-core) +- [`test-utils/`](https://github.com/backstage/backstage/tree/master/packages/test-utils) - + This package contains specific testing facilities used when testing + `core-api`. -- [`test-utils/`](https://github.com/backstage/backstage/tree/master/packages/test-utils) +- [`test-utils-core/`](https://github.com/backstage/backstage/tree/master/packages/test-utils-core) - + This package contains more general purpose testing facilities for testing a + Backstage App. - [`theme/`](https://github.com/backstage/backstage/tree/master/packages/theme) - Holds the Backstage Theme. @@ -196,10 +193,10 @@ We can categorize plugins into three different types; **Frontend**, **Backend** and **GraphQL**. We differentiate these types of plugins when we name them, with a dash-suffix. `-backend` means it’s a backend plugin and so on. -One reason for splitting a plugin is because of to it's dependencies. Another +One reason for splitting a plugin is because of it's dependencies. Another reason is for clear separation of concerns. -Take a look at our [Plugin Gallery](https://backstage.io/plugins) or browse +Take a look at our [Plugin Marketplace](https://backstage.io/plugins) or browse through the [`plugins/`](https://github.com/backstage/backstage/tree/master/plugins) folder. @@ -212,7 +209,7 @@ monorepo setup. This folder contains the source code for backstage.io. It is built with [Docusaurus](https://docusaurus.io/). This folder is not part of the monorepo due to dependency reasons. Look at the - [README](https://github.com/backstage/backstage/blob/master/microsite/README.md) + [microsite README](https://github.com/backstage/backstage/blob/master/microsite/README.md) for instructions on how to run it locally. ## Root files specifically used by the `app` @@ -222,8 +219,8 @@ Some of these files may be subject to be moved out of the root sometime in the future. - [`.npmrc`](https://github.com/backstage/backstage/tree/master/.npmrc) - It's - common for companies to have their own npm registry, this files makes sure - that this folder use the public registry. + common for companies to have their own npm registry, and this file makes sure + that this folder always uses the public registry. - [`.vale.ini`](https://github.com/backstage/backstage/tree/master/.vale.ini) - [Spell checker](https://github.com/errata-ai/vale) for Markdown files. From 0d5a07fe2967c1c94cbc8db165803aee5c543d0b Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 17 Feb 2021 22:37:43 -0500 Subject: [PATCH 134/270] Reword e2e-test --- docs/support/project-structure.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/support/project-structure.md b/docs/support/project-structure.md index c193dd67d0..2b8bdc6ac4 100644 --- a/docs/support/project-structure.md +++ b/docs/support/project-structure.md @@ -154,9 +154,9 @@ are separated out into their own folder, see further down. to read out definitions and generate documentation for it. - [`e2e-test/`](https://github.com/backstage/backstage/tree/master/packages/e2e-test) - - Another CLI that can be run to try out what would happen if you built all the - packages, published them, created a new app, and then run them. CI uses this - for e2e-tests. + Another CLI that can be run to try out what would happen if you build all the + packages, publish them, create a new app, and then run them. CI uses this for + e2e-tests. - [`integration/`](https://github.com/backstage/backstage/tree/master/packages/integration) - Common functionalities of integrations like GitHub, GitLab, etc. From ece137bc07494693e72e7ff25951154d6d7b993b Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 17 Feb 2021 22:42:02 -0500 Subject: [PATCH 135/270] Update design practices --- docs/dls/design.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/dls/design.md b/docs/dls/design.md index 3bf283b77f..042312d0a3 100644 --- a/docs/dls/design.md +++ b/docs/dls/design.md @@ -33,15 +33,18 @@ our users. ### Transparent There are a lot of exciting things coming up and we want to keep you in the -loop! Keep an eye on our Milestones in GitHub to see where we’re headed. We’ll -also be posting updates in the _#design_ channel on -[Discord](https://discord.gg/EBHEGzX). Not only that, we want to keep you -informed on the decisions we’ve made and why we’ve made them. +loop! Keep an eye on our +[Milestones in GitHub](https://github.com/backstage/backstage/milestones) to see +where we're headed and review the +[open design issues](https://github.com/backstage/backstage/issues?q=is%3Aopen+is%3Aissue+label%3Adesign), +to see if you can help. We'll also be posting updates in the _#design_ channel +on [Discord](https://discord.gg/EBHEGzX). Not only that, we want to keep you +informed on the decisions we've made and why we've made them. ## 🛠 Our Practice -The chart below details how we work. **_Stay tuned_**: We are currently in the -process of securing a Figma workspace for Backstage Open Source, and we plan on +The chart below details how we work. We have a +[Figma workspace for Backstage Open Source](figma.md), and we plan on referencing Figma documents to share specs and prototypes with the community. ### Creating a New Design Component From 2691abdd39104f5cd3942e98077e705f2ce3520d Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 17 Feb 2021 23:05:57 -0500 Subject: [PATCH 136/270] Hyphenate word --- docs/support/project-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/support/project-structure.md b/docs/support/project-structure.md index 2b8bdc6ac4..d0d69b4330 100644 --- a/docs/support/project-structure.md +++ b/docs/support/project-structure.md @@ -25,7 +25,7 @@ the code. Standard GitHub folder. It contains - amongst other things - our workflow definitions and templates. Worth noting is the [styles](https://github.com/backstage/backstage/tree/master/.github/styles) - subfolder which is used for a markdown spellchecker. + sub-folder which is used for a markdown spellchecker. - [`.yarn/`](https://github.com/backstage/backstage/tree/master/.yarn) - Backstage ships with it's own `yarn` implementation. This allows us to have From f4f6d8f49cd484df76f7ae8a02c5efe1299f7910 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Feb 2021 04:46:23 +0000 Subject: [PATCH 137/270] chore(deps): bump archiver from 5.1.0 to 5.2.0 Bumps [archiver](https://github.com/archiverjs/node-archiver) from 5.1.0 to 5.2.0. - [Release notes](https://github.com/archiverjs/node-archiver/releases) - [Changelog](https://github.com/archiverjs/node-archiver/blob/master/CHANGELOG.md) - [Commits](https://github.com/archiverjs/node-archiver/compare/5.1.0...5.2.0) Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index ac3a9e4a28..56b99bbd9f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7777,9 +7777,9 @@ archiver-utils@^2.1.0: readable-stream "^2.0.0" archiver@^5.0.2: - version "5.1.0" - resolved "https://registry.npmjs.org/archiver/-/archiver-5.1.0.tgz#05b0f6f7836f3e6356a0532763d2bb91017a7e37" - integrity sha512-iKuQUP1nuKzBC2PFlGet5twENzCfyODmvkxwDV0cEFXavwcLrIW5ssTuHi9dyTPvpWr6Faweo2eQaQiLIwyXTA== + version "5.2.0" + resolved "https://registry.npmjs.org/archiver/-/archiver-5.2.0.tgz#25aa1b3d9febf7aec5b0f296e77e69960c26db94" + integrity sha512-QEAKlgQuAtUxKeZB9w5/ggKXh21bZS+dzzuQ0RPBC20qtDCbTyzqmisoeJP46MP39fg4B4IcyvR+yeyEBdblsQ== dependencies: archiver-utils "^2.1.0" async "^3.2.0" From 0ada34a0f881ace72ffd4d3ff1308308246e60d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 18 Feb 2021 10:46:55 +0100 Subject: [PATCH 138/270] minor typo in migration --- .changeset/new-peaches-melt.md | 5 +++++ plugins/scaffolder-backend/migrations/20210120143715_init.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/new-peaches-melt.md diff --git a/.changeset/new-peaches-melt.md b/.changeset/new-peaches-melt.md new file mode 100644 index 0000000000..aff00e0fa0 --- /dev/null +++ b/.changeset/new-peaches-melt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Minor typo in migration diff --git a/plugins/scaffolder-backend/migrations/20210120143715_init.js b/plugins/scaffolder-backend/migrations/20210120143715_init.js index 256ec7d423..5bde7a7c68 100644 --- a/plugins/scaffolder-backend/migrations/20210120143715_init.js +++ b/plugins/scaffolder-backend/migrations/20210120143715_init.js @@ -77,7 +77,7 @@ exports.up = async function up(knex) { exports.down = async function down(knex) { if (knex.client.config.client !== 'sqlite3') { await knex.schema.alterTable('task_events', table => { - table.dropIndex([], 'ctask_events_task_id_idx'); + table.dropIndex([], 'task_events_task_id_idx'); }); } await knex.schema.dropTable('task_events'); From 257a753fff2e14c4de531565a44d5c4c7758652e Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 18 Feb 2021 10:55:10 +0100 Subject: [PATCH 139/270] cli: Fix handling of dynamic imports in esm.js files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- .changeset/green-beds-sell.md | 5 ++++ packages/cli/config/jest.js | 4 +-- packages/cli/config/jestEsmTransform.js | 36 +++++++++++++++++++++++++ packages/cli/package.json | 4 ++- yarn.lock | 8 ------ 5 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 .changeset/green-beds-sell.md create mode 100644 packages/cli/config/jestEsmTransform.js diff --git a/.changeset/green-beds-sell.md b/.changeset/green-beds-sell.md new file mode 100644 index 0000000000..44b7adcc87 --- /dev/null +++ b/.changeset/green-beds-sell.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Updated transform of `.esm.js` files to be able to handle dynamic imports. diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js index 61cd83b7b0..94cf059cbd 100644 --- a/packages/cli/config/jest.js +++ b/packages/cli/config/jest.js @@ -83,10 +83,8 @@ async function getConfig() { }, }, - // We build .esm.js files with plugin:build, so to be able to load these in tests they need to be transformed - // TODO: jest is working on module support, it's possible that we can remove this in the future transform: { - '\\.esm\\.js$': require.resolve('jest-esm-transformer'), + '\\.esm\\.js$': require.resolve('./jestEsmTransform.js'), // See jestEsmTransform.js '\\.(js|jsx|ts|tsx)$': require.resolve('ts-jest'), '\\.(bmp|gif|jpg|jpeg|png|frag|xml|svg)$': require.resolve( './jestFileTransform.js', diff --git a/packages/cli/config/jestEsmTransform.js b/packages/cli/config/jestEsmTransform.js new file mode 100644 index 0000000000..99f1a600bc --- /dev/null +++ b/packages/cli/config/jestEsmTransform.js @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +const babel = require('@babel/core'); + +// We build .esm.js files with plugin:build, so to be able to load these in tests they need to be transformed +// TODO: jest is working on module support, it's possible that we can remove this in the future +module.exports = { + process(src) { + const result = babel.transform(src, { + babelrc: false, + compact: false, + plugins: [ + // This transforms the regular ESM syntax, import and export statements + require.resolve('@babel/plugin-transform-modules-commonjs'), + // This transforms dynamic `import()`, which is not supported yet in the Node.js VM API + require.resolve('babel-plugin-dynamic-import-node'), + ], + }); + + return result.code; + }, +}; diff --git a/packages/cli/package.json b/packages/cli/package.json index e54696ce43..d360727ebd 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -28,6 +28,8 @@ "backstage-cli": "bin/backstage-cli" }, "dependencies": { + "@babel/core": "^7.4.4", + "@babel/plugin-transform-modules-commonjs": "^7.4.4", "@backstage/cli-common": "^0.1.1", "@backstage/config": "^0.1.2", "@backstage/config-loader": "^0.5.1", @@ -53,6 +55,7 @@ "@typescript-eslint/eslint-plugin": "^v4.14.0", "@typescript-eslint/parser": "^v4.14.0", "@yarnpkg/lockfile": "^1.1.0", + "babel-plugin-dynamic-import-node": "^2.3.3", "bfj": "^7.0.2", "chalk": "^4.0.0", "chokidar": "^3.3.1", @@ -78,7 +81,6 @@ "inquirer": "^7.0.4", "jest": "^26.0.1", "jest-css-modules": "^2.1.0", - "jest-esm-transformer": "^1.0.0", "lodash": "^4.17.19", "mini-css-extract-plugin": "^0.9.0", "ora": "^4.0.3", diff --git a/yarn.lock b/yarn.lock index 56b99bbd9f..d7bcc7a3a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15906,14 +15906,6 @@ jest-environment-node@^26.6.2: jest-mock "^26.6.2" jest-util "^26.6.2" -jest-esm-transformer@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/jest-esm-transformer/-/jest-esm-transformer-1.0.0.tgz#b6c58f496aa48194f96361a52f5c578fd2209726" - integrity sha512-FoPgeMMwy1/CEsc8tBI41i83CEO3x85RJuZi5iAMmWoARXhfgk6Jd7y+4d+z+HCkTKNVDvSWKGRhwjzU9PUbrw== - dependencies: - "@babel/core" "^7.4.4" - "@babel/plugin-transform-modules-commonjs" "^7.4.4" - jest-get-type@^24.9.0: version "24.9.0" resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" From f37992797acf73431cbf1e3def2bb4ee84fd20aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 18 Feb 2021 11:14:03 +0100 Subject: [PATCH 140/270] got rid of some attr and cleaned up a bit in techdocs schema --- .changeset/dry-ads-matter.md | 6 +++ plugins/techdocs-backend/config.d.ts | 27 ++++------ plugins/techdocs/config.d.ts | 78 +++++++--------------------- 3 files changed, 36 insertions(+), 75 deletions(-) create mode 100644 .changeset/dry-ads-matter.md diff --git a/.changeset/dry-ads-matter.md b/.changeset/dry-ads-matter.md new file mode 100644 index 0000000000..fc0a412d1e --- /dev/null +++ b/.changeset/dry-ads-matter.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-techdocs': patch +'@backstage/plugin-techdocs-backend': patch +--- + +Got rid of some `attr` and cleaned up a bit in the TechDocs config schema. diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts index 7ec1162d03..5a323f244a 100644 --- a/plugins/techdocs-backend/config.d.ts +++ b/plugins/techdocs-backend/config.d.ts @@ -13,36 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + /** * TechDocs schema below is an abstract of what's used within techdocs-backend and for its visibility * to view the complete TechDocs schema please refer: plugins/techdocs/config.d.ts - * */ + */ export interface Config { - /** Configuration options for the techdocs-backend plugin */ + /** + * Configuration options for the techdocs-backend plugin + * @see http://backstage.io/docs/features/techdocs/configuration + */ techdocs: { /** - * documentation building process depends on the builder attr - * attr: 'builder' - accepts a string value - * e.g. builder: 'local' - * alternative: 'external' etc. - * @see http://backstage.io/docs/features/techdocs/configuration + * Documentation building process depends on the builder attr */ builder: 'local' | 'external'; + /** - * techdocs publisher information + * Techdocs publisher information */ publisher: { - /** - * attr: 'type' - accepts a string value - * e.g. type: 'local' - * aleternatives: 'googleGcs' etc. - * @see http://backstage.io/docs/features/techdocs/configuration - */ type: 'local' | 'googleGcs' | 'awsS3'; }; + /** - * attr: 'storageUrl' - accepts a string value - * e.g. storageUrl: http://localhost:7000/api/techdocs/static/docs + * @example http://localhost:7000/api/techdocs/static/docs * @deprecated */ storageUrl?: string; diff --git a/plugins/techdocs/config.d.ts b/plugins/techdocs/config.d.ts index 1104d75237..d85269d2ec 100644 --- a/plugins/techdocs/config.d.ts +++ b/plugins/techdocs/config.d.ts @@ -15,105 +15,76 @@ */ export interface Config { - /** Configuration options for the techdocs plugin */ + /** + * Configuration options for the techdocs plugin + * @see http://backstage.io/docs/features/techdocs/configuration + */ techdocs: { /** - * documentation building process depends on the builder attr - * attr: 'builder' - accepts a string value - * e.g. builder: 'local' - * alternative: 'external' etc. - * @see http://backstage.io/docs/features/techdocs/configuration + * Documentation building process depends on the builder attr * @visibility frontend */ builder: 'local' | 'external'; /** - * techdocs publisher information + * Techdocs generator information */ generators?: { - /** - * attr: 'techdocs' - accepts a string value - * e.g. type: 'docker' - * alternatives: 'local' etc. - * @see http://backstage.io/docs/features/techdocs/configuration - */ techdocs: 'local' | 'docker'; }; /** - * techdocs publisher information + * Techdocs publisher information */ publisher?: | { - /** - * attr: 'type' - accepts a string value - * e.g. type: 'local' - * alternatives: 'googleGcs' etc. - * @see http://backstage.io/docs/features/techdocs/configuration - */ type: 'local'; } | { - /** - * attr: 'type' - accepts a string value - * e.g. type: 'awsS3' - * alternatives: 'googleGcs' etc. - * @see http://backstage.io/docs/features/techdocs/configuration - */ type: 'awsS3'; /** - * awsS3 required when 'type' is set to awsS3 + * Required when 'type' is set to awsS3 */ awsS3?: { /** * (Optional) Credentials used to access a storage bucket. * If not set, environment variables or aws config file will be used to authenticate. - * https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-environment.html - * https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-shared.html + * @see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-environment.html + * @see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-shared.html * @visibility secret */ credentials?: { /** * User access key id - * attr: 'accessKeyId' - accepts a string value * @visibility secret */ accessKeyId: string; /** * User secret access key - * attr: 'secretAccessKey' - accepts a string value * @visibility secret */ secretAccessKey: string; }; /** * (Required) Cloud Storage Bucket Name - * attr: 'bucketName' - accepts a string value * @visibility backend */ bucketName: string; /** * (Optional) AWS Region. * If not set, AWS_REGION environment variable or aws config file will be used. - * https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-region.html - * attr: 'region' - accepts a string value + * @see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-region.html * @visibility secret */ region?: string; }; } | { - /** - * attr: 'type' - accepts a string value - * e.g. type: 'azureBlobStorage' - * alternatives: 'azureBlobStorage' etc. - * @see http://backstage.io/docs/features/techdocs/configuration - */ type: 'azureBlobStorage'; /** - * azureBlobStorage required when 'type' is set to azureBlobStorage + * Required when 'type' is set to azureBlobStorage */ azureBlobStorage?: { /** @@ -123,66 +94,55 @@ export interface Config { credentials: { /** * Account access name - * attr: 'account' - accepts a string value * @visibility secret */ accountName: string; /** * (Optional) Account secret primary key * If not set, environment variables will be used to authenticate. - * https://docs.microsoft.com/en-us/azure/storage/common/storage-auth?toc=/azure/storage/blobs/toc.json - * attr: 'accountKey' - accepts a string value + * @see https://docs.microsoft.com/en-us/azure/storage/common/storage-auth?toc=/azure/storage/blobs/toc.json * @visibility secret */ accountKey?: string; }; /** * (Required) Cloud Storage Container Name - * attr: 'containerName' - accepts a string value * @visibility backend */ containerName: string; }; } | { - /** - * attr: 'type' - accepts a string value - * e.g. type: 'googleGcs' - * alternatives: 'googleGcs' etc. - * @see http://backstage.io/docs/features/techdocs/configuration - */ type: 'googleGcs'; /** - * googleGcs required when 'type' is set to googleGcs + * Required when 'type' is set to googleGcs */ googleGcs?: { /** * (Required) Cloud Storage Bucket Name - * attr: 'bucketName' - accepts a string value * @visibility backend */ bucketName: string; /** * (Optional) API key used to write to a storage bucket. * If not set, environment variables will be used to authenticate. - * Read more: https://cloud.google.com/docs/authentication/production - * attr: 'credentials' - accepts a string value + * @see https://cloud.google.com/docs/authentication/production * @visibility secret */ credentials?: string; }; }; + /** - * attr: 'requestUrl' - accepts a string value - * e.g. requestUrl: http://localhost:7000/api/techdocs + * @example http://localhost:7000/api/techdocs * @visibility frontend * @deprecated */ requestUrl?: string; + /** - * attr: 'storageUrl' - accepts a string value - * e.g. storageUrl: http://localhost:7000/api/techdocs/static/docs + * @example http://localhost:7000/api/techdocs/static/docs * @deprecated */ storageUrl?: string; From 35952103d66db80859058082d094139436558ae6 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 18 Feb 2021 13:38:45 +0100 Subject: [PATCH 141/270] TechDocs: AWS Publisher - add tests that fail on Windows --- packages/techdocs-common/__mocks__/aws-sdk.ts | 10 +++++++--- .../src/stages/publish/awsS3.test.ts | 17 ++++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/techdocs-common/__mocks__/aws-sdk.ts b/packages/techdocs-common/__mocks__/aws-sdk.ts index f0fb13c642..b956b4309a 100644 --- a/packages/techdocs-common/__mocks__/aws-sdk.ts +++ b/packages/techdocs-common/__mocks__/aws-sdk.ts @@ -41,7 +41,7 @@ export class S3 { } else { emitter.emit( 'error', - new Error(`The file ${Key} doest not exist !`), + new Error(`The file ${Key} does not exist !`), ); } emitter.emit('end'); @@ -56,7 +56,7 @@ export class S3 { if (fs.existsSync(Key)) { resolve(''); } else { - reject({ message: 'The object doest not exist !' }); + reject({ message: 'The object does not exist !' }); } }); } @@ -71,7 +71,11 @@ export class S3 { return { promise: () => new Promise((resolve, reject) => { - resolve(''); + if (!fs.existsSync(Key)) { + reject(''); + } else { + resolve(''); + } }), }; } diff --git a/packages/techdocs-common/src/stages/publish/awsS3.test.ts b/packages/techdocs-common/src/stages/publish/awsS3.test.ts index f8450a07a0..9376e74e92 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.test.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.test.ts @@ -127,9 +127,9 @@ describe('AwsS3Publish', () => { directory: wrongPathToGeneratedDirectory, }) .catch(error => - expect(error.message).toEqual( - expect.stringContaining( - 'Unable to upload file(s) to AWS S3. Error Failed to read template directory: ENOENT, no such file or directory', + expect(error).toEqual( + new Error( + `Unable to upload file(s) to AWS S3. Error Failed to read template directory: ENOENT, no such file or directory '${wrongPathToGeneratedDirectory}'`, ), ), ); @@ -205,12 +205,19 @@ describe('AwsS3Publish', () => { it('should return an error if the techdocs_metadata.json file is not present', async () => { const entityNameMock = createMockEntityName(); + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); await publisher .fetchTechDocsMetadata(entityNameMock) .catch(error => - expect(error.message).toEqual( - expect.stringContaining('TechDocs metadata fetch'), + expect(error).toEqual( + new Error( + `TechDocs metadata fetch failed, The file ${path.join( + entityRootDir, + 'techdocs_metadata.json', + )} does not exist !`, + ), ), ); }); From c626df2602c9a46c320c5c65bfd8e453d9790648 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 18 Feb 2021 14:11:13 +0100 Subject: [PATCH 142/270] scaffolder-backend: add router tests --- .../src/service/router.test.ts | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index d8a236a213..8fd53e5fb6 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -185,6 +185,9 @@ describe('createRouter', () => { name: 'create-react-app-template', tags: ['experimental', 'react', 'cra'], title: 'Create React App Template', + annotations: { + 'backstage.io/managed-by-location': 'url:https://dev.azure.com', + }, }, spec: { owner: 'web@example.com', @@ -247,4 +250,36 @@ describe('createRouter', () => { expect(response.status).toEqual(400); }); }); + + describe('POST /v2/tasks', () => { + it('rejects template values which do not match the template schema definition', async () => { + const response = await request(app) + .post('/v2/tasks') + .send({ + templateName: '', + values: { + storePath: 'https://github.com/backstage/backstage', + }, + }); + + expect(response.status).toEqual(400); + }); + + it('return the template id', async () => { + const response = await request(app) + .post('/v2/tasks') + .send({ + templateName: 'create-react-app-template', + values: { + storePath: 'https://github.com/backstage/backstage', + component_id: '123', + name: 'test', + use_typescript: false, + }, + }); + + expect(response.body.id).toBeDefined(); + expect(response.status).toEqual(201); + }); + }); }); From 759620539acd26610492fc3cab830cf48e6b6942 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 18 Feb 2021 14:16:01 +0100 Subject: [PATCH 143/270] workflow: build transitive dependencies when only building changed packages in CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: blam Co-authored-by: Fredrik Adelöw Co-authored-by: Johan Haals --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08ec67162b..2337a70e2c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,7 +87,7 @@ jobs: - name: build changed packages if: ${{ steps.yarn-lock.outcome == 'success' }} - run: yarn lerna -- run build --since origin/master + run: yarn lerna -- run build --since origin/master --include-dependencies - name: build all packages if: ${{ steps.yarn-lock.outcome == 'failure' }} From 4188c2ccb416310053e7bb20b11875b77d03b09b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 18 Feb 2021 13:23:19 +0000 Subject: [PATCH 144/270] Version Packages --- .changeset/afraid-dingos-own.md | 5 - .changeset/angry-walls-mate.md | 5 - .changeset/blue-lions-worry.md | 5 - .changeset/brown-pumpkins-impress.md | 5 - .changeset/chilly-cars-shout.md | 5 - .changeset/cuddly-bags-share.md | 6 - .changeset/dry-llamas-wave.md | 7 -- .changeset/eight-doors-matter.md | 19 --- .changeset/five-guests-promise.md | 5 - .changeset/fresh-seals-retire.md | 6 - .changeset/green-beds-sell.md | 5 - .changeset/grumpy-cups-hope.md | 5 - .changeset/itchy-camels-grin.md | 5 - .changeset/loud-owls-beam.md | 5 - .changeset/many-bags-sort.md | 5 - .changeset/mean-grapes-march.md | 7 -- .changeset/metal-spoons-change.md | 59 ---------- .changeset/new-mangos-tap.md | 5 - .changeset/new-peaches-melt.md | 5 - .changeset/ninety-lemons-shake.md | 5 - .changeset/pink-coins-sniff.md | 16 --- .changeset/quick-ways-develop.md | 13 --- .changeset/rich-apricots-lick.md | 5 - .changeset/short-pots-report.md | 7 -- .changeset/six-elephants-poke.md | 5 - .changeset/soft-rings-obey.md | 6 - .changeset/sour-shoes-perform.md | 5 - .changeset/spicy-pants-teach.md | 6 - .changeset/strange-olives-unite.md | 5 - .changeset/stupid-maps-do.md | 5 - .changeset/techdocs-fast-foxes-relate.md | 5 - .../techdocs-improve-error-reporting.md | 5 - .changeset/techdocs-metal-turkeys-sleep.md | 5 - .changeset/techdocs-spotty-cooks-wait.md | 6 - .changeset/thick-scissors-notice.md | 5 - .changeset/tough-worms-clap.md | 5 - .changeset/violet-maps-occur.md | 8 -- .changeset/wicked-forks-sleep.md | 6 - .changeset/wise-books-turn.md | 5 - packages/app/CHANGELOG.md | 48 ++++++++ packages/app/package.json | 48 ++++---- packages/backend-common/CHANGELOG.md | 14 +++ packages/backend-common/package.json | 6 +- packages/cli/CHANGELOG.md | 8 ++ packages/cli/package.json | 8 +- packages/core-api/CHANGELOG.md | 21 ++++ packages/core-api/package.json | 4 +- packages/core/CHANGELOG.md | 18 +++ packages/core/package.json | 6 +- packages/create-app/CHANGELOG.md | 108 ++++++++++++++++++ packages/create-app/package.json | 38 +++--- packages/dev-utils/CHANGELOG.md | 16 +++ packages/dev-utils/package.json | 8 +- packages/integration/CHANGELOG.md | 6 + packages/integration/package.json | 4 +- packages/techdocs-common/CHANGELOG.md | 17 +++ packages/techdocs-common/package.json | 8 +- plugins/api-docs/CHANGELOG.md | 27 +++++ plugins/api-docs/package.json | 10 +- plugins/auth-backend/CHANGELOG.md | 13 +++ plugins/auth-backend/package.json | 6 +- plugins/catalog-backend/CHANGELOG.md | 12 ++ plugins/catalog-backend/package.json | 8 +- plugins/catalog-import/CHANGELOG.md | 19 +++ plugins/catalog-import/package.json | 12 +- plugins/catalog-react/CHANGELOG.md | 26 +++++ plugins/catalog-react/package.json | 8 +- plugins/catalog/CHANGELOG.md | 49 ++++++++ plugins/catalog/package.json | 12 +- plugins/circleci/CHANGELOG.md | 16 +++ plugins/circleci/package.json | 10 +- plugins/cloudbuild/CHANGELOG.md | 16 +++ plugins/cloudbuild/package.json | 10 +- plugins/cost-insights/package.json | 6 +- plugins/explore/CHANGELOG.md | 16 +++ plugins/explore/package.json | 10 +- plugins/fossa/CHANGELOG.md | 16 +++ plugins/fossa/package.json | 10 +- plugins/gcp-projects/package.json | 6 +- plugins/github-actions/CHANGELOG.md | 19 +++ plugins/github-actions/package.json | 12 +- plugins/gitops-profiles/package.json | 6 +- plugins/graphiql/package.json | 6 +- plugins/jenkins/CHANGELOG.md | 17 +++ plugins/jenkins/package.json | 10 +- plugins/kafka/CHANGELOG.md | 16 +++ plugins/kafka/package.json | 10 +- plugins/kubernetes/CHANGELOG.md | 16 +++ plugins/kubernetes/package.json | 10 +- plugins/lighthouse/CHANGELOG.md | 17 +++ plugins/lighthouse/package.json | 10 +- plugins/newrelic/package.json | 6 +- plugins/org/CHANGELOG.md | 22 ++++ plugins/org/package.json | 12 +- plugins/pagerduty/CHANGELOG.md | 20 ++++ plugins/pagerduty/package.json | 10 +- plugins/register-component/CHANGELOG.md | 16 +++ plugins/register-component/package.json | 10 +- plugins/rollbar/CHANGELOG.md | 16 +++ plugins/rollbar/package.json | 10 +- plugins/scaffolder-backend/CHANGELOG.md | 18 +++ plugins/scaffolder-backend/package.json | 8 +- plugins/scaffolder/CHANGELOG.md | 17 +++ plugins/scaffolder/package.json | 10 +- plugins/search/CHANGELOG.md | 16 +++ plugins/search/package.json | 10 +- plugins/sentry/CHANGELOG.md | 17 +++ plugins/sentry/package.json | 10 +- plugins/sonarqube/CHANGELOG.md | 18 +++ plugins/sonarqube/package.json | 10 +- plugins/splunk-on-call/CHANGELOG.md | 18 +++ plugins/splunk-on-call/package.json | 10 +- plugins/tech-radar/package.json | 6 +- plugins/techdocs-backend/CHANGELOG.md | 16 +++ plugins/techdocs-backend/package.json | 8 +- plugins/techdocs/CHANGELOG.md | 22 ++++ plugins/techdocs/package.json | 12 +- plugins/user-settings/CHANGELOG.md | 13 +++ plugins/user-settings/package.json | 8 +- plugins/welcome/package.json | 6 +- 120 files changed, 1004 insertions(+), 521 deletions(-) delete mode 100644 .changeset/afraid-dingos-own.md delete mode 100644 .changeset/angry-walls-mate.md delete mode 100644 .changeset/blue-lions-worry.md delete mode 100644 .changeset/brown-pumpkins-impress.md delete mode 100644 .changeset/chilly-cars-shout.md delete mode 100644 .changeset/cuddly-bags-share.md delete mode 100644 .changeset/dry-llamas-wave.md delete mode 100644 .changeset/eight-doors-matter.md delete mode 100644 .changeset/five-guests-promise.md delete mode 100644 .changeset/fresh-seals-retire.md delete mode 100644 .changeset/green-beds-sell.md delete mode 100644 .changeset/grumpy-cups-hope.md delete mode 100644 .changeset/itchy-camels-grin.md delete mode 100644 .changeset/loud-owls-beam.md delete mode 100644 .changeset/many-bags-sort.md delete mode 100644 .changeset/mean-grapes-march.md delete mode 100644 .changeset/metal-spoons-change.md delete mode 100644 .changeset/new-mangos-tap.md delete mode 100644 .changeset/new-peaches-melt.md delete mode 100644 .changeset/ninety-lemons-shake.md delete mode 100644 .changeset/pink-coins-sniff.md delete mode 100644 .changeset/quick-ways-develop.md delete mode 100644 .changeset/rich-apricots-lick.md delete mode 100644 .changeset/short-pots-report.md delete mode 100644 .changeset/six-elephants-poke.md delete mode 100644 .changeset/soft-rings-obey.md delete mode 100644 .changeset/sour-shoes-perform.md delete mode 100644 .changeset/spicy-pants-teach.md delete mode 100644 .changeset/strange-olives-unite.md delete mode 100644 .changeset/stupid-maps-do.md delete mode 100644 .changeset/techdocs-fast-foxes-relate.md delete mode 100644 .changeset/techdocs-improve-error-reporting.md delete mode 100644 .changeset/techdocs-metal-turkeys-sleep.md delete mode 100644 .changeset/techdocs-spotty-cooks-wait.md delete mode 100644 .changeset/thick-scissors-notice.md delete mode 100644 .changeset/tough-worms-clap.md delete mode 100644 .changeset/violet-maps-occur.md delete mode 100644 .changeset/wicked-forks-sleep.md delete mode 100644 .changeset/wise-books-turn.md create mode 100644 plugins/splunk-on-call/CHANGELOG.md diff --git a/.changeset/afraid-dingos-own.md b/.changeset/afraid-dingos-own.md deleted file mode 100644 index 9f7010d330..0000000000 --- a/.changeset/afraid-dingos-own.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -pass registered logger to requestLoggingHandler diff --git a/.changeset/angry-walls-mate.md b/.changeset/angry-walls-mate.md deleted file mode 100644 index 5902168b2c..0000000000 --- a/.changeset/angry-walls-mate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/integration': minor ---- - -Make `ScmIntegration.resolveUrl` mandatory. diff --git a/.changeset/blue-lions-worry.md b/.changeset/blue-lions-worry.md deleted file mode 100644 index 911e1507b2..0000000000 --- a/.changeset/blue-lions-worry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-sonarqube': patch ---- - -Fix bug retrieving current theme diff --git a/.changeset/brown-pumpkins-impress.md b/.changeset/brown-pumpkins-impress.md deleted file mode 100644 index 5748a9860b..0000000000 --- a/.changeset/brown-pumpkins-impress.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Refactored auth provider factories to accept options along with other internal refactoring of the auth providers. diff --git a/.changeset/chilly-cars-shout.md b/.changeset/chilly-cars-shout.md deleted file mode 100644 index 60871b07a3..0000000000 --- a/.changeset/chilly-cars-shout.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Make the `TemplateCard` conform to what material-ui recommends in their examples. This fixes the extra padding around the buttons. diff --git a/.changeset/cuddly-bags-share.md b/.changeset/cuddly-bags-share.md deleted file mode 100644 index 6aff6a9404..0000000000 --- a/.changeset/cuddly-bags-share.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Hide the kind of the owner if it's the default kind for the `ownedBy` -relationship (group). diff --git a/.changeset/dry-llamas-wave.md b/.changeset/dry-llamas-wave.md deleted file mode 100644 index fea9b122a6..0000000000 --- a/.changeset/dry-llamas-wave.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Implement `UrlReader.search` for the other providers (Azure, Bitbucket, GitLab) as well. - -The `UrlReader` subclasses now are implemented in terms of the respective `Integration` class. diff --git a/.changeset/eight-doors-matter.md b/.changeset/eight-doors-matter.md deleted file mode 100644 index 7dbb56b6c3..0000000000 --- a/.changeset/eight-doors-matter.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@backstage/core-api': patch -'@backstage/plugin-catalog': patch ---- - -Minor refactoring of BackstageApp.getSystemIcons to support custom registered -icons. Custom Icons can be added using: - -```tsx -import AlarmIcon from '@material-ui/icons/Alarm'; -import MyPersonIcon from './MyPerson'; - -const app = createApp({ - icons: { - user: MyPersonIcon // override system icon - alert: AlarmIcon, // Custom icon - }, -}); -``` diff --git a/.changeset/five-guests-promise.md b/.changeset/five-guests-promise.md deleted file mode 100644 index 1d4d0ae303..0000000000 --- a/.changeset/five-guests-promise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Remove the "Move repository" menu entry from the catalog page, as it's just a placeholder. diff --git a/.changeset/fresh-seals-retire.md b/.changeset/fresh-seals-retire.md deleted file mode 100644 index f58eebef4e..0000000000 --- a/.changeset/fresh-seals-retire.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/core-api': patch -'@backstage/core': patch ---- - -Export `createExternalRouteRef`, as well as give it an `id` for easier debugging, and fix parameter requirements when used with `useRouteRef`. diff --git a/.changeset/green-beds-sell.md b/.changeset/green-beds-sell.md deleted file mode 100644 index 44b7adcc87..0000000000 --- a/.changeset/green-beds-sell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Updated transform of `.esm.js` files to be able to handle dynamic imports. diff --git a/.changeset/grumpy-cups-hope.md b/.changeset/grumpy-cups-hope.md deleted file mode 100644 index 6445c07efa..0000000000 --- a/.changeset/grumpy-cups-hope.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-user-settings': patch ---- - -Use routed tabs to link to every settings page. diff --git a/.changeset/itchy-camels-grin.md b/.changeset/itchy-camels-grin.md deleted file mode 100644 index eac3c6a102..0000000000 --- a/.changeset/itchy-camels-grin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-splunk-on-call': patch ---- - -Added splunk-on-call plugin. diff --git a/.changeset/loud-owls-beam.md b/.changeset/loud-owls-beam.md deleted file mode 100644 index 764bfdf72a..0000000000 --- a/.changeset/loud-owls-beam.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Added githubApp authentication to the scaffolder-backend plugin diff --git a/.changeset/many-bags-sort.md b/.changeset/many-bags-sort.md deleted file mode 100644 index dbc8df8b0c..0000000000 --- a/.changeset/many-bags-sort.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Support globs in `FileReaderProcessor`. diff --git a/.changeset/mean-grapes-march.md b/.changeset/mean-grapes-march.md deleted file mode 100644 index 6065fe0892..0000000000 --- a/.changeset/mean-grapes-march.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/backend-common': patch -'@backstage/techdocs-common': patch -'@backstage/plugin-scaffolder-backend': patch ---- - -Switched to using `'x-access-token'` for authenticating Git over HTTPS towards GitHub. diff --git a/.changeset/metal-spoons-change.md b/.changeset/metal-spoons-change.md deleted file mode 100644 index 41828d08ae..0000000000 --- a/.changeset/metal-spoons-change.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Updated docker build to use `backstage-cli backend:bundle` instead of `backstage-cli backend:build-image`. - -To apply this change to an existing application, change the following in `packages/backend/package.json`: - -```diff -- "build": "backstage-cli backend:build", -- "build-image": "backstage-cli backend:build-image --build --tag backstage", -+ "build": "backstage-cli backend:bundle", -+ "build-image": "docker build ../.. -f Dockerfile --tag backstage", -``` - -Note that the backend build is switched to `backend:bundle`, and the `build-image` script simply calls `docker build`. This means the `build-image` script no longer builds all packages, so you have to run `yarn build` in the root first. - -In order to work with the new build method, the `Dockerfile` at `packages/backend/Dockerfile` has been updated with the following contents: - -```dockerfile -# This dockerfile builds an image for the backend package. -# It should be executed with the root of the repo as docker context. -# -# Before building this image, be sure to have run the following commands in the repo root: -# -# yarn install -# yarn tsc -# yarn build -# -# Once the commands have been run, you can build the image using `yarn build-image` - -FROM node:14-buster-slim - -WORKDIR /app - -# Copy repo skeleton first, to avoid unnecessary docker cache invalidation. -# The skeleton contains the package.json of each package in the monorepo, -# and along with yarn.lock and the root package.json, that's enough to run yarn install. -ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ - -RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" - -# Then copy the rest of the backend bundle, along with any other files we might want. -ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./ - -CMD ["node", "packages/backend", "--config", "app-config.yaml"] -``` - -Note that the base image has been switched from `node:14-buster` to `node:14-buster-slim`, significantly reducing the image size. This is enabled by the removal of the `nodegit` dependency, so if you are still using this in your project you will have to stick with the `node:14-buster` base image. - -A `.dockerignore` file has been added to the root of the repo as well, in order to keep the docker context upload small. It lives in the root of the repo with the following contents: - -```gitignore -.git -node_modules -packages -!packages/backend/dist -plugins -``` diff --git a/.changeset/new-mangos-tap.md b/.changeset/new-mangos-tap.md deleted file mode 100644 index 1b57510417..0000000000 --- a/.changeset/new-mangos-tap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Tweak error message in lockfile parsing to include more information. diff --git a/.changeset/new-peaches-melt.md b/.changeset/new-peaches-melt.md deleted file mode 100644 index aff00e0fa0..0000000000 --- a/.changeset/new-peaches-melt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Minor typo in migration diff --git a/.changeset/ninety-lemons-shake.md b/.changeset/ninety-lemons-shake.md deleted file mode 100644 index 960a4eaffe..0000000000 --- a/.changeset/ninety-lemons-shake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Limit the props that are forwarded to the `Link` component in the `EntityRefLink`. diff --git a/.changeset/pink-coins-sniff.md b/.changeset/pink-coins-sniff.md deleted file mode 100644 index b9714f116a..0000000000 --- a/.changeset/pink-coins-sniff.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-catalog-react': patch ---- - -Introduce new cards to `@backstage/plugin-catalog` that can be added to entity pages: - -- `EntityHasSystemsCard` to display systems of a domain. -- `EntityHasComponentsCard` to display components of a system. -- `EntityHasSubcomponentsCard` to display subcomponents of a subcomponent. -- In addition, `EntityHasApisCard` to display APIs of a system is added to `@backstage/plugin-api-docs`. - -`@backstage/plugin-catalog-react` now provides an `EntityTable` to build own cards for entities. -The styling of the tables and new cards was also applied to the existing `EntityConsumedApisCard`, -`EntityConsumingComponentsCard`, `EntityProvidedApisCard`, and `EntityProvidingComponentsCard`. diff --git a/.changeset/quick-ways-develop.md b/.changeset/quick-ways-develop.md deleted file mode 100644 index f3c8087bff..0000000000 --- a/.changeset/quick-ways-develop.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@backstage/core': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-catalog-import': patch -'@backstage/plugin-github-actions': patch -'@backstage/plugin-jenkins': patch -'@backstage/plugin-lighthouse': patch -'@backstage/plugin-org': patch -'@backstage/plugin-sentry': patch -'@backstage/plugin-sonarqube': patch ---- - -Use a more strict type for `variant` of cards. diff --git a/.changeset/rich-apricots-lick.md b/.changeset/rich-apricots-lick.md deleted file mode 100644 index f9daf861d2..0000000000 --- a/.changeset/rich-apricots-lick.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Fixed parsing of OIDC key timestamps when using SQLite. diff --git a/.changeset/short-pots-report.md b/.changeset/short-pots-report.md deleted file mode 100644 index 4323a957f3..0000000000 --- a/.changeset/short-pots-report.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-org': patch ---- - -- Fixes padding in `MembersListCard` -- Fixes email icon size in `GroupProfileCard` -- Uniform sizing across `GroupProfileCard` and `UserProfileCard` diff --git a/.changeset/six-elephants-poke.md b/.changeset/six-elephants-poke.md deleted file mode 100644 index 23e575b67c..0000000000 --- a/.changeset/six-elephants-poke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Migrate about card to new composability API, exporting the entity cards as `EntityAboutCard`. diff --git a/.changeset/soft-rings-obey.md b/.changeset/soft-rings-obey.md deleted file mode 100644 index 91308bdc50..0000000000 --- a/.changeset/soft-rings-obey.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Fixed the `prepare` step for when using local templates that were added to the catalog using the `file:` target configuration. -No more `EPERM: operation not permitted` error messages. diff --git a/.changeset/sour-shoes-perform.md b/.changeset/sour-shoes-perform.md deleted file mode 100644 index e2361cb815..0000000000 --- a/.changeset/sour-shoes-perform.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-pagerduty': minor ---- - -Improved the UI of the pagerduty plugin, and added a standalone TriggerButton diff --git a/.changeset/spicy-pants-teach.md b/.changeset/spicy-pants-teach.md deleted file mode 100644 index 8e16ae089e..0000000000 --- a/.changeset/spicy-pants-teach.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Expose `useRelatedEntities` from `@backstage/plugin-catalog-react` to retrieve -entities references via relations from the API. diff --git a/.changeset/strange-olives-unite.md b/.changeset/strange-olives-unite.md deleted file mode 100644 index 7490f6096a..0000000000 --- a/.changeset/strange-olives-unite.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Migrated the package from using moment to Luxon. #4278 diff --git a/.changeset/stupid-maps-do.md b/.changeset/stupid-maps-do.md deleted file mode 100644 index 1cbac64f68..0000000000 --- a/.changeset/stupid-maps-do.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core': patch ---- - -Export Select component diff --git a/.changeset/techdocs-fast-foxes-relate.md b/.changeset/techdocs-fast-foxes-relate.md deleted file mode 100644 index 7377946f86..0000000000 --- a/.changeset/techdocs-fast-foxes-relate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/techdocs-common': patch ---- - -After TechDocs generate step, insert build timestamp to techdocs_metadata.json diff --git a/.changeset/techdocs-improve-error-reporting.md b/.changeset/techdocs-improve-error-reporting.md deleted file mode 100644 index fc4e550afd..0000000000 --- a/.changeset/techdocs-improve-error-reporting.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/techdocs-common': patch ---- - -Improved error reporting in AzureBlobStorage to surface errors when fetching metadata and uploading files fails. diff --git a/.changeset/techdocs-metal-turkeys-sleep.md b/.changeset/techdocs-metal-turkeys-sleep.md deleted file mode 100644 index 79cf11ce53..0000000000 --- a/.changeset/techdocs-metal-turkeys-sleep.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/techdocs-common': patch ---- - -Pass user and group ID when invoking docker container. When TechDocs invokes Docker, docker could be run as a `root` user which results in generation of files by applications run by non-root user (e.g. TechDocs) will not have access to modify. This PR passes in current user and group ID to docker so that the file permissions of the generated files and folders are correct. diff --git a/.changeset/techdocs-spotty-cooks-wait.md b/.changeset/techdocs-spotty-cooks-wait.md deleted file mode 100644 index 9ec35b9257..0000000000 --- a/.changeset/techdocs-spotty-cooks-wait.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/techdocs-common': patch -'@backstage/plugin-techdocs-backend': patch ---- - -Add etag of the prepared file tree to techdocs_metadata.json in the storage diff --git a/.changeset/thick-scissors-notice.md b/.changeset/thick-scissors-notice.md deleted file mode 100644 index ccf78f3abf..0000000000 --- a/.changeset/thick-scissors-notice.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core': patch ---- - -Make sure that SidebarItems are also active when on sub route. diff --git a/.changeset/tough-worms-clap.md b/.changeset/tough-worms-clap.md deleted file mode 100644 index 3a18593549..0000000000 --- a/.changeset/tough-worms-clap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Fix parsing of the path to default to empty string not undefined if git-url-parse throws something we don't expect. Fixes the error `The "path" argument must be of type string.` when preparing. diff --git a/.changeset/violet-maps-occur.md b/.changeset/violet-maps-occur.md deleted file mode 100644 index 112095d257..0000000000 --- a/.changeset/violet-maps-occur.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/core': patch ---- - -Add support for custom empty state of `Table` components. - -You can now optionally pass `emptyContent` to `Table` that is displayed -if the table has now rows. diff --git a/.changeset/wicked-forks-sleep.md b/.changeset/wicked-forks-sleep.md deleted file mode 100644 index 8f7f034094..0000000000 --- a/.changeset/wicked-forks-sleep.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/cli': patch -'@backstage/create-app': patch ---- - -Upgrading to lerna@4.0.0. diff --git a/.changeset/wise-books-turn.md b/.changeset/wise-books-turn.md deleted file mode 100644 index 77a42c5304..0000000000 --- a/.changeset/wise-books-turn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core': patch ---- - -Add Breadcrumbs component diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 6190dda9da..b85ffd3ae1 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,53 @@ # example-app +## 0.2.16 + +### Patch Changes + +- Updated dependencies [6c4a76c59] +- Updated dependencies [32a950409] +- Updated dependencies [f10950bd2] +- Updated dependencies [914c89b13] +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [257a753ff] +- Updated dependencies [d872f662d] +- Updated dependencies [9337f509d] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [e8692df4a] +- Updated dependencies [53b69236d] +- Updated dependencies [549a859ac] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [532bc0ec0] +- Updated dependencies [688b73110] + - @backstage/plugin-scaffolder@0.5.1 + - @backstage/plugin-catalog@0.3.2 + - @backstage/core@0.6.2 + - @backstage/cli@0.6.1 + - @backstage/plugin-user-settings@0.2.6 + - @backstage/plugin-catalog-react@0.0.4 + - @backstage/plugin-api-docs@0.4.6 + - @backstage/plugin-catalog-import@0.4.1 + - @backstage/plugin-github-actions@0.3.3 + - @backstage/plugin-jenkins@0.3.10 + - @backstage/plugin-lighthouse@0.2.11 + - @backstage/plugin-org@0.3.7 + - @backstage/plugin-sentry@0.3.6 + - @backstage/plugin-pagerduty@0.3.0 + - @backstage/plugin-circleci@0.2.9 + - @backstage/plugin-cloudbuild@0.2.10 + - @backstage/plugin-explore@0.2.6 + - @backstage/plugin-kafka@0.2.3 + - @backstage/plugin-kubernetes@0.3.10 + - @backstage/plugin-register-component@0.2.10 + - @backstage/plugin-rollbar@0.3.1 + - @backstage/plugin-search@0.3.1 + - @backstage/plugin-techdocs@0.5.7 + ## 0.2.15 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 88d039e7c8..19e4e38e5c 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,39 +1,39 @@ { "name": "example-app", - "version": "0.2.15", + "version": "0.2.16", "private": true, "bundled": true, "dependencies": { "@backstage/catalog-model": "^0.7.1", - "@backstage/cli": "^0.6.0", - "@backstage/core": "^0.6.1", - "@backstage/plugin-api-docs": "^0.4.5", - "@backstage/plugin-catalog": "^0.3.1", - "@backstage/plugin-catalog-react": "^0.0.3", - "@backstage/plugin-catalog-import": "^0.4.0", - "@backstage/plugin-circleci": "^0.2.8", - "@backstage/plugin-cloudbuild": "^0.2.9", + "@backstage/cli": "^0.6.1", + "@backstage/core": "^0.6.2", + "@backstage/plugin-api-docs": "^0.4.6", + "@backstage/plugin-catalog": "^0.3.2", + "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/plugin-catalog-import": "^0.4.1", + "@backstage/plugin-circleci": "^0.2.9", + "@backstage/plugin-cloudbuild": "^0.2.10", "@backstage/plugin-cost-insights": "^0.8.1", - "@backstage/plugin-explore": "^0.2.5", + "@backstage/plugin-explore": "^0.2.6", "@backstage/plugin-gcp-projects": "^0.2.4", - "@backstage/plugin-github-actions": "^0.3.2", + "@backstage/plugin-github-actions": "^0.3.3", "@backstage/plugin-gitops-profiles": "^0.2.5", "@backstage/plugin-graphiql": "^0.2.7", - "@backstage/plugin-org": "^0.3.6", - "@backstage/plugin-jenkins": "^0.3.9", - "@backstage/plugin-kafka": "^0.2.2", - "@backstage/plugin-kubernetes": "^0.3.9", - "@backstage/plugin-lighthouse": "^0.2.10", + "@backstage/plugin-org": "^0.3.7", + "@backstage/plugin-jenkins": "^0.3.10", + "@backstage/plugin-kafka": "^0.2.3", + "@backstage/plugin-kubernetes": "^0.3.10", + "@backstage/plugin-lighthouse": "^0.2.11", "@backstage/plugin-newrelic": "^0.2.5", - "@backstage/plugin-pagerduty": "0.2.8", - "@backstage/plugin-register-component": "^0.2.9", - "@backstage/plugin-rollbar": "^0.3.0", - "@backstage/plugin-scaffolder": "^0.5.0", - "@backstage/plugin-sentry": "^0.3.5", - "@backstage/plugin-search": "^0.3.0", + "@backstage/plugin-pagerduty": "0.3.0", + "@backstage/plugin-register-component": "^0.2.10", + "@backstage/plugin-rollbar": "^0.3.1", + "@backstage/plugin-scaffolder": "^0.5.1", + "@backstage/plugin-sentry": "^0.3.6", + "@backstage/plugin-search": "^0.3.1", "@backstage/plugin-tech-radar": "^0.3.5", - "@backstage/plugin-techdocs": "^0.5.6", - "@backstage/plugin-user-settings": "^0.2.5", + "@backstage/plugin-techdocs": "^0.5.7", + "@backstage/plugin-user-settings": "^0.2.6", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 42f1fa4a42..bb913c8fdb 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/backend-common +## 0.5.4 + +### Patch Changes + +- 16fb1d03a: pass registered logger to requestLoggingHandler +- 491f3a0ec: Implement `UrlReader.search` for the other providers (Azure, Bitbucket, GitLab) as well. + + The `UrlReader` subclasses now are implemented in terms of the respective `Integration` class. + +- 434b4e81a: Support globs in `FileReaderProcessor`. +- fb28da212: Switched to using `'x-access-token'` for authenticating Git over HTTPS towards GitHub. +- Updated dependencies [491f3a0ec] + - @backstage/integration@0.5.0 + ## 0.5.3 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 7b1c6870ab..3cd817427f 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.5.3", + "version": "0.5.4", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -32,7 +32,7 @@ "@backstage/cli-common": "^0.1.1", "@backstage/config": "^0.1.2", "@backstage/config-loader": "^0.5.1", - "@backstage/integration": "^0.4.0", + "@backstage/integration": "^0.5.0", "@octokit/rest": "^18.0.12", "@types/cors": "^2.8.6", "@types/express": "^4.17.6", @@ -68,7 +68,7 @@ } }, "devDependencies": { - "@backstage/cli": "^0.6.0", + "@backstage/cli": "^0.6.1", "@backstage/test-utils": "^0.1.7", "@types/archiver": "^5.1.0", "@types/compression": "^1.7.0", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index c40f7605eb..f8c09d45b9 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/cli +## 0.6.1 + +### Patch Changes + +- 257a753ff: Updated transform of `.esm.js` files to be able to handle dynamic imports. +- 9337f509d: Tweak error message in lockfile parsing to include more information. +- 532bc0ec0: Upgrading to lerna@4.0.0. + ## 0.6.0 ### Minor Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index d360727ebd..5be3a2c695 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.6.0", + "version": "0.6.1", "private": false, "publishConfig": { "access": "public" @@ -115,10 +115,10 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-common": "^0.5.2", + "@backstage/backend-common": "^0.5.4", "@backstage/config": "^0.1.2", - "@backstage/core": "^0.6.0", - "@backstage/dev-utils": "^0.1.9", + "@backstage/core": "^0.6.2", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.6", "@backstage/theme": "^0.2.3", "@types/diff": "^4.0.2", diff --git a/packages/core-api/CHANGELOG.md b/packages/core-api/CHANGELOG.md index a40e25b2a8..14a5aa899d 100644 --- a/packages/core-api/CHANGELOG.md +++ b/packages/core-api/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/core-api +## 0.2.10 + +### Patch Changes + +- f10950bd2: Minor refactoring of BackstageApp.getSystemIcons to support custom registered + icons. Custom Icons can be added using: + + ```tsx + import AlarmIcon from '@material-ui/icons/Alarm'; + import MyPersonIcon from './MyPerson'; + + const app = createApp({ + icons: { + user: MyPersonIcon // override system icon + alert: AlarmIcon, // Custom icon + }, + }); + ``` + +- fd3f2a8c0: Export `createExternalRouteRef`, as well as give it an `id` for easier debugging, and fix parameter requirements when used with `useRouteRef`. + ## 0.2.9 ### Patch Changes diff --git a/packages/core-api/package.json b/packages/core-api/package.json index 927eca3490..cfe90ebb34 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-api", "description": "Internal Core API used by Backstage plugins and apps", - "version": "0.2.9", + "version": "0.2.10", "private": false, "publishConfig": { "access": "public", @@ -42,7 +42,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.0", + "@backstage/cli": "^0.6.1", "@backstage/test-utils": "^0.1.6", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index a5a56f633a..bd800e8d63 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/core +## 0.6.2 + +### Patch Changes + +- fd3f2a8c0: Export `createExternalRouteRef`, as well as give it an `id` for easier debugging, and fix parameter requirements when used with `useRouteRef`. +- f4c2bcf54: Use a more strict type for `variant` of cards. +- 07e226872: Export Select component +- f62e7abe5: Make sure that SidebarItems are also active when on sub route. +- 96f378d10: Add support for custom empty state of `Table` components. + + You can now optionally pass `emptyContent` to `Table` that is displayed + if the table has now rows. + +- 688b73110: Add Breadcrumbs component +- Updated dependencies [f10950bd2] +- Updated dependencies [fd3f2a8c0] + - @backstage/core-api@0.2.10 + ## 0.6.1 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index c9045c6465..c93f9cad9c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core", "description": "Core API used by Backstage plugins and apps", - "version": "0.6.1", + "version": "0.6.2", "private": false, "publishConfig": { "access": "public", @@ -30,7 +30,7 @@ }, "dependencies": { "@backstage/config": "^0.1.2", - "@backstage/core-api": "^0.2.8", + "@backstage/core-api": "^0.2.10", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -66,7 +66,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.0", + "@backstage/cli": "^0.6.1", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 6a0f75ebaf..1735d0c1a0 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,113 @@ # @backstage/create-app +## 0.3.10 + +### Patch Changes + +- d50e9b81e: Updated docker build to use `backstage-cli backend:bundle` instead of `backstage-cli backend:build-image`. + + To apply this change to an existing application, change the following in `packages/backend/package.json`: + + ```diff + - "build": "backstage-cli backend:build", + - "build-image": "backstage-cli backend:build-image --build --tag backstage", + + "build": "backstage-cli backend:bundle", + + "build-image": "docker build ../.. -f Dockerfile --tag backstage", + ``` + + Note that the backend build is switched to `backend:bundle`, and the `build-image` script simply calls `docker build`. This means the `build-image` script no longer builds all packages, so you have to run `yarn build` in the root first. + + In order to work with the new build method, the `Dockerfile` at `packages/backend/Dockerfile` has been updated with the following contents: + + ```dockerfile + # This dockerfile builds an image for the backend package. + # It should be executed with the root of the repo as docker context. + # + # Before building this image, be sure to have run the following commands in the repo root: + # + # yarn install + # yarn tsc + # yarn build + # + # Once the commands have been run, you can build the image using `yarn build-image` + + FROM node:14-buster-slim + + WORKDIR /app + + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. + # The skeleton contains the package.json of each package in the monorepo, + # and along with yarn.lock and the root package.json, that's enough to run yarn install. + ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + + RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" + + # Then copy the rest of the backend bundle, along with any other files we might want. + ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./ + + CMD ["node", "packages/backend", "--config", "app-config.yaml"] + ``` + + Note that the base image has been switched from `node:14-buster` to `node:14-buster-slim`, significantly reducing the image size. This is enabled by the removal of the `nodegit` dependency, so if you are still using this in your project you will have to stick with the `node:14-buster` base image. + + A `.dockerignore` file has been added to the root of the repo as well, in order to keep the docker context upload small. It lives in the root of the repo with the following contents: + + ```gitignore + .git + node_modules + packages + !packages/backend/dist + plugins + ``` + +- 532bc0ec0: Upgrading to lerna@4.0.0. +- Updated dependencies [16fb1d03a] +- Updated dependencies [92f01d75c] +- Updated dependencies [6c4a76c59] +- Updated dependencies [32a950409] +- Updated dependencies [491f3a0ec] +- Updated dependencies [f10950bd2] +- Updated dependencies [914c89b13] +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [257a753ff] +- Updated dependencies [d872f662d] +- Updated dependencies [edbc27bfd] +- Updated dependencies [434b4e81a] +- Updated dependencies [fb28da212] +- Updated dependencies [9337f509d] +- Updated dependencies [0ada34a0f] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [d9687c524] +- Updated dependencies [53b69236d] +- Updated dependencies [29c8bcc53] +- Updated dependencies [3600ac3b0] +- Updated dependencies [07e226872] +- Updated dependencies [b0a41c707] +- Updated dependencies [f62e7abe5] +- Updated dependencies [a341a8716] +- Updated dependencies [96f378d10] +- Updated dependencies [532bc0ec0] +- Updated dependencies [688b73110] + - @backstage/backend-common@0.5.4 + - @backstage/plugin-auth-backend@0.3.1 + - @backstage/plugin-scaffolder@0.5.1 + - @backstage/plugin-catalog@0.3.2 + - @backstage/core@0.6.2 + - @backstage/cli@0.6.1 + - @backstage/plugin-user-settings@0.2.6 + - @backstage/plugin-scaffolder-backend@0.7.1 + - @backstage/plugin-api-docs@0.4.6 + - @backstage/plugin-catalog-import@0.4.1 + - @backstage/plugin-github-actions@0.3.3 + - @backstage/plugin-lighthouse@0.2.11 + - @backstage/plugin-techdocs-backend@0.6.1 + - @backstage/plugin-catalog-backend@0.6.2 + - @backstage/plugin-circleci@0.2.9 + - @backstage/plugin-explore@0.2.6 + - @backstage/plugin-search@0.3.1 + - @backstage/plugin-techdocs@0.5.7 + ## 0.3.9 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 5e4cf8ae11..1bc3e845ce 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "Create app package for Backstage", - "version": "0.3.9", + "version": "0.3.10", "private": false, "publishConfig": { "access": "public" @@ -44,30 +44,30 @@ "ts-node": "^8.6.2" }, "peerDependencies": { - "@backstage/backend-common": "^0.5.3", + "@backstage/backend-common": "^0.5.4", "@backstage/catalog-model": "^0.7.1", - "@backstage/cli": "^0.6.0", + "@backstage/cli": "^0.6.1", "@backstage/config": "^0.1.2", - "@backstage/core": "^0.6.1", - "@backstage/plugin-api-docs": "^0.4.5", + "@backstage/core": "^0.6.2", + "@backstage/plugin-api-docs": "^0.4.6", "@backstage/plugin-app-backend": "^0.3.7", - "@backstage/plugin-auth-backend": "^0.3.0", - "@backstage/plugin-catalog": "^0.3.1", - "@backstage/plugin-catalog-backend": "^0.6.1", - "@backstage/plugin-catalog-import": "^0.4.0", - "@backstage/plugin-circleci": "^0.2.8", - "@backstage/plugin-explore": "^0.2.5", - "@backstage/plugin-github-actions": "^0.3.2", - "@backstage/plugin-lighthouse": "^0.2.10", + "@backstage/plugin-auth-backend": "^0.3.1", + "@backstage/plugin-catalog": "^0.3.2", + "@backstage/plugin-catalog-backend": "^0.6.2", + "@backstage/plugin-catalog-import": "^0.4.1", + "@backstage/plugin-circleci": "^0.2.9", + "@backstage/plugin-explore": "^0.2.6", + "@backstage/plugin-github-actions": "^0.3.3", + "@backstage/plugin-lighthouse": "^0.2.11", "@backstage/plugin-proxy-backend": "^0.2.4", "@backstage/plugin-rollbar-backend": "^0.1.7", - "@backstage/plugin-scaffolder": "^0.5.0", - "@backstage/plugin-search": "^0.3.0", - "@backstage/plugin-scaffolder-backend": "^0.7.0", + "@backstage/plugin-scaffolder": "^0.5.1", + "@backstage/plugin-search": "^0.3.1", + "@backstage/plugin-scaffolder-backend": "^0.7.1", "@backstage/plugin-tech-radar": "^0.3.5", - "@backstage/plugin-techdocs": "^0.5.6", - "@backstage/plugin-techdocs-backend": "^0.6.0", - "@backstage/plugin-user-settings": "^0.2.5", + "@backstage/plugin-techdocs": "^0.5.7", + "@backstage/plugin-techdocs-backend": "^0.6.1", + "@backstage/plugin-user-settings": "^0.2.6", "@backstage/test-utils": "^0.1.7", "@backstage/theme": "^0.2.3" }, diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 4e16deebdc..52de79ca58 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/dev-utils +## 0.1.11 + +### Patch Changes + +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.1.10 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 7f53d029b3..abe21fe0f1 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "0.1.10", + "version": "0.1.11", "private": false, "publishConfig": { "access": "public", @@ -29,9 +29,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.6.1", + "@backstage/core": "^0.6.2", "@backstage/catalog-model": "^0.7.1", - "@backstage/plugin-catalog-react": "^0.0.3", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/test-utils": "^0.1.7", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", @@ -47,7 +47,7 @@ "react-router-dom": "6.0.0-beta.0" }, "devDependencies": { - "@backstage/cli": "^0.6.0", + "@backstage/cli": "^0.6.1", "@types/jest": "^26.0.7", "@types/node": "^12.0.0" }, diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index 7056976458..2f6192c047 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/integration +## 0.5.0 + +### Minor Changes + +- 491f3a0ec: Make `ScmIntegration.resolveUrl` mandatory. + ## 0.4.0 ### Minor Changes diff --git a/packages/integration/package.json b/packages/integration/package.json index b4134ab00e..a7cd63171b 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "0.4.0", + "version": "0.5.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -37,7 +37,7 @@ "luxon": "^1.25.0" }, "devDependencies": { - "@backstage/cli": "^0.6.0", + "@backstage/cli": "^0.6.1", "@backstage/test-utils": "^0.1.7", "@types/jest": "^26.0.7", "@types/luxon": "^1.25.0", diff --git a/packages/techdocs-common/CHANGELOG.md b/packages/techdocs-common/CHANGELOG.md index 9f2a8c0be4..caaa9f5e2f 100644 --- a/packages/techdocs-common/CHANGELOG.md +++ b/packages/techdocs-common/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/techdocs-common +## 0.4.1 + +### Patch Changes + +- fb28da212: Switched to using `'x-access-token'` for authenticating Git over HTTPS towards GitHub. +- 26e143e60: After TechDocs generate step, insert build timestamp to techdocs_metadata.json +- c6655413d: Improved error reporting in AzureBlobStorage to surface errors when fetching metadata and uploading files fails. +- 44414239f: Pass user and group ID when invoking docker container. When TechDocs invokes Docker, docker could be run as a `root` user which results in generation of files by applications run by non-root user (e.g. TechDocs) will not have access to modify. This PR passes in current user and group ID to docker so that the file permissions of the generated files and folders are correct. +- b0a41c707: Add etag of the prepared file tree to techdocs_metadata.json in the storage +- Updated dependencies [16fb1d03a] +- Updated dependencies [491f3a0ec] +- Updated dependencies [491f3a0ec] +- Updated dependencies [434b4e81a] +- Updated dependencies [fb28da212] + - @backstage/backend-common@0.5.4 + - @backstage/integration@0.5.0 + ## 0.4.0 ### Minor Changes diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index f1236cd6d5..9363334641 100644 --- a/packages/techdocs-common/package.json +++ b/packages/techdocs-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/techdocs-common", "description": "Common functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", - "version": "0.4.0", + "version": "0.4.1", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -38,10 +38,10 @@ "dependencies": { "@azure/identity": "^1.2.2", "@azure/storage-blob": "^12.4.0", - "@backstage/backend-common": "^0.5.3", + "@backstage/backend-common": "^0.5.4", "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", - "@backstage/integration": "^0.4.0", + "@backstage/integration": "^0.5.0", "@google-cloud/storage": "^5.6.0", "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", @@ -60,7 +60,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.6.0", + "@backstage/cli": "^0.6.1", "@types/fs-extra": "^9.0.5", "@types/git-url-parse": "^9.0.0", "@types/js-yaml": "^3.12.5", diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 47379a9b18..a6aa6c1e2d 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-api-docs +## 0.4.6 + +### Patch Changes + +- 0af242b6d: Introduce new cards to `@backstage/plugin-catalog` that can be added to entity pages: + + - `EntityHasSystemsCard` to display systems of a domain. + - `EntityHasComponentsCard` to display components of a system. + - `EntityHasSubcomponentsCard` to display subcomponents of a subcomponent. + - In addition, `EntityHasApisCard` to display APIs of a system is added to `@backstage/plugin-api-docs`. + + `@backstage/plugin-catalog-react` now provides an `EntityTable` to build own cards for entities. + The styling of the tables and new cards was also applied to the existing `EntityConsumedApisCard`, + `EntityConsumingComponentsCard`, `EntityProvidedApisCard`, and `EntityProvidingComponentsCard`. + +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.4.5 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 8df169c1d9..4182ccbe14 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.4.5", + "version": "0.4.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,8 +31,8 @@ "dependencies": { "@asyncapi/react-component": "^0.18.2", "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.1", - "@backstage/plugin-catalog-react": "^0.0.3", + "@backstage/core": "^0.6.2", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/theme": "^0.2.3", "@material-icons/font": "^1.0.2", "@material-ui/core": "^4.11.0", @@ -49,8 +49,8 @@ "swagger-ui-react": "^3.37.2" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index e97d79c4ae..3d69afee37 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-auth-backend +## 0.3.1 + +### Patch Changes + +- 92f01d75c: Refactored auth provider factories to accept options along with other internal refactoring of the auth providers. +- d9687c524: Fixed parsing of OIDC key timestamps when using SQLite. +- 3600ac3b0: Migrated the package from using moment to Luxon. #4278 +- Updated dependencies [16fb1d03a] +- Updated dependencies [491f3a0ec] +- Updated dependencies [434b4e81a] +- Updated dependencies [fb28da212] + - @backstage/backend-common@0.5.4 + ## 0.3.0 ### Minor Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 281ea063fd..f206c0046d 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.5.3", + "@backstage/backend-common": "^0.5.4", "@backstage/catalog-client": "^0.3.6", "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", @@ -66,7 +66,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.6.0", + "@backstage/cli": "^0.6.1", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 9ae261ecf3..dce76b57a3 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend +## 0.6.2 + +### Patch Changes + +- Updated dependencies [16fb1d03a] +- Updated dependencies [491f3a0ec] +- Updated dependencies [491f3a0ec] +- Updated dependencies [434b4e81a] +- Updated dependencies [fb28da212] + - @backstage/backend-common@0.5.4 + - @backstage/integration@0.5.0 + ## 0.6.1 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index c595c8dd5a..bc087a3017 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.6.1", + "version": "0.6.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,10 +30,10 @@ }, "dependencies": { "@azure/msal-node": "^1.0.0-beta.3", - "@backstage/backend-common": "^0.5.3", + "@backstage/backend-common": "^0.5.4", "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", - "@backstage/integration": "^0.4.0", + "@backstage/integration": "^0.5.0", "@octokit/graphql": "^4.5.8", "@types/express": "^4.17.6", "@types/ldapjs": "^1.0.9", @@ -59,7 +59,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", + "@backstage/cli": "^0.6.1", "@backstage/test-utils": "^0.1.7", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 3966557555..e12718bc96 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-catalog-import +## 0.4.1 + +### Patch Changes + +- f4c2bcf54: Use a more strict type for `variant` of cards. +- Updated dependencies [491f3a0ec] +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/integration@0.5.0 + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.4.0 ### Minor Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 3016ada247..ad196e4f1f 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.4.0", + "version": "0.4.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,9 +32,9 @@ "dependencies": { "@backstage/catalog-model": "^0.7.1", "@backstage/catalog-client": "^0.3.6", - "@backstage/core": "^0.6.1", - "@backstage/integration": "^0.4.0", - "@backstage/plugin-catalog-react": "^0.0.3", + "@backstage/core": "^0.6.2", + "@backstage/integration": "^0.5.0", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -51,8 +51,8 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 38cc17e1ba..07898af872 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-catalog-react +## 0.0.4 + +### Patch Changes + +- d34d26125: Limit the props that are forwarded to the `Link` component in the `EntityRefLink`. +- 0af242b6d: Introduce new cards to `@backstage/plugin-catalog` that can be added to entity pages: + + - `EntityHasSystemsCard` to display systems of a domain. + - `EntityHasComponentsCard` to display components of a system. + - `EntityHasSubcomponentsCard` to display subcomponents of a subcomponent. + - In addition, `EntityHasApisCard` to display APIs of a system is added to `@backstage/plugin-api-docs`. + + `@backstage/plugin-catalog-react` now provides an `EntityTable` to build own cards for entities. + The styling of the tables and new cards was also applied to the existing `EntityConsumedApisCard`, + `EntityConsumingComponentsCard`, `EntityProvidedApisCard`, and `EntityProvidingComponentsCard`. + +- 10a0124e0: Expose `useRelatedEntities` from `@backstage/plugin-catalog-react` to retrieve + entities references via relations from the API. +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + ## 0.0.3 ### Patch Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 649f3b2ff6..432c59ef3b 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-react", - "version": "0.0.3", + "version": "0.0.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,7 +30,7 @@ "dependencies": { "@backstage/catalog-client": "^0.3.6", "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.1", + "@backstage/core": "^0.6.2", "@material-ui/core": "^4.11.0", "@types/react": "^16.9", "react": "^16.13.1", @@ -39,8 +39,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index d786fc2f15..6dc2913b60 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,54 @@ # @backstage/plugin-catalog +## 0.3.2 + +### Patch Changes + +- 32a950409: Hide the kind of the owner if it's the default kind for the `ownedBy` + relationship (group). +- f10950bd2: Minor refactoring of BackstageApp.getSystemIcons to support custom registered + icons. Custom Icons can be added using: + + ```tsx + import AlarmIcon from '@material-ui/icons/Alarm'; + import MyPersonIcon from './MyPerson'; + + const app = createApp({ + icons: { + user: MyPersonIcon // override system icon + alert: AlarmIcon, // Custom icon + }, + }); + ``` + +- 914c89b13: Remove the "Move repository" menu entry from the catalog page, as it's just a placeholder. +- 0af242b6d: Introduce new cards to `@backstage/plugin-catalog` that can be added to entity pages: + + - `EntityHasSystemsCard` to display systems of a domain. + - `EntityHasComponentsCard` to display components of a system. + - `EntityHasSubcomponentsCard` to display subcomponents of a subcomponent. + - In addition, `EntityHasApisCard` to display APIs of a system is added to `@backstage/plugin-api-docs`. + + `@backstage/plugin-catalog-react` now provides an `EntityTable` to build own cards for entities. + The styling of the tables and new cards was also applied to the existing `EntityConsumedApisCard`, + `EntityConsumingComponentsCard`, `EntityProvidedApisCard`, and `EntityProvidingComponentsCard`. + +- f4c2bcf54: Use a more strict type for `variant` of cards. +- 53b69236d: Migrate about card to new composability API, exporting the entity cards as `EntityAboutCard`. +- Updated dependencies [6c4a76c59] +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/plugin-scaffolder@0.5.1 + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.3.1 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 072acc6472..a03be16632 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.3.1", + "version": "0.3.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,9 +32,9 @@ "dependencies": { "@backstage/catalog-client": "^0.3.6", "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.1", - "@backstage/plugin-catalog-react": "^0.0.3", - "@backstage/plugin-scaffolder": "^0.5.0", + "@backstage/core": "^0.6.2", + "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/plugin-scaffolder": "^0.5.1", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -51,8 +51,8 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@microsoft/microsoft-graph-types": "^1.25.0", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index 1c5d35aa70..47e15ac88e 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-circleci +## 0.2.9 + +### Patch Changes + +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.2.8 ### Patch Changes diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 6f741ab1ce..9c7794dd59 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-circleci", - "version": "0.2.8", + "version": "0.2.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,8 +32,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.1", - "@backstage/plugin-catalog-react": "^0.0.3", + "@backstage/core": "^0.6.2", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -50,8 +50,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md index 8dccd1dbbd..498f32489b 100644 --- a/plugins/cloudbuild/CHANGELOG.md +++ b/plugins/cloudbuild/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-cloudbuild +## 0.2.10 + +### Patch Changes + +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.2.9 ### Patch Changes diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index a00fe13fe0..1638bd1446 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-cloudbuild", - "version": "0.2.9", + "version": "0.2.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,8 +31,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.1", - "@backstage/plugin-catalog-react": "^0.0.3", - "@backstage/core": "^0.6.1", + "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/core": "^0.6.2", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,8 +47,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 2f0511c217..d4b8ee1ad0 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/config": "^0.1.2", - "@backstage/core": "^0.6.1", + "@backstage/core": "^0.6.2", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -55,8 +55,8 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/explore/CHANGELOG.md b/plugins/explore/CHANGELOG.md index b0334effe1..698cf08c34 100644 --- a/plugins/explore/CHANGELOG.md +++ b/plugins/explore/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-explore +## 0.2.6 + +### Patch Changes + +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.2.5 ### Patch Changes diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 497e108b6f..697a8c67fe 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-explore", - "version": "0.2.5", + "version": "0.2.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,8 +31,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.1", - "@backstage/plugin-catalog-react": "^0.0.3", + "@backstage/core": "^0.6.2", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/plugin-explore-react": "^0.0.2", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", @@ -45,8 +45,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/fossa/CHANGELOG.md b/plugins/fossa/CHANGELOG.md index 1e9e08d7f3..ebbfef0ff3 100644 --- a/plugins/fossa/CHANGELOG.md +++ b/plugins/fossa/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-fossa +## 0.2.2 + +### Patch Changes + +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.2.1 ### Patch Changes diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index 334469184f..6cc2c30b29 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-fossa", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,8 +32,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.1", - "@backstage/plugin-catalog-react": "^0.0.3", + "@backstage/core": "^0.6.2", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -44,8 +44,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index 36dc403d7b..d5b19fa239 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.6.1", + "@backstage/core": "^0.6.2", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,8 +41,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index 8aba49fedc..4dff868c42 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-github-actions +## 0.3.3 + +### Patch Changes + +- f4c2bcf54: Use a more strict type for `variant` of cards. +- Updated dependencies [491f3a0ec] +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/integration@0.5.0 + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.3.2 ### Patch Changes diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index e5498563a5..44599c8c36 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-github-actions", - "version": "0.3.2", + "version": "0.3.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,9 +33,9 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.1", - "@backstage/plugin-catalog-react": "^0.0.3", - "@backstage/core": "^0.6.1", - "@backstage/integration": "^0.4.0", + "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/core": "^0.6.2", + "@backstage/integration": "^0.5.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -50,8 +50,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 251185b799..415c43c598 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.6.1", + "@backstage/core": "^0.6.2", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -42,8 +42,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index 307cef0dee..48e87877d4 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.6.1", + "@backstage/core": "^0.6.2", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,8 +43,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md index 26c838d97a..872b2c3146 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-jenkins +## 0.3.10 + +### Patch Changes + +- f4c2bcf54: Use a more strict type for `variant` of cards. +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.3.9 ### Patch Changes diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index fb3e719858..bad68dadb5 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-jenkins", - "version": "0.3.9", + "version": "0.3.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,8 +32,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.1", - "@backstage/plugin-catalog-react": "^0.0.3", + "@backstage/core": "^0.6.2", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,8 +47,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/kafka/CHANGELOG.md b/plugins/kafka/CHANGELOG.md index 3d78708345..7972091b84 100644 --- a/plugins/kafka/CHANGELOG.md +++ b/plugins/kafka/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-kafka +## 0.2.3 + +### Patch Changes + +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.2.2 ### Patch Changes diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index 46097f1c29..df8581751e 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kafka", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.1", - "@backstage/plugin-catalog-react": "^0.0.3", + "@backstage/core": "^0.6.2", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -33,8 +33,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index 91f6f8c672..4be7017d35 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-kubernetes +## 0.3.10 + +### Patch Changes + +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.3.9 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 3d7e30373b..e06d53989c 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.3.9", + "version": "0.3.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,9 +32,9 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.1", - "@backstage/plugin-catalog-react": "^0.0.3", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/config": "^0.1.2", - "@backstage/core": "^0.6.1", + "@backstage/core": "^0.6.2", "@backstage/plugin-kubernetes-backend": "^0.2.6", "@backstage/theme": "^0.2.3", "@kubernetes/client-node": "^0.13.2", @@ -48,8 +48,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index fab61283d9..e4697a66d7 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-lighthouse +## 0.2.11 + +### Patch Changes + +- f4c2bcf54: Use a more strict type for `variant` of cards. +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.2.10 ### Patch Changes diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 064e260389..899c173616 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-lighthouse", - "version": "0.2.10", + "version": "0.2.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,8 +33,8 @@ "dependencies": { "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", - "@backstage/core": "^0.6.1", - "@backstage/plugin-catalog-react": "^0.0.3", + "@backstage/core": "^0.6.2", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -46,8 +46,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 6ab18a9cf5..daf48cb1bb 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.6.1", + "@backstage/core": "^0.6.2", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,8 +41,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 898705cdc4..9347ca7ca1 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-org +## 0.3.7 + +### Patch Changes + +- f4c2bcf54: Use a more strict type for `variant` of cards. +- e8692df4a: - Fixes padding in `MembersListCard` + - Fixes email icon size in `GroupProfileCard` + - Uniform sizing across `GroupProfileCard` and `UserProfileCard` +- Updated dependencies [f10950bd2] +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core-api@0.2.10 + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.3.6 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index 4a278a22f5..2d114a9184 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.3.6", + "version": "0.3.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.1", - "@backstage/core-api": "^0.2.9", - "@backstage/plugin-catalog-react": "^0.0.3", + "@backstage/core": "^0.6.2", + "@backstage/core-api": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -35,8 +35,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/pagerduty/CHANGELOG.md b/plugins/pagerduty/CHANGELOG.md index 3fe8e6db70..ec4bda11ea 100644 --- a/plugins/pagerduty/CHANGELOG.md +++ b/plugins/pagerduty/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-pagerduty +## 0.3.0 + +### Minor Changes + +- 549a859ac: Improved the UI of the pagerduty plugin, and added a standalone TriggerButton + +### Patch Changes + +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.2.8 ### Patch Changes diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 93a9ce3e4b..a03462df7d 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-pagerduty", - "version": "0.2.8", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,8 +31,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.1", - "@backstage/plugin-catalog-react": "^0.0.3", + "@backstage/core": "^0.6.2", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -46,8 +46,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/register-component/CHANGELOG.md b/plugins/register-component/CHANGELOG.md index 30844b6fc6..e1786c95c7 100644 --- a/plugins/register-component/CHANGELOG.md +++ b/plugins/register-component/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-register-component +## 0.2.10 + +### Patch Changes + +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.2.9 ### Patch Changes diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index ee95eae540..78a8323fdc 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-register-component", - "version": "0.2.9", + "version": "0.2.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,8 +31,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.1", - "@backstage/plugin-catalog-react": "^0.0.3", + "@backstage/core": "^0.6.2", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -45,8 +45,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index 61ae7e2706..a9a28b4e95 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-rollbar +## 0.3.1 + +### Patch Changes + +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.3.0 ### Minor Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 39ecae186c..ba8947850e 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,8 +32,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.1", - "@backstage/plugin-catalog-react": "^0.0.3", + "@backstage/core": "^0.6.2", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,8 +47,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 5a3553dc79..2e56afc8c8 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-scaffolder-backend +## 0.7.1 + +### Patch Changes + +- edbc27bfd: Added githubApp authentication to the scaffolder-backend plugin +- fb28da212: Switched to using `'x-access-token'` for authenticating Git over HTTPS towards GitHub. +- 0ada34a0f: Minor typo in migration +- 29c8bcc53: Fixed the `prepare` step for when using local templates that were added to the catalog using the `file:` target configuration. + No more `EPERM: operation not permitted` error messages. +- a341a8716: Fix parsing of the path to default to empty string not undefined if git-url-parse throws something we don't expect. Fixes the error `The "path" argument must be of type string.` when preparing. +- Updated dependencies [16fb1d03a] +- Updated dependencies [491f3a0ec] +- Updated dependencies [491f3a0ec] +- Updated dependencies [434b4e81a] +- Updated dependencies [fb28da212] + - @backstage/backend-common@0.5.4 + - @backstage/integration@0.5.0 + ## 0.7.0 ### Minor Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 3b25fb3881..06cac907de 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "0.7.0", + "version": "0.7.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,11 +29,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.5.3", + "@backstage/backend-common": "^0.5.4", "@backstage/catalog-client": "^0.3.6", "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", - "@backstage/integration": "^0.4.0", + "@backstage/integration": "^0.5.0", "@gitbeaker/core": "^28.0.2", "@gitbeaker/node": "^28.0.2", "@octokit/rest": "^18.0.12", @@ -61,7 +61,7 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.6.0", + "@backstage/cli": "^0.6.1", "@backstage/test-utils": "^0.1.7", "@types/fs-extra": "^9.0.1", "@types/mock-fs": "^4.13.0", diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 2e0676e3aa..34b042b3a9 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-scaffolder +## 0.5.1 + +### Patch Changes + +- 6c4a76c59: Make the `TemplateCard` conform to what material-ui recommends in their examples. This fixes the extra padding around the buttons. +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.5.0 ### Minor Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 2b0e1a3771..fa9625e594 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "0.5.0", + "version": "0.5.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,8 +31,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.1", - "@backstage/plugin-catalog-react": "^0.0.3", + "@backstage/core": "^0.6.2", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -51,8 +51,8 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@backstage/catalog-client": "^0.3.6", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 38cbf202e1..887d922613 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-search +## 0.3.1 + +### Patch Changes + +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.3.0 ### Minor Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 56f863a2a6..e1770abfa2 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,9 +29,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.6.1", + "@backstage/core": "^0.6.2", "@backstage/catalog-model": "^0.7.1", - "@backstage/plugin-catalog-react": "^0.0.3", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,8 +43,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index 4c4d68c67b..d8446686b3 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-sentry +## 0.3.6 + +### Patch Changes + +- f4c2bcf54: Use a more strict type for `variant` of cards. +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.3.5 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index b79b7613fe..a3a88e1bee 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sentry", - "version": "0.3.5", + "version": "0.3.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,8 +32,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.1", - "@backstage/plugin-catalog-react": "^0.0.3", + "@backstage/core": "^0.6.2", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -46,8 +46,8 @@ "timeago.js": "^4.0.2" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/sonarqube/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index 11306bb5cd..85d861e5d3 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-sonarqube +## 0.1.12 + +### Patch Changes + +- 3a82293da: Fix bug retrieving current theme +- f4c2bcf54: Use a more strict type for `variant` of cards. +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.1.11 ### Patch Changes diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index e8236fbfd6..0219a9d8fa 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sonarqube", - "version": "0.1.11", + "version": "0.1.12", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,8 +33,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.1", - "@backstage/plugin-catalog-react": "^0.0.3", - "@backstage/core": "^0.6.1", + "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/core": "^0.6.2", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,8 +47,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/splunk-on-call/CHANGELOG.md b/plugins/splunk-on-call/CHANGELOG.md new file mode 100644 index 0000000000..ad98bf1feb --- /dev/null +++ b/plugins/splunk-on-call/CHANGELOG.md @@ -0,0 +1,18 @@ +# @backstage/plugin-splunk-on-call + +## 0.1.2 + +### Patch Changes + +- 70e2ba9cf: Added splunk-on-call plugin. +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/plugin-catalog-react@0.0.4 diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index c0542bd64c..3573c1bc5e 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-splunk-on-call", - "version": "0.1.1", + "version": "0.1.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,8 +31,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.1", - "@backstage/plugin-catalog-react": "^0.0.2", + "@backstage/core": "^0.6.2", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -45,8 +45,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 7a7c886372..5b21613053 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -30,7 +30,7 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.6.1", + "@backstage/core": "^0.6.2", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,8 +43,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 50925cec5f..cae37c076e 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-techdocs-backend +## 0.6.1 + +### Patch Changes + +- b0a41c707: Add etag of the prepared file tree to techdocs_metadata.json in the storage +- Updated dependencies [16fb1d03a] +- Updated dependencies [491f3a0ec] +- Updated dependencies [434b4e81a] +- Updated dependencies [fb28da212] +- Updated dependencies [26e143e60] +- Updated dependencies [c6655413d] +- Updated dependencies [44414239f] +- Updated dependencies [b0a41c707] + - @backstage/backend-common@0.5.4 + - @backstage/techdocs-common@0.4.1 + ## 0.6.0 ### Minor Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 2d4d1309c0..994e05fefa 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "0.6.0", + "version": "0.6.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,10 +30,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.5.3", + "@backstage/backend-common": "^0.5.4", "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", - "@backstage/techdocs-common": "^0.4.0", + "@backstage/techdocs-common": "^0.4.1", "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", "cross-fetch": "^3.0.6", @@ -45,7 +45,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.6.0", + "@backstage/cli": "^0.6.1", "supertest": "^4.0.2" }, "files": [ diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index f782dcb3ad..1772ea62fe 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-techdocs +## 0.5.7 + +### Patch Changes + +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [fb28da212] +- Updated dependencies [d34d26125] +- Updated dependencies [0af242b6d] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [10a0124e0] +- Updated dependencies [07e226872] +- Updated dependencies [26e143e60] +- Updated dependencies [c6655413d] +- Updated dependencies [44414239f] +- Updated dependencies [b0a41c707] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + - @backstage/techdocs-common@0.4.1 + - @backstage/plugin-catalog-react@0.0.4 + ## 0.5.6 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 32983270ad..fdedc7d4dc 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "0.5.6", + "version": "0.5.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,11 +33,11 @@ "dependencies": { "@backstage/config": "^0.1.2", "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.1", - "@backstage/plugin-catalog-react": "^0.0.3", + "@backstage/core": "^0.6.2", + "@backstage/plugin-catalog-react": "^0.0.4", "@backstage/test-utils": "^0.1.7", "@backstage/theme": "^0.2.3", - "@backstage/techdocs-common": "^0.4.0", + "@backstage/techdocs-common": "^0.4.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -50,8 +50,8 @@ "sanitize-html": "^2.3.2" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 467f207754..d35b347e16 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-user-settings +## 0.2.6 + +### Patch Changes + +- d872f662d: Use routed tabs to link to every settings page. +- Updated dependencies [fd3f2a8c0] +- Updated dependencies [f4c2bcf54] +- Updated dependencies [07e226872] +- Updated dependencies [f62e7abe5] +- Updated dependencies [96f378d10] +- Updated dependencies [688b73110] + - @backstage/core@0.6.2 + ## 0.2.5 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 493fb16a28..f6cd6231c1 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.2.5", + "version": "0.2.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.6.1", + "@backstage/core": "^0.6.2", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,8 +41,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index b6744216f3..81ba864a24 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -30,7 +30,7 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.6.1", + "@backstage/core": "^0.6.2", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,8 +41,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", - "@backstage/dev-utils": "^0.1.10", + "@backstage/cli": "^0.6.1", + "@backstage/dev-utils": "^0.1.11", "@backstage/test-utils": "^0.1.7", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", From 220337900080570cc3b2618d32bb3b11b83b4c17 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Thu, 18 Feb 2021 09:41:09 -0500 Subject: [PATCH 145/270] Fix "it" possessive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw --- docs/support/project-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/support/project-structure.md b/docs/support/project-structure.md index d0d69b4330..564e7f8e7e 100644 --- a/docs/support/project-structure.md +++ b/docs/support/project-structure.md @@ -193,7 +193,7 @@ We can categorize plugins into three different types; **Frontend**, **Backend** and **GraphQL**. We differentiate these types of plugins when we name them, with a dash-suffix. `-backend` means it’s a backend plugin and so on. -One reason for splitting a plugin is because of it's dependencies. Another +One reason for splitting a plugin is because of its dependencies. Another reason is for clear separation of concerns. Take a look at our [Plugin Marketplace](https://backstage.io/plugins) or browse From fd55339103dc9ebefc311e35f113c74fc4c99b95 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Thu, 18 Feb 2021 09:46:38 -0500 Subject: [PATCH 146/270] Prettier fix --- docs/support/project-structure.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/support/project-structure.md b/docs/support/project-structure.md index 564e7f8e7e..55a92da571 100644 --- a/docs/support/project-structure.md +++ b/docs/support/project-structure.md @@ -193,8 +193,8 @@ We can categorize plugins into three different types; **Frontend**, **Backend** and **GraphQL**. We differentiate these types of plugins when we name them, with a dash-suffix. `-backend` means it’s a backend plugin and so on. -One reason for splitting a plugin is because of its dependencies. Another -reason is for clear separation of concerns. +One reason for splitting a plugin is because of its dependencies. Another reason +is for clear separation of concerns. Take a look at our [Plugin Marketplace](https://backstage.io/plugins) or browse through the From fde9584fd1acf49c3d942f42b1179d82c31b425c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 18 Feb 2021 15:49:48 +0100 Subject: [PATCH 147/270] Update kafka package.json to include the config schema --- plugins/kafka-backend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/kafka-backend/package.json b/plugins/kafka-backend/package.json index 1eaa1d8316..1280e30633 100644 --- a/plugins/kafka-backend/package.json +++ b/plugins/kafka-backend/package.json @@ -50,6 +50,6 @@ }, "files": [ "dist", - "schema.d.ts" + "config.d.ts" ] } From 669b497fd237ea88388d7eca3f4e8d6842e120cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 18 Feb 2021 15:57:03 +0100 Subject: [PATCH 148/270] yarn.lock update --- yarn.lock | 35 ++++++++++------------------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/yarn.lock b/yarn.lock index d7bcc7a3a5..8f4a643c8e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1827,10 +1827,10 @@ yup "^0.29.3" "@backstage/core@^0.3.0": - version "0.6.1" + version "0.6.2" dependencies: "@backstage/config" "^0.1.2" - "@backstage/core-api" "^0.2.8" + "@backstage/core-api" "^0.2.10" "@backstage/theme" "^0.2.3" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" @@ -1865,29 +1865,14 @@ remark-gfm "^1.0.0" zen-observable "^0.8.15" -"@backstage/plugin-catalog-react@^0.0.2": - version "0.0.2" - resolved "https://registry.npmjs.org/@backstage/plugin-catalog-react/-/plugin-catalog-react-0.0.2.tgz#e50da2dac9fab3a0d5973f8d1083ee2c368e5e52" - integrity sha512-O6aujFPRaEFTk4XlwOoswbnoHIOqMtj6ycUj6R1mNKOM4plUgGDKKhO3be69FHMJEMbiSvVe6AW+1kXaK+1LqA== - dependencies: - "@backstage/catalog-client" "^0.3.5" - "@backstage/catalog-model" "^0.7.1" - "@backstage/core" "^0.6.0" - "@material-ui/core" "^4.11.0" - "@types/react" "^16.9" - react "^16.13.1" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" - "@backstage/plugin-catalog@^0.2.0": - version "0.3.1" + version "0.3.2" dependencies: "@backstage/catalog-client" "^0.3.6" "@backstage/catalog-model" "^0.7.1" - "@backstage/core" "^0.6.1" - "@backstage/plugin-catalog-react" "^0.0.3" - "@backstage/plugin-scaffolder" "^0.5.0" + "@backstage/core" "^0.6.2" + "@backstage/plugin-catalog-react" "^0.0.4" + "@backstage/plugin-scaffolder" "^0.5.1" "@backstage/theme" "^0.2.3" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" @@ -1904,13 +1889,13 @@ swr "^0.3.0" "@backstage/plugin-catalog@^0.2.1": - version "0.3.1" + version "0.3.2" dependencies: "@backstage/catalog-client" "^0.3.6" "@backstage/catalog-model" "^0.7.1" - "@backstage/core" "^0.6.1" - "@backstage/plugin-catalog-react" "^0.0.3" - "@backstage/plugin-scaffolder" "^0.5.0" + "@backstage/core" "^0.6.2" + "@backstage/plugin-catalog-react" "^0.0.4" + "@backstage/plugin-scaffolder" "^0.5.1" "@backstage/theme" "^0.2.3" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" From 862595ce5014412f5c79de66ee33d66d8febaafb Mon Sep 17 00:00:00 2001 From: Jeff Durand Date: Thu, 18 Feb 2021 13:52:39 +0000 Subject: [PATCH 149/270] tech-radar: add description dialog box --- .../src/components/RadarComponent.tsx | 1 + .../RadarDescription.test.tsx | 43 +++++++++++++ .../RadarDescription/RadarDescription.tsx | 62 ++++++++++++++++++ .../src/components/RadarDescription/index.ts | 17 +++++ .../components/RadarEntry/RadarEntry.test.tsx | 36 +++++++++-- .../src/components/RadarEntry/RadarEntry.tsx | 47 +++++++++++++- .../components/RadarLegend/RadarLegend.tsx | 63 ++++++++++++++++++- .../src/components/RadarPlot/RadarPlot.tsx | 2 + plugins/tech-radar/src/utils/types.ts | 2 + 9 files changed, 262 insertions(+), 11 deletions(-) create mode 100644 plugins/tech-radar/src/components/RadarDescription/RadarDescription.test.tsx create mode 100644 plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx create mode 100644 plugins/tech-radar/src/components/RadarDescription/index.ts diff --git a/plugins/tech-radar/src/components/RadarComponent.tsx b/plugins/tech-radar/src/components/RadarComponent.tsx index 759fec62c3..bed45e63fc 100644 --- a/plugins/tech-radar/src/components/RadarComponent.tsx +++ b/plugins/tech-radar/src/components/RadarComponent.tsx @@ -68,6 +68,7 @@ const RadarComponent = (props: TechRadarComponentProps): JSX.Element => { }; }), moved: entry.timeline[0].moved, + description: entry.timeline[0].description, url: entry.url, }; }); diff --git a/plugins/tech-radar/src/components/RadarDescription/RadarDescription.test.tsx b/plugins/tech-radar/src/components/RadarDescription/RadarDescription.test.tsx new file mode 100644 index 0000000000..54b8ef0892 --- /dev/null +++ b/plugins/tech-radar/src/components/RadarDescription/RadarDescription.test.tsx @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 React from 'react'; +import { render, screen } from '@testing-library/react'; +import { ThemeProvider } from '@material-ui/core'; +import { lightTheme } from '@backstage/theme'; + +import RadarDescription, { Props } from './RadarDescription'; + +const minProps: Props = { + open: true, + title: 'example-title', + description: 'example-description', + onClose: () => {}, +}; + +describe('RadarDescription', () => { + it('should render', () => { + render( + + + , + ); + + const radarDescription = screen.getByTestId('radar-description'); + expect(radarDescription).toBeInTheDocument(); + expect(screen.getByText(String(minProps.description))).toBeInTheDocument(); + }); +}); diff --git a/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx b/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx new file mode 100644 index 0000000000..ecef4900fb --- /dev/null +++ b/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 React from 'react'; +import Dialog from '@material-ui/core/Dialog'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import { Button, DialogActions, DialogContent } from '@material-ui/core'; +import LinkIcon from '@material-ui/icons/Link'; + +export type Props = { + open: boolean; + onClose: () => void; + title: string; + description: string; + url?: string; +}; + +const RadarDescription = (props: Props): JSX.Element => { + // const classes = useStyles(props); + const { open, onClose, title, description, url } = props; + + const handleClick = () => { + onClose(); + if (url) { + window.location.href = url; + } + }; + + return ( + + {title} + {description} + {url && ( + + + + )} + + ); +}; + +export default RadarDescription; diff --git a/plugins/tech-radar/src/components/RadarDescription/index.ts b/plugins/tech-radar/src/components/RadarDescription/index.ts new file mode 100644 index 0000000000..908e97701e --- /dev/null +++ b/plugins/tech-radar/src/components/RadarDescription/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { default } from './RadarDescription'; diff --git a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.test.tsx b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.test.tsx index 2d24f301d1..0424624007 100644 --- a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.test.tsx +++ b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.test.tsx @@ -15,7 +15,8 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; import GetBBoxPolyfill from '../../utils/polyfills/getBBox'; @@ -29,6 +30,12 @@ const minProps: Props = { color: 'red', }; +const optionalProps: Props = { + ...minProps, + title: 'example-title', + description: 'example-description', +}; + describe('RadarEntry', () => { beforeAll(() => { GetBBoxPolyfill.create(0, 0, 1000, 500); @@ -38,8 +45,8 @@ describe('RadarEntry', () => { GetBBoxPolyfill.remove(); }); - it('should render', () => { - const rendered = render( + it('should render link only', () => { + render( @@ -47,10 +54,29 @@ describe('RadarEntry', () => { , ); - const radarEntry = rendered.getByTestId('radar-entry'); + const radarEntry = screen.getByTestId('radar-entry'); const { x, y } = minProps; expect(radarEntry).toBeInTheDocument(); expect(radarEntry.getAttribute('transform')).toBe(`translate(${x}, ${y})`); - expect(rendered.getByText(String(minProps.value))).toBeInTheDocument(); + expect(screen.getByText(String(minProps.value))).toBeInTheDocument(); + }); + + it('should render with description', () => { + render( + + + + + , + ); + + userEvent.click(screen.getByRole('button')); + + const radarEntry = screen.getByTestId('radar-entry'); + expect(radarEntry).toBeInTheDocument(); + + const radarDescription = screen.getByTestId('radar-description'); + expect(radarDescription).toBeInTheDocument(); + expect(screen.getByText(String(minProps.value))).toBeInTheDocument(); }); }); diff --git a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx index 29d35e01e2..f146eb5fa8 100644 --- a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx +++ b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx @@ -17,6 +17,7 @@ import React from 'react'; import { makeStyles, Theme } from '@material-ui/core'; import { WithLink } from '../../utils/components'; +import RadarDescription from '../RadarDescription'; export type Props = { x: number; @@ -25,6 +26,8 @@ export type Props = { color: string; url?: string; moved?: number; + description?: string; + title?: string; onMouseEnter?: (event: React.MouseEvent) => void; onMouseLeave?: (event: React.MouseEvent) => void; onClick?: (event: React.MouseEvent) => void; @@ -59,9 +62,12 @@ const makeBlip = (color: string, moved?: number) => { const RadarEntry = (props: Props): JSX.Element => { const classes = useStyles(props); + const [open, setOpen] = React.useState(false); const { moved, + description, + title, color, url, value, @@ -74,6 +80,18 @@ const RadarEntry = (props: Props): JSX.Element => { const blip = makeBlip(color, moved); + const handleClickOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + + const toggle = () => { + setOpen(!open); + }; + return ( { onClick={onClick} data-testid="radar-entry" > - - {blip} - + {' '} + {description ? ( + <> + + {blip} + + + + ) : ( + + {blip} + + )} {value} diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx index e10993b010..998a3370c1 100644 --- a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx @@ -17,6 +17,7 @@ import React from 'react'; import { makeStyles, Theme } from '@material-ui/core'; import type { Quadrant, Ring, Entry } from '../../utils/types'; import { WithLink } from '../../utils/components'; +import RadarDescription from '../RadarDescription'; type Segments = { [k: number]: { [k: number]: Entry[] }; @@ -105,6 +106,60 @@ const RadarLegend = (props: Props): JSX.Element => { onEntryMouseLeave?: Props['onEntryMouseEnter']; }; + type RadarLegendLinkProps = { + url?: string; + description?: string; + title?: string; + }; + + const RadarLegendLink = ({ + url, + description, + title, + }: RadarLegendLinkProps) => { + const [open, setOpen] = React.useState(false); + + const handleClickOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + + const toggle = () => { + setOpen(!open); + }; + + if (description && url) { + return ( + <> + + {title} + + + + ); + } + return ( + + {title} + + ); + }; + const RadarLegendRing = ({ ring, entries, @@ -129,9 +184,11 @@ const RadarLegend = (props: Props): JSX.Element => { onEntryMouseLeave && (() => onEntryMouseLeave(entry)) } > - - {entry.title} - + ))} diff --git a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx index 7f68ffe021..4dd11c6ccf 100644 --- a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx +++ b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx @@ -73,7 +73,9 @@ const RadarPlot = (props: Props): JSX.Element => { color={entry.color || ''} value={(entry?.index || 0) + 1} url={entry.url} + description={entry.description} moved={entry.moved} + title={entry.title} onMouseEnter={onEntryMouseEnter && (() => onEntryMouseEnter(entry))} onMouseLeave={onEntryMouseLeave && (() => onEntryMouseLeave(entry))} /> diff --git a/plugins/tech-radar/src/utils/types.ts b/plugins/tech-radar/src/utils/types.ts index 3322a67dcb..e4b2ef6fbe 100644 --- a/plugins/tech-radar/src/utils/types.ts +++ b/plugins/tech-radar/src/utils/types.ts @@ -68,6 +68,8 @@ export type Entry = { url?: string; // How this entry has recently moved; -1 for "down", +1 for "up", 0 for not moved moved?: MovedState; + // Most recent description to display in the UI + description?: string; active?: boolean; timeline?: Array; }; From 9f2b3a26e9e860aa1042eb83613edc3c99238e8a Mon Sep 17 00:00:00 2001 From: Jeff Durand Date: Thu, 18 Feb 2021 16:47:33 +0000 Subject: [PATCH 150/270] added changeset description --- .changeset/popular-donuts-fetch.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/popular-donuts-fetch.md diff --git a/.changeset/popular-donuts-fetch.md b/.changeset/popular-donuts-fetch.md new file mode 100644 index 0000000000..9681bdb875 --- /dev/null +++ b/.changeset/popular-donuts-fetch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-radar': patch +--- + +Added a dialog box that will show up when a you click on link on the radar and display the description if provided. From 5f5c98e2ae9d286f8f7bcbd65a7b05119f6f9e3a Mon Sep 17 00:00:00 2001 From: Jeff Durand Date: Thu, 18 Feb 2021 17:54:06 +0000 Subject: [PATCH 151/270] updated button to learn more based on reviewer suggestions --- .../src/components/RadarDescription/RadarDescription.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx b/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx index ecef4900fb..24e31dd34f 100644 --- a/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx +++ b/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx @@ -49,9 +49,8 @@ const RadarDescription = (props: Props): JSX.Element => { onClick={handleClick} color="primary" startIcon={} - variant="contained" > - Visit URL + LEARN MORE )} From 1966cc1802a2b993b3c32e2c863b47cc259e18a3 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 18 Feb 2021 14:11:52 +0100 Subject: [PATCH 152/270] TechDocs: AWS Publisher - fix upload path to be OS agnostic --- packages/techdocs-common/__mocks__/aws-sdk.ts | 54 ++++++++++++------- .../src/stages/publish/awsS3.test.ts | 33 ++++++++---- .../src/stages/publish/awsS3.ts | 12 ++++- 3 files changed, 68 insertions(+), 31 deletions(-) diff --git a/packages/techdocs-common/__mocks__/aws-sdk.ts b/packages/techdocs-common/__mocks__/aws-sdk.ts index b956b4309a..b546494118 100644 --- a/packages/techdocs-common/__mocks__/aws-sdk.ts +++ b/packages/techdocs-common/__mocks__/aws-sdk.ts @@ -15,7 +15,28 @@ */ import type { S3 as S3Types } from 'aws-sdk'; import { EventEmitter } from 'events'; -import fs from 'fs'; +import fs from 'fs-extra'; +import os from 'os'; +import path from 'path'; + +const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; + +/** + * @param Key Contains either / or \ as file separator depending upon OS. + */ +const checkFileExists = async (Key: string): Promise => { + // Key will always have / as file separator irrespective of OS since S3 expects /. + // Normalize Key to OS specific path before checking if file exists. + const relativeFilePath = Key.split(path.posix.sep).join(path.sep); + const filePath = path.join(rootDir, Key); + + try { + await fs.access(filePath, fs.constants.F_OK); + return true; + } catch (err) { + return false; + } +}; export class S3 { private readonly options; @@ -26,22 +47,27 @@ export class S3 { headObject({ Key }: { Key: string }) { return { - promise: () => this.checkFileExists(Key), + promise: async () => { + if (!(await checkFileExists(Key))) { + throw new Error('File does not exist'); + } + }, }; } getObject({ Key }: { Key: string }) { + const filePath = path.join(rootDir, Key); return { - promise: () => this.checkFileExists(Key), + promise: () => checkFileExists(filePath), createReadStream: () => { const emitter = new EventEmitter(); process.nextTick(() => { - if (fs.existsSync(Key)) { - emitter.emit('data', Buffer.from(fs.readFileSync(Key))); + if (fs.existsSync(filePath)) { + emitter.emit('data', Buffer.from(fs.readFileSync(filePath))); } else { emitter.emit( 'error', - new Error(`The file ${Key} does not exist !`), + new Error(`The file ${filePath} does not exist !`), ); } emitter.emit('end'); @@ -51,16 +77,6 @@ export class S3 { }; } - checkFileExists(Key: string) { - return new Promise((resolve, reject) => { - if (fs.existsSync(Key)) { - resolve(''); - } else { - reject({ message: 'The object does not exist !' }); - } - }); - } - headBucket() { return new Promise(resolve => { resolve(''); @@ -70,9 +86,9 @@ export class S3 { upload({ Key }: { Key: string }) { return { promise: () => - new Promise((resolve, reject) => { - if (!fs.existsSync(Key)) { - reject(''); + new Promise(async (resolve, reject) => { + if (!(await checkFileExists(Key))) { + reject(`The file ${Key} does not exist`); } else { resolve(''); } diff --git a/packages/techdocs-common/src/stages/publish/awsS3.test.ts b/packages/techdocs-common/src/stages/publish/awsS3.test.ts index 9376e74e92..bf029dd7db 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.test.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.test.ts @@ -16,7 +16,8 @@ import type { Entity, EntityName } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import mockFs from 'mock-fs'; -import path from 'path'; +import * as os from 'os'; +import * as path from 'path'; import * as winston from 'winston'; import { AwsS3Publish } from './awsS3'; import { PublisherBase, TechDocsMetadata } from './types'; @@ -41,13 +42,15 @@ const createMockEntityName = (): EntityName => ({ namespace: 'test-namespace', }); +const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; + const getEntityRootDir = (entity: Entity) => { const { kind, metadata: { namespace, name }, } = entity; - const entityRootDir = path.join(namespace as string, kind, name); - return entityRootDir; + + return path.join(rootDir, namespace as string, kind, name); }; const logger = winston.createLogger(); @@ -57,6 +60,7 @@ jest.spyOn(logger, 'error').mockReturnValue(logger); let publisher: PublisherBase; beforeEach(() => { + mockFs.restore(); const mockConfig = new ConfigReader({ techdocs: { requestUrl: 'http://localhost:7000', @@ -78,6 +82,10 @@ beforeEach(() => { describe('AwsS3Publish', () => { describe('publish', () => { + afterEach(() => { + mockFs.restore(); + }); + it('should publish a directory', async () => { const entity = createMockEntity(); const entityRootDir = getEntityRootDir(entity); @@ -98,11 +106,11 @@ describe('AwsS3Publish', () => { directory: entityRootDir, }), ).toBeUndefined(); - mockFs.restore(); }); it('should fail to publish a directory', async () => { const wrongPathToGeneratedDirectory = path.join( + rootDir, 'wrong', 'path', 'to', @@ -126,13 +134,18 @@ describe('AwsS3Publish', () => { entity, directory: wrongPathToGeneratedDirectory, }) - .catch(error => - expect(error).toEqual( - new Error( - `Unable to upload file(s) to AWS S3. Error Failed to read template directory: ENOENT, no such file or directory '${wrongPathToGeneratedDirectory}'`, + .catch(error => { + expect(error.message).toEqual( + // Can not do exact error message match due to mockFs adding unexpected characters in the path when throwing the error + // Issue reported https://github.com/tschaub/mock-fs/issues/118 + expect.stringContaining( + `Unable to upload file(s) to AWS S3. Error Failed to read template directory: ENOENT, no such file or directory`, ), - ), - ); + ); + expect(error.message).toEqual( + expect.stringContaining(wrongPathToGeneratedDirectory), + ); + }); mockFs.restore(); }); }); diff --git a/packages/techdocs-common/src/stages/publish/awsS3.ts b/packages/techdocs-common/src/stages/publish/awsS3.ts index b0b29e8654..6145ec6365 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.ts @@ -140,9 +140,17 @@ export class AwsS3Publish implements PublisherBase { // Remove the absolute path prefix of the source directory // Path of all files to upload, relative to the root of the source directory // e.g. ['index.html', 'sub-page/index.html', 'assets/images/favicon.png'] - const relativeFilePath = filePath.replace(`${directory}/`, ''); + const relativeFilePath = path.relative(directory, filePath); + + // Convert destination file path to a POSIX path for uploading. + // S3 expects / as path separator and relativeFilePath will contain \\ on Windows. + // https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html + const relativeFilePathPosix = relativeFilePath + .split(path.sep) + .join(path.posix.sep); + const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`; - const destination = `${entityRootDir}/${relativeFilePath}`; // S3 Bucket file relative path + const destination = `${entityRootDir}/${relativeFilePathPosix}`; // S3 Bucket file relative path const fileContent = await fs.readFile(filePath, 'utf8'); From 4b3500021c2311fe59c4d8b2a15e5872f8d0c7a0 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 18 Feb 2021 20:55:35 +0100 Subject: [PATCH 153/270] TechDocs/GCS: Fix publisher to work with Windows file path --- .../__mocks__/@google-cloud/storage.ts | 29 ++++++++++++- packages/techdocs-common/__mocks__/aws-sdk.ts | 2 +- .../src/stages/publish/googleStorage.test.ts | 36 ++++++++++++---- .../src/stages/publish/googleStorage.ts | 42 +++++++++++-------- 4 files changed, 82 insertions(+), 27 deletions(-) diff --git a/packages/techdocs-common/__mocks__/@google-cloud/storage.ts b/packages/techdocs-common/__mocks__/@google-cloud/storage.ts index b84018c089..503ee397b4 100644 --- a/packages/techdocs-common/__mocks__/@google-cloud/storage.ts +++ b/packages/techdocs-common/__mocks__/@google-cloud/storage.ts @@ -13,10 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import fs from 'fs-extra'; +import path from 'path'; + type storageOptions = { keyFilename?: string; }; +/** + * @param sourceFile contains either / or \ as file separator depending upon OS. + */ +const checkFileExists = async (sourceFile: string): Promise => { + // sourceFile will always have / as file separator irrespective of OS since S3 expects /. + // Normalize sourceFile to OS specific path before checking if file exists. + const filePath = sourceFile.split(path.posix.sep).join(path.sep); + + try { + await fs.access(filePath, fs.constants.F_OK); + return true; + } catch (err) { + console.log("File doesn't exist"); + console.log(filePath); + return false; + } +}; + class Bucket { private readonly bucketName; @@ -31,8 +52,12 @@ class Bucket { } upload(source: string, { destination }) { - return new Promise(resolve => { - resolve({ source, destination }); + return new Promise(async (resolve, reject) => { + if (await checkFileExists(source)) { + resolve({ source, destination }); + } else { + reject(`Source file ${source} does not exist.`); + } }); } } diff --git a/packages/techdocs-common/__mocks__/aws-sdk.ts b/packages/techdocs-common/__mocks__/aws-sdk.ts index b546494118..6957ea7430 100644 --- a/packages/techdocs-common/__mocks__/aws-sdk.ts +++ b/packages/techdocs-common/__mocks__/aws-sdk.ts @@ -22,7 +22,7 @@ import path from 'path'; const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; /** - * @param Key Contains either / or \ as file separator depending upon OS. + * @param Key contains either / or \ as file separator depending upon OS. */ const checkFileExists = async (Key: string): Promise => { // Key will always have / as file separator irrespective of OS since S3 expects /. diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts index f8fb00647c..bf6822b82c 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts @@ -13,18 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import mockFs from 'mock-fs'; -import * as winston from 'winston'; +import { getVoidLogger } from '@backstage/backend-common'; +import { Entity } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; +import mockFs from 'mock-fs'; +import os from 'os'; +import path from 'path'; import { GoogleGCSPublish } from './googleStorage'; import { PublisherBase } from './types'; -const createMockEntity = (annotations = {}) => { +const createMockEntity = (annotations = {}): Entity => { return { apiVersion: 'version', kind: 'TestKind', metadata: { name: 'test-component-name', + namespace: 'test-namespace', annotations: { ...annotations, }, @@ -32,12 +36,24 @@ const createMockEntity = (annotations = {}) => { }; }; -const logger = winston.createLogger(); +const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; + +const getEntityRootDir = (entity: Entity) => { + const { + kind, + metadata: { namespace, name }, + } = entity; + + return path.join(rootDir, namespace as string, kind, name); +}; + +const logger = getVoidLogger(); jest.spyOn(logger, 'info').mockReturnValue(logger); let publisher: PublisherBase; beforeEach(async () => { + mockFs.restore(); const mockConfig = new ConfigReader({ techdocs: { requestUrl: 'http://localhost:7000', @@ -55,9 +71,16 @@ beforeEach(async () => { }); describe('GoogleGCSPublish', () => { + afterEach(() => { + mockFs.restore(); + }); + it('should publish a directory', async () => { + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); + mockFs({ - '/path/to/generatedDirectory': { + [entityRootDir]: { 'index.html': '', '404.html': '', assets: { @@ -66,11 +89,10 @@ describe('GoogleGCSPublish', () => { }, }); - const entity = createMockEntity(); expect( await publisher.publish({ entity, - directory: '/path/to/generatedDirectory', + directory: entityRootDir, }), ).toBeUndefined(); mockFs.restore(); diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.ts b/packages/techdocs-common/src/stages/publish/googleStorage.ts index 8876007504..1041794161 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.ts @@ -13,20 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import path from 'path'; -import express from 'express'; -import { - Storage, - UploadResponse, - FileExistsResponse, -} from '@google-cloud/storage'; -import { Logger } from 'winston'; import { Entity, EntityName } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import { getHeadersForFileExtension, getFileTreeRecursively } from './helpers'; -import { PublisherBase, PublishRequest, TechDocsMetadata } from './types'; +import { + FileExistsResponse, + Storage, + UploadResponse, +} from '@google-cloud/storage'; +import express from 'express'; import JSON5 from 'json5'; import createLimiter from 'p-limit'; +import path from 'path'; +import { Logger } from 'winston'; +import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers'; +import { PublisherBase, PublishRequest, TechDocsMetadata } from './types'; export class GoogleGCSPublish implements PublisherBase { static async fromConfig( @@ -105,19 +105,27 @@ export class GoogleGCSPublish implements PublisherBase { const limiter = createLimiter(10); const uploadPromises: Array> = []; - allFilesToUpload.forEach(filePath => { + allFilesToUpload.forEach(sourceFilePath => { // Remove the absolute path prefix of the source directory // Path of all files to upload, relative to the root of the source directory // e.g. ['index.html', 'sub-page/index.html', 'assets/images/favicon.png'] - const relativeFilePath = filePath.replace(`${directory}/`, ''); + const relativeFilePath = path.relative(directory, sourceFilePath); + + // Convert destination file path to a POSIX path for uploading. + // GCS expects / as path separator and relativeFilePath will contain \\ on Windows. + // https://cloud.google.com/storage/docs/gsutil/addlhelp/HowSubdirectoriesWork + const relativeFilePathPosix = relativeFilePath + .split(path.sep) + .join(path.posix.sep); + const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`; - const destination = `${entityRootDir}/${relativeFilePath}`; // GCS Bucket file relative path + const destination = `${entityRootDir}/${relativeFilePathPosix}`; // GCS Bucket file relative path // Rate limit the concurrent execution of file uploads to batches of 10 (per publish) const uploadFile = limiter(() => - this.storageClient - .bucket(this.bucketName) - .upload(filePath, { destination }), + this.storageClient.bucket(this.bucketName).upload(sourceFilePath, { + destination, + }), ); uploadPromises.push(uploadFile); }); @@ -130,7 +138,7 @@ export class GoogleGCSPublish implements PublisherBase { resolve(undefined); }) .catch((err: Error) => { - const errorMessage = `Unable to upload file(s) to Google Cloud Storage. Error ${err.message}`; + const errorMessage = `Unable to upload file(s) to Google Cloud Storage. Error ${err}`; this.logger.error(errorMessage); reject(errorMessage); }); From 9ff646b6a89bd8aa118470800933d58f8c143fc8 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 18 Feb 2021 21:33:53 +0100 Subject: [PATCH 154/270] TechDocs/GCS: Use async/await and add more test --- .../src/stages/publish/awsS3.test.ts | 35 ++++++------ .../src/stages/publish/awsS3.ts | 2 +- .../src/stages/publish/googleStorage.test.ts | 54 +++++++++++++++++-- .../src/stages/publish/googleStorage.ts | 27 +++++----- 4 files changed, 80 insertions(+), 38 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/awsS3.test.ts b/packages/techdocs-common/src/stages/publish/awsS3.test.ts index bf029dd7db..4f586119e5 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.test.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.test.ts @@ -82,11 +82,7 @@ beforeEach(() => { describe('AwsS3Publish', () => { describe('publish', () => { - afterEach(() => { - mockFs.restore(); - }); - - it('should publish a directory', async () => { + beforeEach(() => { const entity = createMockEntity(); const entityRootDir = getEntityRootDir(entity); @@ -99,6 +95,15 @@ describe('AwsS3Publish', () => { }, }, }); + }); + + afterEach(() => { + mockFs.restore(); + }); + + it('should publish a directory', async () => { + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); expect( await publisher.publish({ @@ -116,18 +121,14 @@ describe('AwsS3Publish', () => { 'to', 'generatedDirectory', ); - const entity = createMockEntity(); - const entityRootDir = getEntityRootDir(entity); - mockFs({ - [entityRootDir]: { - 'index.html': '', - '404.html': '', - assets: { - 'main.css': '', - }, - }, - }); + const entity = createMockEntity(); + await expect( + publisher.publish({ + entity, + directory: wrongPathToGeneratedDirectory, + }), + ).rejects.toThrowError(); await publisher .publish({ @@ -139,7 +140,7 @@ describe('AwsS3Publish', () => { // Can not do exact error message match due to mockFs adding unexpected characters in the path when throwing the error // Issue reported https://github.com/tschaub/mock-fs/issues/118 expect.stringContaining( - `Unable to upload file(s) to AWS S3. Error Failed to read template directory: ENOENT, no such file or directory`, + `Unable to upload file(s) to AWS S3. Error: Failed to read template directory: ENOENT, no such file or directory`, ), ); expect(error.message).toEqual( diff --git a/packages/techdocs-common/src/stages/publish/awsS3.ts b/packages/techdocs-common/src/stages/publish/awsS3.ts index 6145ec6365..50f55ffe91 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.ts @@ -172,7 +172,7 @@ export class AwsS3Publish implements PublisherBase { ); return; } catch (e) { - const errorMessage = `Unable to upload file(s) to AWS S3. Error ${e.message}`; + const errorMessage = `Unable to upload file(s) to AWS S3. ${e}`; this.logger.error(errorMessage); throw new Error(errorMessage); } diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts index bf6822b82c..9d4904b223 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts @@ -71,11 +71,7 @@ beforeEach(async () => { }); describe('GoogleGCSPublish', () => { - afterEach(() => { - mockFs.restore(); - }); - - it('should publish a directory', async () => { + beforeEach(() => { const entity = createMockEntity(); const entityRootDir = getEntityRootDir(entity); @@ -88,6 +84,15 @@ describe('GoogleGCSPublish', () => { }, }, }); + }); + + afterEach(() => { + mockFs.restore(); + }); + + it('should publish a directory', async () => { + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); expect( await publisher.publish({ @@ -97,4 +102,43 @@ describe('GoogleGCSPublish', () => { ).toBeUndefined(); mockFs.restore(); }); + + it('should fail to publish a directory', async () => { + const wrongPathToGeneratedDirectory = path.join( + rootDir, + 'wrong', + 'path', + 'to', + 'generatedDirectory', + ); + + const entity = createMockEntity(); + + await expect( + publisher.publish({ + entity, + directory: wrongPathToGeneratedDirectory, + }), + ).rejects.toThrowError(); + + await publisher + .publish({ + entity, + directory: wrongPathToGeneratedDirectory, + }) + .catch(error => { + expect(error.message).toEqual( + // Can not do exact error message match due to mockFs adding unexpected characters in the path when throwing the error + // Issue reported https://github.com/tschaub/mock-fs/issues/118 + expect.stringContaining( + `Unable to upload file(s) to Google Cloud Storage. Error: Failed to read template directory: ENOENT, no such file or directory`, + ), + ); + expect(error.message).toEqual( + expect.stringContaining(wrongPathToGeneratedDirectory), + ); + }); + + mockFs.restore(); + }); }); diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.ts b/packages/techdocs-common/src/stages/publish/googleStorage.ts index 1041794161..f45fb7eafb 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.ts @@ -97,8 +97,8 @@ export class GoogleGCSPublish implements PublisherBase { * Upload all the files from the generated `directory` to the GCS bucket. * Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html */ - publish({ entity, directory }: PublishRequest): Promise { - return new Promise(async (resolve, reject) => { + async publish({ entity, directory }: PublishRequest): Promise { + try { // Note: GCS manages creation of parent directories if they do not exist. // So collecting path of only the files is good enough. const allFilesToUpload = await getFileTreeRecursively(directory); @@ -130,19 +130,16 @@ export class GoogleGCSPublish implements PublisherBase { uploadPromises.push(uploadFile); }); - Promise.all(uploadPromises) - .then(() => { - this.logger.info( - `Successfully uploaded all the generated files for Entity ${entity.metadata.name}. Total number of files: ${allFilesToUpload.length}`, - ); - resolve(undefined); - }) - .catch((err: Error) => { - const errorMessage = `Unable to upload file(s) to Google Cloud Storage. Error ${err}`; - this.logger.error(errorMessage); - reject(errorMessage); - }); - }); + await Promise.all(uploadPromises); + + this.logger.info( + `Successfully uploaded all the generated files for Entity ${entity.metadata.name}. Total number of files: ${allFilesToUpload.length}`, + ); + } catch (e) { + const errorMessage = `Unable to upload file(s) to Google Cloud Storage. ${e}`; + this.logger.error(errorMessage); + throw new Error(errorMessage); + } } fetchTechDocsMetadata(entityName: EntityName): Promise { From 6c36689039d5a952c7c8d9da7e6c5b9dfb989426 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 18 Feb 2021 21:58:44 +0100 Subject: [PATCH 155/270] TechDocs/Azure: Fix publisher to work with Windows file paths --- .../stages/publish/azureBlobStorage.test.ts | 70 ++++++++----------- .../src/stages/publish/azureBlobStorage.ts | 44 +++++++----- 2 files changed, 57 insertions(+), 57 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts index 62c81df5c4..e43f9112f5 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import mockFs from 'mock-fs'; -import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; +import type { Entity } from '@backstage/catalog-model'; +import { ConfigReader } from '@backstage/config'; +import mockFs from 'mock-fs'; import { AzureBlobStoragePublish } from './azureBlobStorage'; import { PublisherBase } from './types'; -import type { Entity } from '@backstage/catalog-model'; -import type { Logger } from 'winston'; const createMockEntity = (annotations = {}) => { return { @@ -52,34 +51,33 @@ function createLogger() { } let publisher: PublisherBase; - -describe('publishing with valid credentials', () => { - let logger: Logger; - - beforeEach(async () => { - const mockConfig = new ConfigReader({ - techdocs: { - requestUrl: 'http://localhost:7000', - publisher: { - type: 'azureBlobStorage', - azureBlobStorage: { - credentials: { - accountName: 'accountName', - accountKey: 'accountKey', - }, - containerName: 'containerName', +beforeEach(async () => { + mockFs.restore(); + const mockConfig = new ConfigReader({ + techdocs: { + requestUrl: 'http://localhost:7000', + publisher: { + type: 'azureBlobStorage', + azureBlobStorage: { + credentials: { + accountName: 'accountName', + accountKey: 'accountKey', }, + containerName: 'containerName', }, }, - }); - - logger = createLogger(); - - publisher = await AzureBlobStoragePublish.fromConfig(mockConfig, logger); + }, }); + publisher = await AzureBlobStoragePublish.fromConfig( + mockConfig, + createLogger(), + ); +}); + +describe('publishing with valid credentials', () => { describe('publish', () => { - it('should publish a directory', async () => { + beforeEach(() => { const entity = createMockEntity(); const entityRootDir = getEntityRootDir(entity); @@ -92,6 +90,11 @@ describe('publishing with valid credentials', () => { }, }, }); + }); + + it('should publish a directory', async () => { + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); expect( await publisher.publish({ @@ -105,17 +108,6 @@ describe('publishing with valid credentials', () => { it('should fail to publish a directory', async () => { const wrongPathToGeneratedDirectory = 'wrong/path/to/generatedDirectory'; const entity = createMockEntity(); - const entityRootDir = getEntityRootDir(entity); - - mockFs({ - [entityRootDir]: { - 'index.html': '', - '404.html': '', - assets: { - 'main.css': '', - }, - }, - }); await publisher .publish({ @@ -124,7 +116,7 @@ describe('publishing with valid credentials', () => { }) .catch(error => expect(error.message).toContain( - 'Unable to upload file(s) to Azure Blob Storage. Failed to read template directory: ENOENT, no such file or directory', + 'Unable to upload file(s) to Azure Blob Storage. Error: Failed to read template directory: ENOENT, no such file or directory', ), ); mockFs.restore(); @@ -235,7 +227,7 @@ describe('error reporting', () => { expect(logger.error).toHaveBeenCalledWith( expect.stringContaining( - `Unable to upload file(s) to Azure Blob Storage. Upload failed for test-namespace/TestKind/test-component-name/index.html with status code 500`, + `Unable to upload file(s) to Azure Blob Storage. Error: Upload failed for test-namespace/TestKind/test-component-name/index.html with status code 500`, ), ); diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts index 7000e9f28c..0d2e9871bf 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts @@ -13,20 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import platformPath from 'path'; -import express from 'express'; +import { DefaultAzureCredential } from '@azure/identity'; import { BlobServiceClient, StorageSharedKeyCredential, } from '@azure/storage-blob'; -import { DefaultAzureCredential } from '@azure/identity'; -import { Logger } from 'winston'; import { Entity, EntityName } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import { getHeadersForFileExtension, getFileTreeRecursively } from './helpers'; -import { PublisherBase, PublishRequest, TechDocsMetadata } from './types'; -import limiterFactory from 'p-limit'; +import express from 'express'; import JSON5 from 'json5'; +import limiterFactory from 'p-limit'; +import { default as path, default as platformPath } from 'path'; +import { Logger } from 'winston'; +import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers'; +import { PublisherBase, PublishRequest, TechDocsMetadata } from './types'; // The number of batches that may be ongoing at the same time. const BATCH_CONCURRENCY = 3; @@ -126,27 +126,35 @@ export class AzureBlobStoragePublish implements PublisherBase { // or start thrashing. const limiter = limiterFactory(BATCH_CONCURRENCY); - const promises = allFilesToUpload.map(filePath => { + const promises = allFilesToUpload.map(sourceFilePath => { // Remove the absolute path prefix of the source directory // Path of all files to upload, relative to the root of the source directory // e.g. ['index.html', 'sub-page/index.html', 'assets/images/favicon.png'] - const relativeFilePath = filePath.replace(`${directory}/`, ''); - const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`; - const destination = platformPath.normalize( - `${entityRootDir}/${relativeFilePath}`, - ); // Azure Blob Storage Container file relative path + const relativeFilePath = path.normalize( + path.relative(directory, sourceFilePath), + ); + // Convert destination file path to a POSIX path for uploading. + // Azure Blob Storage expects / as path separator and relativeFilePath will contain \\ on Windows. + // https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#blob-names + const relativeFilePathPosix = relativeFilePath + .split(path.sep) + .join(path.posix.sep); + + // The / delimiter is intentional since it represents the cloud storage and not the local file system. + const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`; + const destination = `${entityRootDir}/${relativeFilePathPosix}`; // Azure Blob Storage Container file relative path return limiter(async () => { const response = await this.storageClient .getContainerClient(this.containerName) .getBlockBlobClient(destination) - .uploadFile(filePath); + .uploadFile(sourceFilePath); if (response._response.status >= 400) { return { ...response, error: new Error( - `Upload failed for ${filePath} with status code ${response._response.status}`, + `Upload failed for ${sourceFilePath} with status code ${response._response.status}`, ), }; } @@ -173,18 +181,18 @@ export class AzureBlobStoragePublish implements PublisherBase { ); } } catch (e) { - const errorMessage = `Unable to upload file(s) to Azure Blob Storage. ${e.message}`; + const errorMessage = `Unable to upload file(s) to Azure Blob Storage. ${e}`; this.logger.error(errorMessage); throw new Error(errorMessage); } } - private download(containerName: string, path: string): Promise { + private download(containerName: string, blobPath: string): Promise { return new Promise((resolve, reject) => { const fileStreamChunks: Array = []; this.storageClient .getContainerClient(containerName) - .getBlockBlobClient(path) + .getBlockBlobClient(blobPath) .download() .then(res => { const body = res.readableStreamBody; From 837ce612deca7d7d92c3b6e171876740e59f6a82 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 18 Feb 2021 21:59:02 +0100 Subject: [PATCH 156/270] TechDocs/Publishers: Use path.normalize --- packages/techdocs-common/src/stages/publish/awsS3.ts | 1 + packages/techdocs-common/src/stages/publish/googleStorage.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/techdocs-common/src/stages/publish/awsS3.ts b/packages/techdocs-common/src/stages/publish/awsS3.ts index 50f55ffe91..96cce331ec 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.ts @@ -149,6 +149,7 @@ export class AwsS3Publish implements PublisherBase { .split(path.sep) .join(path.posix.sep); + // The / delimiter is intentional since it represents the cloud storage and not the local file system. const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`; const destination = `${entityRootDir}/${relativeFilePathPosix}`; // S3 Bucket file relative path diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.ts b/packages/techdocs-common/src/stages/publish/googleStorage.ts index f45fb7eafb..e07defb237 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.ts @@ -118,6 +118,7 @@ export class GoogleGCSPublish implements PublisherBase { .split(path.sep) .join(path.posix.sep); + // The / delimiter is intentional since it represents the cloud storage and not the local file system. const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`; const destination = `${entityRootDir}/${relativeFilePathPosix}`; // GCS Bucket file relative path From 4aa9b100fbec54daf6d30cc2c820140d855eab41 Mon Sep 17 00:00:00 2001 From: Elliot Greenwood Date: Sun, 14 Feb 2021 01:43:14 +0000 Subject: [PATCH 157/270] Allow for theming inside the OwnershipCard Signed-off-by: Elliot Greenwood --- .../OwnershipCard/OwnershipCard.stories.tsx | 61 ++++++++++++++++--- .../Cards/OwnershipCard/OwnershipCard.tsx | 27 +++++--- 2 files changed, 70 insertions(+), 18 deletions(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx index af42ef6759..b5f0fb9bde 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx @@ -13,10 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Grid } from '@material-ui/core'; +import { Grid, ThemeProvider } from '@material-ui/core'; import React from 'react'; import { MemoryRouter } from 'react-router'; import { GroupEntity } from '@backstage/catalog-model'; +import { + lightTheme, + createTheme, + genPageTheme, + shapes, +} from '@backstage/theme'; import { EntityContext, CatalogApi, @@ -83,14 +89,51 @@ const apiRegistry = ApiRegistry.from([[catalogApiRef, catalogApi]]); export const Default = () => ( - - - - - + + + + + + + - - - + + + + +); + +const monochromeTheme = createTheme({ + ...lightTheme, + defaultPageTheme: 'home', + pageTheme: { + home: genPageTheme(['#444'], shapes.wave2), + documentation: genPageTheme(['#474747'], shapes.wave2), + tool: genPageTheme(['#222'], shapes.wave2), + service: genPageTheme(['#aaa'], shapes.wave2), + website: genPageTheme(['#0e0e0e'], shapes.wave2), + library: genPageTheme(['#9d9d9d'], shapes.wave2), + other: genPageTheme(['#aaa'], shapes.wave2), + app: genPageTheme(['#666'], shapes.wave2), + }, +}); + +export const Themed = () => ( + + + + + + + + + + + + ); diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index 30f3bf0967..0c46b8e670 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -21,13 +21,12 @@ import { isOwnerOf, useEntity, } from '@backstage/plugin-catalog-react'; -import { pageTheme } from '@backstage/theme'; +import { BackstageTheme, genPageTheme } from '@backstage/theme'; import { Box, createStyles, Grid, makeStyles, - Theme, Typography, } from '@material-ui/core'; import Alert from '@material-ui/lab/Alert'; @@ -43,7 +42,17 @@ type EntitiesTypes = | 'api' | 'tool'; -const useStyles = makeStyles((theme: Theme) => +const createPageTheme = ( + theme: BackstageTheme, + shapeKey: string, + colorsKey: string, +) => { + const { colors } = theme.getPageTheme({ themeId: colorsKey }); + const { shape } = theme.getPageTheme({ themeId: shapeKey }); + return genPageTheme(colors, shape).backgroundImage; +}; + +const useStyles = makeStyles((theme: BackstageTheme) => createStyles({ card: { border: `1px solid ${theme.palette.divider}`, @@ -60,22 +69,22 @@ const useStyles = makeStyles((theme: Theme) => fontWeight: theme.typography.fontWeightBold, }, service: { - background: `${pageTheme.home.shape}, linear-gradient(90deg, ${pageTheme.service.colors})`, + background: createPageTheme(theme, 'home', 'service'), }, website: { - background: `${pageTheme.home.shape}, linear-gradient(90deg, ${pageTheme.website.colors})`, + background: createPageTheme(theme, 'home', 'website'), }, library: { - background: `${pageTheme.home.shape}, linear-gradient(90deg, ${pageTheme.library.colors})`, + background: createPageTheme(theme, 'home', 'library'), }, documentation: { - background: `${pageTheme.home.shape}, linear-gradient(90deg, ${pageTheme.documentation.colors})`, + background: createPageTheme(theme, 'home', 'documentation'), }, api: { - background: `${pageTheme.home.shape}, linear-gradient(90deg, #005B4B, #005B4B)`, + background: createPageTheme(theme, 'home', 'home'), }, tool: { - background: `${pageTheme.home.shape}, linear-gradient(90deg, ${pageTheme.tool.colors})`, + background: createPageTheme(theme, 'home', 'tool'), }, }), ); From b25530b34ba8b3e831e61f4200d801a008ca9c97 Mon Sep 17 00:00:00 2001 From: Elliot Greenwood Date: Thu, 18 Feb 2021 20:56:12 +0000 Subject: [PATCH 158/270] Add storybook theming to local theme Signed-off-by: Elliot Greenwood --- .../OwnershipCard/OwnershipCard.stories.tsx | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx index b5f0fb9bde..bce1240434 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx @@ -18,10 +18,10 @@ import React from 'react'; import { MemoryRouter } from 'react-router'; import { GroupEntity } from '@backstage/catalog-model'; import { - lightTheme, createTheme, genPageTheme, shapes, + BackstageTheme, } from '@backstage/theme'; import { EntityContext, @@ -89,36 +89,33 @@ const apiRegistry = ApiRegistry.from([[catalogApiRef, catalogApi]]); export const Default = () => ( - - - - - - - + + + + + - - - + + + ); -const monochromeTheme = createTheme({ - ...lightTheme, - defaultPageTheme: 'home', - pageTheme: { - home: genPageTheme(['#444'], shapes.wave2), - documentation: genPageTheme(['#474747'], shapes.wave2), - tool: genPageTheme(['#222'], shapes.wave2), - service: genPageTheme(['#aaa'], shapes.wave2), - website: genPageTheme(['#0e0e0e'], shapes.wave2), - library: genPageTheme(['#9d9d9d'], shapes.wave2), - other: genPageTheme(['#aaa'], shapes.wave2), - app: genPageTheme(['#666'], shapes.wave2), - }, -}); +const monochromeTheme = (outer: BackstageTheme) => + createTheme({ + ...outer, + defaultPageTheme: 'home', + pageTheme: { + home: genPageTheme(['#444'], shapes.wave2), + documentation: genPageTheme(['#474747'], shapes.wave2), + tool: genPageTheme(['#222'], shapes.wave2), + service: genPageTheme(['#aaa'], shapes.wave2), + website: genPageTheme(['#0e0e0e'], shapes.wave2), + library: genPageTheme(['#9d9d9d'], shapes.wave2), + other: genPageTheme(['#aaa'], shapes.wave2), + app: genPageTheme(['#666'], shapes.wave2), + }, + }); export const Themed = () => ( From e3bc5aad701dac40936ea2dbf487da75329259ff Mon Sep 17 00:00:00 2001 From: Elliot Greenwood Date: Thu, 18 Feb 2021 20:59:59 +0000 Subject: [PATCH 159/270] Add change set Signed-off-by: Elliot Greenwood --- .changeset/cyan-feet-hide.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cyan-feet-hide.md diff --git a/.changeset/cyan-feet-hide.md b/.changeset/cyan-feet-hide.md new file mode 100644 index 0000000000..a1d58d8252 --- /dev/null +++ b/.changeset/cyan-feet-hide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': patch +--- + +Use the `pageTheme` to colour the OwnershipCard boxes with their respective theme colours. From 6266ddd1130cae25af1ad15a52744e6c3e4dd35e Mon Sep 17 00:00:00 2001 From: Stefan Buck Date: Thu, 18 Feb 2021 22:00:09 +0100 Subject: [PATCH 160/270] chore: remove app:diff command from cli --- .changeset/unlucky-ducks-report.md | 5 ++ docs/cli/commands.md | 20 -------- packages/cli/src/commands/app/diff.ts | 74 --------------------------- packages/cli/src/commands/index.ts | 7 --- 4 files changed, 5 insertions(+), 101 deletions(-) create mode 100644 .changeset/unlucky-ducks-report.md delete mode 100644 packages/cli/src/commands/app/diff.ts diff --git a/.changeset/unlucky-ducks-report.md b/.changeset/unlucky-ducks-report.md new file mode 100644 index 0000000000..4f361a6279 --- /dev/null +++ b/.changeset/unlucky-ducks-report.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +The `yarn backstage-cli app:diff` has been broken since a couple of months. The command to perform updates `yarn backstage-cli versions:bump` prints change logs which seems to be a good replacement for this command. diff --git a/docs/cli/commands.md b/docs/cli/commands.md index ea72f7e050..5a68690615 100644 --- a/docs/cli/commands.md +++ b/docs/cli/commands.md @@ -24,7 +24,6 @@ Below is a cleaned up output of `yarn backstage-cli --help`. ```text app:build Build an app for a production release -app:diff Diff an existing app with the creation template app:serve Serve an app for local development backend:build Build a backend plugin @@ -112,25 +111,6 @@ Options: -h, --help display help for command ``` -## app:diff - -Scope: `app` - -Diff an existing app with the template used in `@backstage/create-app`. This -will verify that your app package has not diverged from the template, and can be -useful to run after updating the version of `@backstage/cli` in your app. - -This command is experimental and may be removed in the future. - -```text -Usage: backstage-cli app:diff - -Options: - --check Fail if changes are required - --yes Apply all changes - -h, --help display help for command -``` - ## app:serve Scope: `app` diff --git a/packages/cli/src/commands/app/diff.ts b/packages/cli/src/commands/app/diff.ts deleted file mode 100644 index a667407f23..0000000000 --- a/packages/cli/src/commands/app/diff.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 { Command } from 'commander'; -import { - diffTemplateFiles, - handlers, - handleAllFiles, - inquirerPromptFunc, - makeCheckPromptFunc, - yesPromptFunc, -} from '../../lib/diff'; -import { version } from '../../lib/version'; - -const fileHandlers = [ - { - patterns: ['packages/app/package.json'], - handler: handlers.appPackageJson, - }, - { - patterns: [/tsconfig\.json$/], - handler: handlers.exactMatch, - }, - { - patterns: [ - /README\.md$/, - /\.eslintrc\.js$/, - // make sure files in 1st level of src/ and dev/ exist - /^packages\/app\/(src|dev)\/[^/]+$/, - ], - handler: handlers.exists, - }, - { - patterns: [ - 'lerna.json', - /^src\//, - /^patches\//, - /^packages\/app\/public\//, - /^packages\/app\/cypress/, - // Let plugin:diff take care of the plugins - /^plugins/, - /package\.json$/, - ], - handler: handlers.skip, - }, -]; - -export default async (cmd: Command) => { - let promptFunc = inquirerPromptFunc; - let finalize = () => {}; - - if (cmd.check) { - [promptFunc, finalize] = makeCheckPromptFunc(); - } else if (cmd.yes) { - promptFunc = yesPromptFunc; - } - - const templateFiles = await diffTemplateFiles('default-app', { version }); - await handleAllFiles(fileHandlers, templateFiles, promptFunc); - await finalize(); -}; diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 1d267c6889..e058aaf696 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -76,13 +76,6 @@ export function registerCommands(program: CommanderStatic) { .option(...configOption) .action(lazy(() => import('./backend/dev').then(m => m.default))); - program - .command('app:diff') - .option('--check', 'Fail if changes are required') - .option('--yes', 'Apply all changes') - .description('Diff an existing app with the creation template') - .action(lazy(() => import('./app/diff').then(m => m.default))); - program .command('create-plugin') .option( From b0fc3549e6dc7f25a1c44aad9bd03f04f19ca7f0 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 18 Feb 2021 22:16:48 +0100 Subject: [PATCH 161/270] TechDocs/Azure: Update mock library tests to work with Windows --- .../__mocks__/@azure/storage-blob.ts | 24 +++++++++++++++-- .../__mocks__/@google-cloud/storage.ts | 2 -- .../src/stages/publish/awsS3.test.ts | 2 ++ .../stages/publish/azureBlobStorage.test.ts | 27 ++++++++++++++----- .../src/stages/publish/googleStorage.test.ts | 2 ++ 5 files changed, 47 insertions(+), 10 deletions(-) diff --git a/packages/techdocs-common/__mocks__/@azure/storage-blob.ts b/packages/techdocs-common/__mocks__/@azure/storage-blob.ts index 6633a71b2c..7157f9da60 100644 --- a/packages/techdocs-common/__mocks__/@azure/storage-blob.ts +++ b/packages/techdocs-common/__mocks__/@azure/storage-blob.ts @@ -13,11 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import fs from 'fs'; import type { BlobUploadCommonResponse, ContainerGetPropertiesResponse, } from '@azure/storage-blob'; +import fs from 'fs-extra'; +import os from 'os'; +import path from 'path'; + +const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; +/** + * @param sourceFile contains either / or \ as file separator depending upon OS. + */ +const checkFileExists = async (sourceFile: string): Promise => { + // sourceFile will always have / as file separator irrespective of OS since S3 expects /. + // Normalize sourceFile to OS specific path before checking if file exists. + const relativeFilePath = sourceFile.split(path.posix.sep).join(path.sep); + const filePath = path.join(rootDir, sourceFile); + + try { + await fs.access(filePath, fs.constants.F_OK); + return true; + } catch (err) { + return false; + } +}; export class BlockBlobClient { private readonly blobName; @@ -39,7 +59,7 @@ export class BlockBlobClient { } exists() { - return Promise.resolve(fs.existsSync(this.blobName)); + return checkFileExists(this.blobName); } } diff --git a/packages/techdocs-common/__mocks__/@google-cloud/storage.ts b/packages/techdocs-common/__mocks__/@google-cloud/storage.ts index 503ee397b4..9f19e8bf99 100644 --- a/packages/techdocs-common/__mocks__/@google-cloud/storage.ts +++ b/packages/techdocs-common/__mocks__/@google-cloud/storage.ts @@ -32,8 +32,6 @@ const checkFileExists = async (sourceFile: string): Promise => { await fs.access(filePath, fs.constants.F_OK); return true; } catch (err) { - console.log("File doesn't exist"); - console.log(filePath); return false; } }; diff --git a/packages/techdocs-common/src/stages/publish/awsS3.test.ts b/packages/techdocs-common/src/stages/publish/awsS3.test.ts index 4f586119e5..0c2a33dc03 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.test.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.test.ts @@ -22,6 +22,8 @@ import * as winston from 'winston'; import { AwsS3Publish } from './awsS3'; import { PublisherBase, TechDocsMetadata } from './types'; +// NOTE: /packages/techdocs-common/__mocks__ is being used to mock aws-sdk client library + const createMockEntity = (annotations = {}): Entity => { return { apiVersion: 'version', diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts index e43f9112f5..dfb32b9ce8 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts @@ -17,9 +17,13 @@ import { getVoidLogger } from '@backstage/backend-common'; import type { Entity } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import mockFs from 'mock-fs'; +import os from 'os'; +import path from 'path'; import { AzureBlobStoragePublish } from './azureBlobStorage'; import { PublisherBase } from './types'; +// NOTE: /packages/techdocs-common/__mocks__ is being used to mock Azure client library + const createMockEntity = (annotations = {}) => { return { apiVersion: 'version', @@ -34,13 +38,14 @@ const createMockEntity = (annotations = {}) => { }; }; +const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; const getEntityRootDir = (entity: Entity) => { const { kind, metadata: { namespace, name }, } = entity; - const entityRootDir = `${namespace}/${kind}/${name}`; - return entityRootDir; + + return path.join(rootDir, namespace as string, kind, name); }; function createLogger() { @@ -106,7 +111,14 @@ describe('publishing with valid credentials', () => { }); it('should fail to publish a directory', async () => { - const wrongPathToGeneratedDirectory = 'wrong/path/to/generatedDirectory'; + const wrongPathToGeneratedDirectory = path.join( + rootDir, + 'wrong', + 'path', + 'to', + 'generatedDirectory', + ); + const entity = createMockEntity(); await publisher @@ -115,8 +127,8 @@ describe('publishing with valid credentials', () => { directory: wrongPathToGeneratedDirectory, }) .catch(error => - expect(error.message).toContain( - 'Unable to upload file(s) to Azure Blob Storage. Error: Failed to read template directory: ENOENT, no such file or directory', + expect(error.message).toBe( + `Unable to upload file(s) to Azure Blob Storage. Error: Failed to read template directory: ENOENT, no such file or directory '${wrongPathToGeneratedDirectory}'`, ), ); mockFs.restore(); @@ -227,7 +239,10 @@ describe('error reporting', () => { expect(logger.error).toHaveBeenCalledWith( expect.stringContaining( - `Unable to upload file(s) to Azure Blob Storage. Error: Upload failed for test-namespace/TestKind/test-component-name/index.html with status code 500`, + `Unable to upload file(s) to Azure Blob Storage. Error: Upload failed for ${path.join( + entityRootDir, + 'index.html', + )} with status code 500`, ), ); diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts index 9d4904b223..1069b7b9a4 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts @@ -22,6 +22,8 @@ import path from 'path'; import { GoogleGCSPublish } from './googleStorage'; import { PublisherBase } from './types'; +// NOTE: /packages/techdocs-common/__mocks__ is being used to mock Google Cloud Storage client library + const createMockEntity = (annotations = {}): Entity => { return { apiVersion: 'version', From 0084248dc0c28db2dea0bccca14666cb6a2d4fd6 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 18 Feb 2021 22:19:29 +0100 Subject: [PATCH 162/270] TechDocs/Azure: mockFs inserts unexpected characters in windows path --- .../src/stages/publish/azureBlobStorage.test.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts index dfb32b9ce8..b88b58424d 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts @@ -126,11 +126,17 @@ describe('publishing with valid credentials', () => { entity, directory: wrongPathToGeneratedDirectory, }) - .catch(error => - expect(error.message).toBe( - `Unable to upload file(s) to Azure Blob Storage. Error: Failed to read template directory: ENOENT, no such file or directory '${wrongPathToGeneratedDirectory}'`, - ), - ); + .catch(error => { + // Can not do exact error message match due to mockFs adding unexpected characters in the path when throwing the error + // Issue reported https://github.com/tschaub/mock-fs/issues/118 + expect.stringContaining( + `Unable to upload file(s) to Azure Blob Storage. Error: Failed to read template directory: ENOENT, no such file or directory`, + ); + + expect(error.message).toEqual( + expect.stringContaining(wrongPathToGeneratedDirectory), + ); + }); mockFs.restore(); }); }); From 1e4ddd71da08717c1822ae5ec0ae5da5a70d3dbd Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 18 Feb 2021 22:21:38 +0100 Subject: [PATCH 163/270] TechDocs: Add changeset about publisher fix on windows --- .changeset/techdocs-wild-zoos-do.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/techdocs-wild-zoos-do.md diff --git a/.changeset/techdocs-wild-zoos-do.md b/.changeset/techdocs-wild-zoos-do.md new file mode 100644 index 0000000000..42a2a6f1c4 --- /dev/null +++ b/.changeset/techdocs-wild-zoos-do.md @@ -0,0 +1,5 @@ +--- +'@backstage/techdocs-common': patch +--- + +Fix AWS, GCS and Azure publisher to work on Windows. From b032111f37e8d09f59d0d084002e8dfb89a3754f Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 18 Feb 2021 22:32:43 +0100 Subject: [PATCH 164/270] TechDocs: Clarify differences in sourceFile Path among publishers --- packages/techdocs-common/__mocks__/@azure/storage-blob.ts | 5 +++-- packages/techdocs-common/__mocks__/@google-cloud/storage.ts | 4 ++-- packages/techdocs-common/__mocks__/aws-sdk.ts | 3 ++- packages/techdocs-common/src/stages/publish/awsS3.test.ts | 4 ++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/techdocs-common/__mocks__/@azure/storage-blob.ts b/packages/techdocs-common/__mocks__/@azure/storage-blob.ts index 7157f9da60..4fc1e4c374 100644 --- a/packages/techdocs-common/__mocks__/@azure/storage-blob.ts +++ b/packages/techdocs-common/__mocks__/@azure/storage-blob.ts @@ -23,10 +23,11 @@ import path from 'path'; const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; /** - * @param sourceFile contains either / or \ as file separator depending upon OS. + * @param sourceFile Relative path to entity root dir. Contains either / or \ as file separator + * depending upon the OS. */ const checkFileExists = async (sourceFile: string): Promise => { - // sourceFile will always have / as file separator irrespective of OS since S3 expects /. + // sourceFile will always have / as file separator irrespective of OS since Azure expects /. // Normalize sourceFile to OS specific path before checking if file exists. const relativeFilePath = sourceFile.split(path.posix.sep).join(path.sep); const filePath = path.join(rootDir, sourceFile); diff --git a/packages/techdocs-common/__mocks__/@google-cloud/storage.ts b/packages/techdocs-common/__mocks__/@google-cloud/storage.ts index 9f19e8bf99..648aceadb4 100644 --- a/packages/techdocs-common/__mocks__/@google-cloud/storage.ts +++ b/packages/techdocs-common/__mocks__/@google-cloud/storage.ts @@ -21,10 +21,10 @@ type storageOptions = { }; /** - * @param sourceFile contains either / or \ as file separator depending upon OS. + * @param sourceFile Absolute path. Contains either / or \ as file separator depending upon the OS. */ const checkFileExists = async (sourceFile: string): Promise => { - // sourceFile will always have / as file separator irrespective of OS since S3 expects /. + // sourceFile will always have / as file separator irrespective of OS since GCS expects /. // Normalize sourceFile to OS specific path before checking if file exists. const filePath = sourceFile.split(path.posix.sep).join(path.sep); diff --git a/packages/techdocs-common/__mocks__/aws-sdk.ts b/packages/techdocs-common/__mocks__/aws-sdk.ts index 6957ea7430..0a50b0e792 100644 --- a/packages/techdocs-common/__mocks__/aws-sdk.ts +++ b/packages/techdocs-common/__mocks__/aws-sdk.ts @@ -22,7 +22,8 @@ import path from 'path'; const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; /** - * @param Key contains either / or \ as file separator depending upon OS. + * @param Key Relative path to entity root dir. Contains either / or \ as file separator + * depending upon the OS. */ const checkFileExists = async (Key: string): Promise => { // Key will always have / as file separator irrespective of OS since S3 expects /. diff --git a/packages/techdocs-common/src/stages/publish/awsS3.test.ts b/packages/techdocs-common/src/stages/publish/awsS3.test.ts index 0c2a33dc03..0696ec5b1a 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.test.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.test.ts @@ -16,8 +16,8 @@ import type { Entity, EntityName } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import mockFs from 'mock-fs'; -import * as os from 'os'; -import * as path from 'path'; +import os from 'os'; +import path from 'path'; import * as winston from 'winston'; import { AwsS3Publish } from './awsS3'; import { PublisherBase, TechDocsMetadata } from './types'; From 15203089585f989f25b760638f818527918ce2fa Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 18 Feb 2021 23:19:33 +0100 Subject: [PATCH 165/270] TechDocs/GCS: Improve test coverage by testing other two methods --- .../__mocks__/@google-cloud/storage.ts | 42 ++++ packages/techdocs-common/__mocks__/aws-sdk.ts | 2 +- .../src/stages/publish/googleStorage.test.ts | 222 +++++++++++++----- .../src/stages/publish/googleStorage.ts | 6 +- 4 files changed, 205 insertions(+), 67 deletions(-) diff --git a/packages/techdocs-common/__mocks__/@google-cloud/storage.ts b/packages/techdocs-common/__mocks__/@google-cloud/storage.ts index 648aceadb4..264852d31c 100644 --- a/packages/techdocs-common/__mocks__/@google-cloud/storage.ts +++ b/packages/techdocs-common/__mocks__/@google-cloud/storage.ts @@ -13,13 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { EventEmitter } from 'events'; import fs from 'fs-extra'; +import os from 'os'; import path from 'path'; type storageOptions = { keyFilename?: string; }; +const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; /** * @param sourceFile Absolute path. Contains either / or \ as file separator depending upon the OS. */ @@ -36,6 +39,41 @@ const checkFileExists = async (sourceFile: string): Promise => { } }; +class GCSFile { + private readonly localFilePath: string; + + constructor(private readonly destinationFilePath: string) { + this.destinationFilePath = destinationFilePath; + this.localFilePath = path.join(rootDir, this.destinationFilePath); + } + + exists() { + return new Promise(async (resolve, reject) => { + if (await checkFileExists(this.localFilePath)) { + resolve([true]); + } else { + reject(); + } + }); + } + + createReadStream() { + const emitter = new EventEmitter(); + process.nextTick(() => { + if (fs.existsSync(this.localFilePath)) { + emitter.emit('data', Buffer.from(fs.readFileSync(this.localFilePath))); + emitter.emit('end'); + } else { + emitter.emit( + 'error', + new Error(`The file ${this.localFilePath} does not exist !`), + ); + } + }); + return emitter; + } +} + class Bucket { private readonly bucketName; @@ -58,6 +96,10 @@ class Bucket { } }); } + + file(destinationFilePath: string) { + return new GCSFile(destinationFilePath); + } } export class Storage { diff --git a/packages/techdocs-common/__mocks__/aws-sdk.ts b/packages/techdocs-common/__mocks__/aws-sdk.ts index 0a50b0e792..4717feda9b 100644 --- a/packages/techdocs-common/__mocks__/aws-sdk.ts +++ b/packages/techdocs-common/__mocks__/aws-sdk.ts @@ -65,13 +65,13 @@ export class S3 { process.nextTick(() => { if (fs.existsSync(filePath)) { emitter.emit('data', Buffer.from(fs.readFileSync(filePath))); + emitter.emit('end'); } else { emitter.emit( 'error', new Error(`The file ${filePath} does not exist !`), ); } - emitter.emit('end'); }); return emitter; }, diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts index 1069b7b9a4..a8df9ccbc1 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts @@ -14,13 +14,13 @@ * limitations under the License. */ import { getVoidLogger } from '@backstage/backend-common'; -import { Entity } from '@backstage/catalog-model'; +import { Entity, EntityName } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import mockFs from 'mock-fs'; import os from 'os'; import path from 'path'; import { GoogleGCSPublish } from './googleStorage'; -import { PublisherBase } from './types'; +import { PublisherBase, TechDocsMetadata } from './types'; // NOTE: /packages/techdocs-common/__mocks__ is being used to mock Google Cloud Storage client library @@ -38,6 +38,12 @@ const createMockEntity = (annotations = {}): Entity => { }; }; +const createMockEntityName = (): EntityName => ({ + kind: 'TestKind', + name: 'test-component-name', + namespace: 'test-namespace', +}); + const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; const getEntityRootDir = (entity: Entity) => { @@ -73,74 +79,164 @@ beforeEach(async () => { }); describe('GoogleGCSPublish', () => { - beforeEach(() => { - const entity = createMockEntity(); - const entityRootDir = getEntityRootDir(entity); + describe('publish', () => { + beforeEach(() => { + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); - mockFs({ - [entityRootDir]: { - 'index.html': '', - '404.html': '', - assets: { - 'main.css': '', + mockFs({ + [entityRootDir]: { + 'index.html': '', + '404.html': '', + assets: { + 'main.css': '', + }, }, - }, + }); + }); + + afterEach(() => { + mockFs.restore(); + }); + + it('should publish a directory', async () => { + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); + + expect( + await publisher.publish({ + entity, + directory: entityRootDir, + }), + ).toBeUndefined(); + mockFs.restore(); + }); + + it('should fail to publish a directory', async () => { + const wrongPathToGeneratedDirectory = path.join( + rootDir, + 'wrong', + 'path', + 'to', + 'generatedDirectory', + ); + + const entity = createMockEntity(); + + await expect( + publisher.publish({ + entity, + directory: wrongPathToGeneratedDirectory, + }), + ).rejects.toThrowError(); + + await publisher + .publish({ + entity, + directory: wrongPathToGeneratedDirectory, + }) + .catch(error => { + expect(error.message).toEqual( + // Can not do exact error message match due to mockFs adding unexpected characters in the path when throwing the error + // Issue reported https://github.com/tschaub/mock-fs/issues/118 + expect.stringContaining( + `Unable to upload file(s) to Google Cloud Storage. Error: Failed to read template directory: ENOENT, no such file or directory`, + ), + ); + expect(error.message).toEqual( + expect.stringContaining(wrongPathToGeneratedDirectory), + ); + }); + + mockFs.restore(); }); }); - afterEach(() => { - mockFs.restore(); - }); + describe('hasDocsBeenGenerated', () => { + it('should return true if docs has been generated', async () => { + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); - it('should publish a directory', async () => { - const entity = createMockEntity(); - const entityRootDir = getEntityRootDir(entity); - - expect( - await publisher.publish({ - entity, - directory: entityRootDir, - }), - ).toBeUndefined(); - mockFs.restore(); - }); - - it('should fail to publish a directory', async () => { - const wrongPathToGeneratedDirectory = path.join( - rootDir, - 'wrong', - 'path', - 'to', - 'generatedDirectory', - ); - - const entity = createMockEntity(); - - await expect( - publisher.publish({ - entity, - directory: wrongPathToGeneratedDirectory, - }), - ).rejects.toThrowError(); - - await publisher - .publish({ - entity, - directory: wrongPathToGeneratedDirectory, - }) - .catch(error => { - expect(error.message).toEqual( - // Can not do exact error message match due to mockFs adding unexpected characters in the path when throwing the error - // Issue reported https://github.com/tschaub/mock-fs/issues/118 - expect.stringContaining( - `Unable to upload file(s) to Google Cloud Storage. Error: Failed to read template directory: ENOENT, no such file or directory`, - ), - ); - expect(error.message).toEqual( - expect.stringContaining(wrongPathToGeneratedDirectory), - ); + mockFs({ + [entityRootDir]: { + 'index.html': 'file-content', + }, }); - mockFs.restore(); + expect(await publisher.hasDocsBeenGenerated(entity)).toBe(true); + mockFs.restore(); + }); + + it('should return false if docs has not been generated', async () => { + const entity = createMockEntity(); + + expect(await publisher.hasDocsBeenGenerated(entity)).toBe(false); + }); + }); + + describe('fetchTechDocsMetadata', () => { + beforeEach(() => { + mockFs.restore(); + }); + + it('should return tech docs metadata', async () => { + const entityNameMock = createMockEntityName(); + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); + + mockFs({ + [entityRootDir]: { + 'techdocs_metadata.json': + '{"site_name": "backstage", "site_description": "site_content"}', + }, + }); + + const expectedMetadata: TechDocsMetadata = { + site_name: 'backstage', + site_description: 'site_content', + }; + expect( + await publisher.fetchTechDocsMetadata(entityNameMock), + ).toStrictEqual(expectedMetadata); + }); + + it('should return tech docs metadata when json encoded with single quotes', async () => { + const entityNameMock = createMockEntityName(); + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); + + mockFs({ + [entityRootDir]: { + 'techdocs_metadata.json': + "{'site_name': 'backstage', 'site_description': 'site_content'}", + }, + }); + + const expectedMetadata: TechDocsMetadata = { + site_name: 'backstage', + site_description: 'site_content', + }; + expect( + await publisher.fetchTechDocsMetadata(entityNameMock), + ).toStrictEqual(expectedMetadata); + mockFs.restore(); + }); + + it('should return an error if the techdocs_metadata.json file is not present', async () => { + const entityNameMock = createMockEntityName(); + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); + + await publisher + .fetchTechDocsMetadata(entityNameMock) + .catch(errorMessage => + expect(errorMessage).toEqual( + `The file ${path.join( + entityRootDir, + 'techdocs_metadata.json', + )} does not exist !`, + ), + ); + }); }); }); diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.ts b/packages/techdocs-common/src/stages/publish/googleStorage.ts index e07defb237..12ecf45b2d 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.ts @@ -160,9 +160,9 @@ export class GoogleGCSPublish implements PublisherBase { fileStreamChunks.push(chunk); }) .on('end', () => { - const techdocsMetadataJson = Buffer.concat( - fileStreamChunks, - ).toString(); + const techdocsMetadataJson = Buffer.concat(fileStreamChunks).toString( + 'utf-8', + ); resolve(JSON5.parse(techdocsMetadataJson)); }); }); From a5ff1a1e0fd318a9a32dc6f6828ad3c14006ecac Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Thu, 18 Feb 2021 15:34:54 -0800 Subject: [PATCH 166/270] Address review comments --- plugins/catalog-backend/config.d.ts | 4 ++-- .../AwsOrganizationCloudAccountProcessor.ts | 15 +++++++-------- .../processors/awsOrganization/config.ts | 18 ++++++------------ plugins/techdocs/config.d.ts | 3 +-- 4 files changed, 16 insertions(+), 24 deletions(-) diff --git a/plugins/catalog-backend/config.d.ts b/plugins/catalog-backend/config.d.ts index b85846af2a..32ff44457c 100644 --- a/plugins/catalog-backend/config.d.ts +++ b/plugins/catalog-backend/config.d.ts @@ -338,13 +338,13 @@ export interface Config { * AwsOrganizationCloudAccountProcessor configuration */ awsOrganization?: { - providers: Array<{ + provider: { /** * The role to be assumed by this processor * */ roleArn?: string; - }>; + }; }; /** diff --git a/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.ts index 83fd89801f..7964541840 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.ts @@ -19,7 +19,7 @@ import { Account, ListAccountsResponse } from 'aws-sdk/clients/organizations'; import * as results from './results'; import { CatalogProcessor, CatalogProcessorEmit } from './types'; -import { Config } from '../../../../../packages/config/src'; +import { Config } from '@backstage/config'; import { Logger } from 'winston'; import { AwsOrganizationProviderConfig, @@ -41,33 +41,32 @@ const ORGANIZATION_ANNOTATION: string = 'amazonaws.com/organization-id'; export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor { logger: Logger; organizations: Organizations; - providers: AwsOrganizationProviderConfig[]; + provider: AwsOrganizationProviderConfig; static fromConfig(config: Config, options: { logger: Logger }) { const c = config.getOptionalConfig('catalog.processors.awsOrganization'); return new AwsOrganizationCloudAccountProcessor({ ...options, - providers: c ? readAwsOrganizationConfig(c) : [], + provider: c ? readAwsOrganizationConfig(c) : {}, }); } constructor(options: { - providers: AwsOrganizationProviderConfig[]; + provider: AwsOrganizationProviderConfig; logger: Logger; }) { - this.providers = options.providers; + this.provider = options.provider; this.logger = options.logger; let credentials = undefined; if ( - this.providers.length > 0 && - this.providers[0].roleArn !== undefined && + this.provider.roleArn !== undefined && AWS.config.credentials instanceof Credentials ) { credentials = new AWS.ChainableTemporaryCredentials({ masterCredentials: AWS.config.credentials as Credentials, params: { RoleSessionName: 'backstage-aws-organization-processor', - RoleArn: this.providers[0].roleArn, + RoleArn: this.provider.roleArn, }, }); } diff --git a/plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.ts b/plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.ts index 120079d679..90206f504c 100644 --- a/plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.ts +++ b/plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.ts @@ -28,17 +28,11 @@ export type AwsOrganizationProviderConfig = { export function readAwsOrganizationConfig( config: Config, -): AwsOrganizationProviderConfig[] { - const providers: AwsOrganizationProviderConfig[] = []; - const providerConfigs = config.getOptionalConfigArray('providers') ?? []; +): AwsOrganizationProviderConfig { + const providerConfig = config.getOptionalConfig('provider'); - for (const providerConfig of providerConfigs) { - const roleArn = providerConfig.getOptionalString('roleArn'); - - providers.push({ - roleArn, - }); - } - - return providers; + const roleArn = providerConfig?.getOptionalString('roleArn'); + return { + roleArn, + }; } diff --git a/plugins/techdocs/config.d.ts b/plugins/techdocs/config.d.ts index da30578c99..5285cc9071 100644 --- a/plugins/techdocs/config.d.ts +++ b/plugins/techdocs/config.d.ts @@ -88,8 +88,7 @@ export interface Config { secretAccessKey: string; /** * ARN of role to be assumed - * attr: 'roleArn' - accepts a string value - * @visibility secret + * @visibility backend */ roleArn?: string; }; From 6262a5661448af85b44ebb14400e67e3030c3651 Mon Sep 17 00:00:00 2001 From: Jeff Durand Date: Fri, 19 Feb 2021 00:04:34 +0000 Subject: [PATCH 167/270] fixing based on review --- .../RadarDescription.test.tsx | 2 +- .../RadarDescription/RadarDescription.tsx | 8 ++-- .../src/components/RadarDescription/index.ts | 2 +- .../src/components/RadarEntry/RadarEntry.tsx | 40 +++++++++---------- .../components/RadarLegend/RadarLegend.tsx | 20 +++++----- 5 files changed, 38 insertions(+), 34 deletions(-) diff --git a/plugins/tech-radar/src/components/RadarDescription/RadarDescription.test.tsx b/plugins/tech-radar/src/components/RadarDescription/RadarDescription.test.tsx index 54b8ef0892..b19794042e 100644 --- a/plugins/tech-radar/src/components/RadarDescription/RadarDescription.test.tsx +++ b/plugins/tech-radar/src/components/RadarDescription/RadarDescription.test.tsx @@ -19,7 +19,7 @@ import { render, screen } from '@testing-library/react'; import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; -import RadarDescription, { Props } from './RadarDescription'; +import { Props, RadarDescription } from './RadarDescription'; const minProps: Props = { open: true, diff --git a/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx b/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx index 24e31dd34f..20c489f630 100644 --- a/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx +++ b/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx @@ -29,7 +29,6 @@ export type Props = { }; const RadarDescription = (props: Props): JSX.Element => { - // const classes = useStyles(props); const { open, onClose, title, description, url } = props; const handleClick = () => { @@ -41,7 +40,9 @@ const RadarDescription = (props: Props): JSX.Element => { return ( - {title} + + {title} + {description} {url && ( @@ -49,6 +50,7 @@ const RadarDescription = (props: Props): JSX.Element => { onClick={handleClick} color="primary" startIcon={} + href={url} > LEARN MORE @@ -58,4 +60,4 @@ const RadarDescription = (props: Props): JSX.Element => { ); }; -export default RadarDescription; +export { RadarDescription }; diff --git a/plugins/tech-radar/src/components/RadarDescription/index.ts b/plugins/tech-radar/src/components/RadarDescription/index.ts index 908e97701e..748898cf0b 100644 --- a/plugins/tech-radar/src/components/RadarDescription/index.ts +++ b/plugins/tech-radar/src/components/RadarDescription/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { default } from './RadarDescription'; +export { RadarDescription, Props } from './RadarDescription'; diff --git a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx index f146eb5fa8..fb47a75c71 100644 --- a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx +++ b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { makeStyles, Theme } from '@material-ui/core'; import { WithLink } from '../../utils/components'; -import RadarDescription from '../RadarDescription'; +import { RadarDescription } from '../RadarDescription'; export type Props = { x: number; @@ -101,26 +101,26 @@ const RadarEntry = (props: Props): JSX.Element => { data-testid="radar-entry" > {' '} + {open && ( + + )} {description ? ( - <> - - {blip} - - - + + {blip} + ) : ( {blip} diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx index 998a3370c1..d4ef38d5ee 100644 --- a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { makeStyles, Theme } from '@material-ui/core'; import type { Quadrant, Ring, Entry } from '../../utils/types'; import { WithLink } from '../../utils/components'; -import RadarDescription from '../RadarDescription'; +import { RadarDescription } from '../RadarDescription'; type Segments = { [k: number]: { [k: number]: Entry[] }; @@ -131,7 +131,7 @@ const RadarLegend = (props: Props): JSX.Element => { setOpen(!open); }; - if (description && url) { + if (description) { return ( <> { > {title} - + {open && ( + + )} ); } From 9abacc8ebc1425bce1625003462575f0c61c3a98 Mon Sep 17 00:00:00 2001 From: Jeff Durand Date: Fri, 19 Feb 2021 00:10:33 +0000 Subject: [PATCH 168/270] need to export type as type --- plugins/tech-radar/src/components/RadarDescription/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/tech-radar/src/components/RadarDescription/index.ts b/plugins/tech-radar/src/components/RadarDescription/index.ts index 748898cf0b..027407718e 100644 --- a/plugins/tech-radar/src/components/RadarDescription/index.ts +++ b/plugins/tech-radar/src/components/RadarDescription/index.ts @@ -14,4 +14,5 @@ * limitations under the License. */ -export { RadarDescription, Props } from './RadarDescription'; +export { RadarDescription } from './RadarDescription'; +export type { Props } from './RadarDescription'; From fd3214557cb04b0c64a6cb0071b5035781068923 Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Thu, 18 Feb 2021 16:13:33 -0800 Subject: [PATCH 169/270] Fix ts error --- .../processors/AwsOrganizationCloudAccountProcessor.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.test.ts index 596e62748f..292564dc4f 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.test.ts @@ -20,7 +20,7 @@ import * as winston from 'winston'; describe('AwsOrganizationCloudAccountProcessor', () => { describe('readLocation', () => { const processor = new AwsOrganizationCloudAccountProcessor({ - providers: [], + provider: {}, logger: winston.createLogger(), }); const location = { type: 'aws-cloud-accounts', target: '' }; From e0f141d36e44287c2e49c99f104e28af2e058688 Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Thu, 18 Feb 2021 19:37:58 -0800 Subject: [PATCH 170/270] fix test after changing config schema --- .../processors/awsOrganization/config.test.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.test.ts b/plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.test.ts index 571d19c829..09d95b479d 100644 --- a/plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.test.ts @@ -20,18 +20,14 @@ import { readAwsOrganizationConfig } from './config'; describe('readAwsOrganizationConfig', () => { it('applies all of the defaults', () => { const config = { - providers: [ - { - roleArn: 'aws::arn::foo', - }, - ], - }; - const actual = readAwsOrganizationConfig(new ConfigReader(config)); - const expected = [ - { + provider: { roleArn: 'aws::arn::foo', }, - ]; + }; + const actual = readAwsOrganizationConfig(new ConfigReader(config)); + const expected = { + roleArn: 'aws::arn::foo', + }; expect(actual).toEqual(expected); }); }); From d66fa57802058a3e6cafb3c74cf661798eaabc7b Mon Sep 17 00:00:00 2001 From: Tadashi Nemoto Date: Fri, 19 Feb 2021 15:16:22 +0900 Subject: [PATCH 171/270] =?UTF-8?q?Fix=20Japanese=20Good=20Morning(?= =?UTF-8?q?=E3=81=8A=E6=97=A9=E3=81=86=20->=20=E3=81=8A=E3=81=AF=E3=82=88?= =?UTF-8?q?=E3=81=86)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CatalogPage/utils/locales/goodMorning.locales.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog/src/components/CatalogPage/utils/locales/goodMorning.locales.json b/plugins/catalog/src/components/CatalogPage/utils/locales/goodMorning.locales.json index a67c292d83..febbc32c49 100644 --- a/plugins/catalog/src/components/CatalogPage/utils/locales/goodMorning.locales.json +++ b/plugins/catalog/src/components/CatalogPage/utils/locales/goodMorning.locales.json @@ -27,7 +27,7 @@ "Indonesian": "Selamat pagi", "Irish": "Dia dhuit", "Italian": "Buongiorno", - "Japanese": "お早う", + "Japanese": "おはよう", "Korean": "안녕하세요", "Kazakh": "Kayırlı tan", "Kurdish": "Beyanî baş", From c1c3184b387a1c0e4351ed7f4dabc3f914a3ff7c Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 19 Feb 2021 09:35:14 +0100 Subject: [PATCH 172/270] create-app: add catalog-client dependency --- packages/create-app/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 1bc3e845ce..b427b1e2ed 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -45,6 +45,7 @@ }, "peerDependencies": { "@backstage/backend-common": "^0.5.4", + "@backstage/catalog-client": "^0.3.6", "@backstage/catalog-model": "^0.7.1", "@backstage/cli": "^0.6.1", "@backstage/config": "^0.1.2", From 79f746170951d54a2816a8935c30e28c5d59c24b Mon Sep 17 00:00:00 2001 From: Olle Lundberg Date: Fri, 19 Feb 2021 09:05:26 +0100 Subject: [PATCH 173/270] Recommend a safer find command Piping find to xargs is dangerous as xargs will interpret any characters defined in $IFS (Input Field Separator), usually , as separators in its input. This might lead to unintented operations on files if any of the input to xargs containts any of the characters defined in $IFS, most often this happens if a file contains a space in its name. The safest way to execute a find | xargs is to force find to separate its output with null characters and tell xargs to read null characters as the delimiter AND also tell xargs to put the argument to the command its supposed to run in quotation marks: `find ... -print0 | xargs -I {} -0 rm -rf "{}"` When running with GNU find you most likely also want to add --no-run-if-empty or -r for short: `find ... -print0 | xargs -I {} -0 --no-run-if-empty rm -rf "{}"` This stops the invocation of xargs if there is no input on stdin, this is however not portable and will break on BSD/macOS, the portability is not a concern in this case though as the find | xargs happens in docker. As you can see this gets unwieldly fast and despite using every precaution, it's still not safe. When xargs is run with -0 to treat null characters as the delimiter for its input and a file has a null character in its name, xargs will treat the null character in the file name as a delimiter and xargs will exhibit the same behaviour as it did with spaces in file names. Ahhh, isn't Unix wonderful? Loose APIs defined as untyped strings... There is a salvation though! Most find xargs pipes are unnecessary and can be replaced with built in functionality in find, the -exec flag. Now, -exec comes in 2 flavours, one that is terminated with \; (the most commonly used) and one terminated with \+ (the one most people actually want to use). \; spawns a new invocation per found entry, thus creating some process creation overhead. \+ instead concatenates the found entries as arguments to the program we want to run, resulting in less overhead and usually a faster execution. Since find is smart enough to be aware of what constitutes an entry (i.e it doesn't treat the entries as just a bunch of random strings to read from stdin) it makes the whole invocation of the program, rm in this case, safe even if it contains characters defines in $IFS or null characters. And with this overly elaborate commit message I bring you this 1 line change. --- docs/getting-started/deployment-docker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/deployment-docker.md b/docs/getting-started/deployment-docker.md index 9486e0aeb0..69158f9104 100644 --- a/docs/getting-started/deployment-docker.md +++ b/docs/getting-started/deployment-docker.md @@ -141,7 +141,7 @@ COPY package.json yarn.lock ./ COPY packages packages COPY plugins plugins -RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -print | xargs rm -rf +RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -exec rm -rf {} \+ # Stage 2 - Install dependencies and build packages FROM node:14-buster-slim AS build From 49f9b7346dcb777edd0904954c069e74fdb3d3b3 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 18 Feb 2021 11:05:29 +0100 Subject: [PATCH 174/270] Rename `type` of `ItemCard` to `subtitle` and allow to pass react nodes Signed-off-by: Oliver Sand --- .changeset/popular-nails-agree.md | 6 ++++++ .../src/layout/ItemCard/ItemCard.stories.tsx | 4 ++-- .../core/src/layout/ItemCard/ItemCard.test.tsx | 18 ++++++++++++++---- packages/core/src/layout/ItemCard/ItemCard.tsx | 9 +++++++-- 4 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 .changeset/popular-nails-agree.md diff --git a/.changeset/popular-nails-agree.md b/.changeset/popular-nails-agree.md new file mode 100644 index 0000000000..361ff4745d --- /dev/null +++ b/.changeset/popular-nails-agree.md @@ -0,0 +1,6 @@ +--- +'@backstage/core': patch +--- + +Deprecate `type` of `ItemCard` and introduce new `subtitle` which allows passing +react nodes. diff --git a/packages/core/src/layout/ItemCard/ItemCard.stories.tsx b/packages/core/src/layout/ItemCard/ItemCard.stories.tsx index b53fa9eb7a..eea78562e8 100644 --- a/packages/core/src/layout/ItemCard/ItemCard.stories.tsx +++ b/packages/core/src/layout/ItemCard/ItemCard.stories.tsx @@ -30,7 +30,7 @@ export const Default = () => ( title="Item Card" description="This is the description of an Item Card" label="Button" - type="Pretitle" + subtitle="Pretitle" onClick={() => {}} /> @@ -39,7 +39,7 @@ export const Default = () => ( title="Item Card" description="This is the description of an Item Card" label="Button" - type="Pretitle" + subtitle="Pretitle" onClick={() => {}} /> diff --git a/packages/core/src/layout/ItemCard/ItemCard.test.tsx b/packages/core/src/layout/ItemCard/ItemCard.test.tsx index 6afeb16c5b..30cf95fd85 100644 --- a/packages/core/src/layout/ItemCard/ItemCard.test.tsx +++ b/packages/core/src/layout/ItemCard/ItemCard.test.tsx @@ -14,25 +14,35 @@ * limitations under the License. */ -import React from 'react'; import { renderInTestApp } from '@backstage/test-utils'; +import React from 'react'; import { ItemCard } from './ItemCard'; const minProps = { description: 'This is the description of an Item Card', label: 'Button', title: 'Item Card', - type: 'Pretitle', }; describe('', () => { it('renders default without exploding', async () => { - const { description, label, title, type } = minProps; + const { description, label, title } = minProps; const { getByText } = await renderInTestApp(); expect(getByText(description)).toBeInTheDocument(); expect(getByText(title)).toBeInTheDocument(); expect(getByText(label)).toBeInTheDocument(); - expect(getByText(type)).toBeInTheDocument(); + }); + + it('renders with subtitle without exploding', async () => { + const { description, label, title } = minProps; + const subtitle = 'Pretitle'; + const { getByText } = await renderInTestApp( + , + ); + expect(getByText(description)).toBeInTheDocument(); + expect(getByText(title)).toBeInTheDocument(); + expect(getByText(label)).toBeInTheDocument(); + expect(getByText(subtitle)).toBeInTheDocument(); }); it('renders with tags without exploding', async () => { diff --git a/packages/core/src/layout/ItemCard/ItemCard.tsx b/packages/core/src/layout/ItemCard/ItemCard.tsx index 4242cd6381..9e16def5d3 100644 --- a/packages/core/src/layout/ItemCard/ItemCard.tsx +++ b/packages/core/src/layout/ItemCard/ItemCard.tsx @@ -15,7 +15,7 @@ */ import { Button, Card, Chip, makeStyles, Typography } from '@material-ui/core'; import clsx from 'clsx'; -import React from 'react'; +import React, { ReactNode } from 'react'; import { Link } from '../../components'; const useStyles = makeStyles(theme => ({ @@ -45,7 +45,9 @@ type ItemCardProps = { description?: string; tags?: string[]; title: string; + /** @deprecated Use subtitle instead */ type?: string; + subtitle?: ReactNode; label: string; onClick?: () => void; href?: string; @@ -56,6 +58,7 @@ export const ItemCard = ({ tags, title, type, + subtitle, label, onClick, href, @@ -65,7 +68,9 @@ export const ItemCard = ({ return (
- {type ?? {type}} + {(subtitle || type) && ( + {subtitle ?? type} + )} {title}
From 9615e68fb6382fbb08847a89a84ad9c46d4a48fb Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 18 Feb 2021 11:07:11 +0100 Subject: [PATCH 175/270] Forward link styling of `EntityRefLink` and `EnriryRefLinks` into the underling `Link` Signed-off-by: Oliver Sand --- .changeset/kind-eels-run.md | 7 +++++ packages/core/src/components/Link/Link.tsx | 6 ++-- packages/core/src/components/Link/index.ts | 1 + .../EntityRefLink/EntityRefLink.tsx | 14 +++++----- .../EntityRefLink/EntityRefLinks.tsx | 28 +++++++++---------- 5 files changed, 32 insertions(+), 24 deletions(-) create mode 100644 .changeset/kind-eels-run.md diff --git a/.changeset/kind-eels-run.md b/.changeset/kind-eels-run.md new file mode 100644 index 0000000000..0d890b0478 --- /dev/null +++ b/.changeset/kind-eels-run.md @@ -0,0 +1,7 @@ +--- +'@backstage/core': patch +'@backstage/plugin-catalog-react': patch +--- + +Forward link styling of `EntityRefLink` and `EnriryRefLinks` into the underling +`Link`. diff --git a/packages/core/src/components/Link/Link.tsx b/packages/core/src/components/Link/Link.tsx index b551b0511d..cd74b681db 100644 --- a/packages/core/src/components/Link/Link.tsx +++ b/packages/core/src/components/Link/Link.tsx @@ -14,17 +14,17 @@ * limitations under the License. */ -import React, { ElementType } from 'react'; import { Link as MaterialLink, LinkProps as MaterialLinkProps, } from '@material-ui/core'; +import React, { ElementType } from 'react'; import { Link as RouterLink, LinkProps as RouterLinkProps, } from 'react-router-dom'; -type Props = MaterialLinkProps & +export type LinkProps = MaterialLinkProps & RouterLinkProps & { component?: ElementType; }; @@ -33,7 +33,7 @@ type Props = MaterialLinkProps & * Thin wrapper on top of material-ui's Link component * Makes the Link to utilise react-router */ -export const Link = React.forwardRef((props, ref) => { +export const Link = React.forwardRef((props, ref) => { const to = String(props.to); return /^https?:\/\//.test(to) ? ( // External links diff --git a/packages/core/src/components/Link/index.ts b/packages/core/src/components/Link/index.ts index 18e990181c..9be779feb7 100644 --- a/packages/core/src/components/Link/index.ts +++ b/packages/core/src/components/Link/index.ts @@ -15,3 +15,4 @@ */ export { Link } from './Link'; +export type { LinkProps } from './Link'; diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx index 50a80af12a..73e3824d98 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx @@ -15,22 +15,22 @@ */ import { Entity, - ENTITY_DEFAULT_NAMESPACE, EntityName, + ENTITY_DEFAULT_NAMESPACE, } from '@backstage/catalog-model'; -import { Link } from '@backstage/core'; -import React from 'react'; +import { Link, LinkProps } from '@backstage/core'; +import React, { forwardRef } from 'react'; import { generatePath } from 'react-router'; import { entityRoute } from '../../routes'; import { formatEntityRefTitle } from './format'; -type EntityRefLinkProps = { +export type EntityRefLinkProps = { entityRef: Entity | EntityName; defaultKind?: string; children?: React.ReactNode; -}; +} & Omit; -export const EntityRefLink = React.forwardRef( +export const EntityRefLink = forwardRef( (props, ref) => { const { entityRef, defaultKind, children, ...linkProps } = props; @@ -59,9 +59,9 @@ export const EntityRefLink = React.forwardRef( // TODO: Use useRouteRef here to generate the path return ( {children} {!children && formatEntityRefTitle(entityRef, { defaultKind })} diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx index fb99fad176..52ed87c80b 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx @@ -14,26 +14,26 @@ * limitations under the License. */ import { Entity, EntityName } from '@backstage/catalog-model'; +import { LinkProps } from '@backstage/core'; import React from 'react'; import { EntityRefLink } from './EntityRefLink'; -type EntityRefLinksProps = { +export type EntityRefLinksProps = { entityRefs: (Entity | EntityName)[]; defaultKind?: string; -}; +} & Omit; export const EntityRefLinks = ({ entityRefs, defaultKind, -}: EntityRefLinksProps) => { - return ( - <> - {entityRefs.map((r, i) => ( - - {i > 0 && ', '} - - - ))} - - ); -}; + ...linkProps +}: EntityRefLinksProps) => ( + <> + {entityRefs.map((r, i) => ( + + {i > 0 && ', '} + + + ))} + +); From 347137ccf217ab2cc7e7f84929e4cbb76db28944 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 18 Feb 2021 11:07:54 +0100 Subject: [PATCH 176/270] Display the owner of a domain on the domain card Signed-off-by: Oliver Sand --- .changeset/eight-chefs-reply.md | 5 +++ .../src/components/DomainCard/DomainCard.tsx | 41 ++++++++++++------- 2 files changed, 32 insertions(+), 14 deletions(-) create mode 100644 .changeset/eight-chefs-reply.md diff --git a/.changeset/eight-chefs-reply.md b/.changeset/eight-chefs-reply.md new file mode 100644 index 0000000000..9e133e2722 --- /dev/null +++ b/.changeset/eight-chefs-reply.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-explore': patch +--- + +Display the owner of a domain on the domain card. diff --git a/plugins/explore/src/components/DomainCard/DomainCard.tsx b/plugins/explore/src/components/DomainCard/DomainCard.tsx index 2c0126b94a..e5efc543b2 100644 --- a/plugins/explore/src/components/DomainCard/DomainCard.tsx +++ b/plugins/explore/src/components/DomainCard/DomainCard.tsx @@ -13,11 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { DomainEntity } from '@backstage/catalog-model'; +import { DomainEntity, RELATION_OWNED_BY } from '@backstage/catalog-model'; import { ItemCard } from '@backstage/core'; import { + EntityRefLinks, entityRoute, entityRouteParams, + getEntityRelations, } from '@backstage/plugin-catalog-react'; import React from 'react'; import { generatePath } from 'react-router-dom'; @@ -26,16 +28,27 @@ type DomainCardProps = { entity: DomainEntity; }; -export const DomainCard = ({ entity }: DomainCardProps) => ( - -); +export const DomainCard = ({ entity }: DomainCardProps) => { + const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); + + return ( + + } + label="Explore" + // TODO: Use useRouteRef here to generate the path + href={generatePath( + `/catalog/${entityRoute.path}`, + entityRouteParams(entity), + )} + /> + ); +}; From 798cbf6336af5afeadf5cd22e008c5f9393a6551 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Feb 2021 09:51:31 +0000 Subject: [PATCH 177/270] chore(deps): bump @roadiehq/backstage-plugin-github-pull-requests Bumps [@roadiehq/backstage-plugin-github-pull-requests](https://github.com/RoadieHQ/backstage-plugin-github-pull-requests) from 0.6.3 to 0.7.6. - [Release notes](https://github.com/RoadieHQ/backstage-plugin-github-pull-requests/releases) - [Commits](https://github.com/RoadieHQ/backstage-plugin-github-pull-requests/commits) Signed-off-by: dependabot[bot] --- packages/app/package.json | 2 +- yarn.lock | 98 ++++++++------------------------------- 2 files changed, 20 insertions(+), 80 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 19e4e38e5c..c7c517b9ff 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -40,7 +40,7 @@ "@octokit/rest": "^18.0.12", "@roadiehq/backstage-plugin-buildkite": "^0.1.3", "@roadiehq/backstage-plugin-github-insights": "^0.2.16", - "@roadiehq/backstage-plugin-github-pull-requests": "^0.6.3", + "@roadiehq/backstage-plugin-github-pull-requests": "^0.7.6", "@roadiehq/backstage-plugin-travis-ci": "^0.2.8", "history": "^5.0.0", "prop-types": "^15.7.2", diff --git a/yarn.lock b/yarn.lock index b0a74498c5..04c68c69c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1867,29 +1867,6 @@ remark-gfm "^1.0.0" zen-observable "^0.8.15" -"@backstage/plugin-catalog@^0.2.0": - version "0.3.2" - dependencies: - "@backstage/catalog-client" "^0.3.6" - "@backstage/catalog-model" "^0.7.1" - "@backstage/core" "^0.6.2" - "@backstage/plugin-catalog-react" "^0.0.4" - "@backstage/plugin-scaffolder" "^0.5.1" - "@backstage/theme" "^0.2.3" - "@material-ui/core" "^4.11.0" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - "@types/react" "^16.9" - classnames "^2.2.6" - git-url-parse "^11.4.4" - react "^16.13.1" - react-dom "^16.13.1" - react-helmet "6.1.0" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" - swr "^0.3.0" - "@backstage/plugin-catalog@^0.2.1": version "0.3.2" dependencies: @@ -3886,17 +3863,6 @@ prop-types "^15.7.2" react-is "^16.8.0" -"@material-ui/lab@^4.0.0-alpha.56": - version "4.0.0-alpha.56" - resolved "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.56.tgz#ff63080949b55b40625e056bbda05e130d216d34" - integrity sha512-xPlkK+z/6y/24ka4gVJgwPfoCF4RCh8dXb1BNE7MtF9bXEBLN/lBxNTK8VAa0qm3V2oinA6xtUIdcRh0aeRtVw== - dependencies: - "@babel/runtime" "^7.4.4" - "@material-ui/utils" "^4.10.2" - clsx "^1.0.4" - prop-types "^15.7.2" - react-is "^16.8.0" - "@material-ui/pickers@^3.2.2": version "3.2.10" resolved "https://registry.npmjs.org/@material-ui/pickers/-/pickers-3.2.10.tgz#19df024895876eb0ec7cd239bbaea595f703f0ae" @@ -4151,14 +4117,6 @@ "@octokit/types" "^6.8.3" deprecation "^2.3.1" -"@octokit/plugin-rest-endpoint-methods@4.4.1": - version "4.4.1" - resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.4.1.tgz#105cf93255432155de078c9efc33bd4e14d1cd63" - integrity sha512-+v5PcvrUcDeFXf8hv1gnNvNLdm4C0+2EiuWt9EatjjUmfriM1pTMM+r4j1lLHxeBQ9bVDmbywb11e3KjuavieA== - dependencies: - "@octokit/types" "^6.1.0" - deprecation "^2.3.1" - "@octokit/request-error@^2.0.0": version "2.0.2" resolved "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.2.tgz#0e76b83f5d8fdda1db99027ea5f617c2e6ba9ed0" @@ -4182,17 +4140,7 @@ once "^1.4.0" universal-user-agent "^6.0.0" -"@octokit/rest@^18.0.0", "@octokit/rest@^18.0.12": - version "18.0.12" - resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.0.12.tgz#278bd41358c56d87c201e787e8adc0cac132503a" - integrity sha512-hNRCZfKPpeaIjOVuNJzkEL6zacfZlBPV8vw8ReNeyUkVvbuCvvrrx8K8Gw2eyHHsmd4dPlAxIXIZ9oHhJfkJpw== - dependencies: - "@octokit/core" "^3.2.3" - "@octokit/plugin-paginate-rest" "^2.6.2" - "@octokit/plugin-request-log" "^1.0.2" - "@octokit/plugin-rest-endpoint-methods" "4.4.1" - -"@octokit/rest@^18.1.0": +"@octokit/rest@^18.0.0", "@octokit/rest@^18.0.12", "@octokit/rest@^18.1.0": version "18.1.1" resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.1.1.tgz#bd7053c28db3577c936029e9da6bfbd046474a2f" integrity sha512-ZcCHMyfGT1qtJD72usigAfUQ6jU89ZUPFb2AOubR6WZ7/RRFVZUENVm1I2yvJBUicqTujezPW9cY1+o3Mb4rNA== @@ -4209,15 +4157,7 @@ dependencies: "@types/node" ">= 8" -"@octokit/types@^6.0.0", "@octokit/types@^6.0.1", "@octokit/types@^6.0.3", "@octokit/types@^6.1.0": - version "6.2.1" - resolved "https://registry.npmjs.org/@octokit/types/-/types-6.2.1.tgz#7f881fe44475ab1825776a4a59ca1ae082ed1043" - integrity sha512-jHs9OECOiZxuEzxMZcXmqrEO8GYraHF+UzNVH2ACYh8e/Y7YoT+hUf9ldvVd6zIvWv4p3NdxbQ0xx3ku5BnSiA== - dependencies: - "@octokit/openapi-types" "^2.2.0" - "@types/node" ">= 8" - -"@octokit/types@^6.8.3": +"@octokit/types@^6.0.0", "@octokit/types@^6.0.1", "@octokit/types@^6.0.3", "@octokit/types@^6.8.3": version "6.8.5" resolved "https://registry.npmjs.org/@octokit/types/-/types-6.8.5.tgz#797dfdad8c75718e97dc687d4c9fc49200ca8d17" integrity sha512-ZsQawftZoi0kSF2pCsdgLURbOjtVcHnBOXiSxBKSNF56CRjARt5rb/g8WJgqB8vv4lgUEHrv06EdDKYQ22vA9Q== @@ -4399,27 +4339,27 @@ react-router "^6.0.0-beta.0" react-use "^15.3.3" -"@roadiehq/backstage-plugin-github-pull-requests@^0.6.3": - version "0.6.3" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-0.6.3.tgz#46b63e90f3f5412a4b8f0df96ada62cff7046478" - integrity sha512-ofWH9k4WVVwTbK/XAVqmtH03QW3rsT822p4neOse0Wy0dAKlqlSU4nwl5jBKMJXsf8lfc0c7gbc50R7VumzoOQ== +"@roadiehq/backstage-plugin-github-pull-requests@^0.7.6": + version "0.7.6" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-0.7.6.tgz#06a7c22ddf26ab504dd8f95c96ef0319c915ed80" + integrity sha512-UMNniDR+0MGhVX6f4Bojm4BOmv6u323ycWkzXNLoQpWSm03EpZGeT8FL1Q0CY0Cd4DgdoV+L0seFqt8jEbOr0g== dependencies: - "@backstage/catalog-model" "^0.2.0" - "@backstage/core" "^0.3.0" - "@backstage/plugin-catalog" "^0.2.0" - "@backstage/theme" "^0.2.0" + "@backstage/catalog-model" "^0.7.1" + "@backstage/core" "^0.6.1" + "@backstage/plugin-catalog" "^0.3.1" + "@backstage/theme" "^0.2.3" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "^4.0.0-alpha.56" "@octokit/rest" "^18.0.0" "@octokit/types" "^5.0.1" - "@types/react-dom" "^16.9.8" + "@types/node-fetch" "^2.5.7" history "^5.0.0" moment "^2.27.0" + node-fetch "^2.6.1" react "^16.13.1" react-dom "^16.13.1" react-router "6.0.0-beta.0" - react-use "^15.3.3" + react-use "^15.3.6" "@roadiehq/backstage-plugin-travis-ci@^0.2.8": version "0.2.8" @@ -6360,7 +6300,7 @@ dependencies: "@types/node" "*" -"@types/node-fetch@2.5.7", "@types/node-fetch@^2.5.4": +"@types/node-fetch@2.5.7": version "2.5.7" resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw== @@ -6368,7 +6308,7 @@ "@types/node" "*" form-data "^3.0.0" -"@types/node-fetch@^2.5.0": +"@types/node-fetch@^2.5.0", "@types/node-fetch@^2.5.4", "@types/node-fetch@^2.5.7": version "2.5.8" resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb" integrity sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw== @@ -21717,10 +21657,10 @@ react-use@^12.2.0: ts-easing "^0.2.0" tslib "^1.10.0" -react-use@^15.3.3: - version "15.3.4" - resolved "https://registry.npmjs.org/react-use/-/react-use-15.3.4.tgz#f853d310bd71f75b38900a8caa3db93f6dc6e872" - integrity sha512-cHq1dELW6122oi1+xX7lwNyE/ugZs5L902BuO8eFJCfn2api1KeuPVG1M/GJouVARoUf54S2dYFMKo5nQXdTag== +react-use@^15.3.3, react-use@^15.3.6: + version "15.3.8" + resolved "https://registry.npmjs.org/react-use/-/react-use-15.3.8.tgz#ca839ac7fb3d696e5ccbeabbc8dadc2698969d30" + integrity sha512-GeGcrmGuUvZrY5wER3Lnph9DSYhZt5nEjped4eKDq8BRGr2CnLf9bDQWG9RFc7oCPphnscUUdOovzq0E5F2c6Q== dependencies: "@types/js-cookie" "2.2.6" "@xobotyi/scrollbar-width" "1.9.5" From 346a0feecf9436db9c36335e6ba10325e73fcef9 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Fri, 19 Feb 2021 10:58:53 +0100 Subject: [PATCH 178/270] TechDocs: Fallback to ENTITY_DEFAULT_NAMESPACE when namespace is undefined --- packages/techdocs-common/src/stages/publish/awsS3.test.ts | 8 ++++++-- .../src/stages/publish/azureBlobStorage.test.ts | 4 ++-- .../src/stages/publish/googleStorage.test.ts | 8 ++++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/awsS3.test.ts b/packages/techdocs-common/src/stages/publish/awsS3.test.ts index 0696ec5b1a..b98f62e0f5 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.test.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.test.ts @@ -13,7 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { Entity, EntityName } from '@backstage/catalog-model'; +import { + Entity, + EntityName, + ENTITY_DEFAULT_NAMESPACE, +} from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import mockFs from 'mock-fs'; import os from 'os'; @@ -52,7 +56,7 @@ const getEntityRootDir = (entity: Entity) => { metadata: { namespace, name }, } = entity; - return path.join(rootDir, namespace as string, kind, name); + return path.join(rootDir, namespace || ENTITY_DEFAULT_NAMESPACE, kind, name); }; const logger = winston.createLogger(); diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts index b88b58424d..d8a112a403 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { getVoidLogger } from '@backstage/backend-common'; -import type { Entity } from '@backstage/catalog-model'; +import { Entity, ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import mockFs from 'mock-fs'; import os from 'os'; @@ -45,7 +45,7 @@ const getEntityRootDir = (entity: Entity) => { metadata: { namespace, name }, } = entity; - return path.join(rootDir, namespace as string, kind, name); + return path.join(rootDir, namespace || ENTITY_DEFAULT_NAMESPACE, kind, name); }; function createLogger() { diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts index a8df9ccbc1..e0d56db8cc 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts @@ -14,7 +14,11 @@ * limitations under the License. */ import { getVoidLogger } from '@backstage/backend-common'; -import { Entity, EntityName } from '@backstage/catalog-model'; +import { + Entity, + EntityName, + ENTITY_DEFAULT_NAMESPACE, +} from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import mockFs from 'mock-fs'; import os from 'os'; @@ -52,7 +56,7 @@ const getEntityRootDir = (entity: Entity) => { metadata: { namespace, name }, } = entity; - return path.join(rootDir, namespace as string, kind, name); + return path.join(rootDir, namespace || ENTITY_DEFAULT_NAMESPACE, kind, name); }; const logger = getVoidLogger(); From 5851bd5d2b01738c3dab7c0926a45636286be8f4 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Fri, 19 Feb 2021 11:06:27 +0100 Subject: [PATCH 179/270] TechDocs: Use expect.assertions to make sure the error messages are checked --- packages/techdocs-common/src/stages/publish/awsS3.test.ts | 1 + .../techdocs-common/src/stages/publish/azureBlobStorage.test.ts | 1 + .../techdocs-common/src/stages/publish/googleStorage.test.ts | 2 ++ 3 files changed, 4 insertions(+) diff --git a/packages/techdocs-common/src/stages/publish/awsS3.test.ts b/packages/techdocs-common/src/stages/publish/awsS3.test.ts index b98f62e0f5..3d7bd337d2 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.test.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.test.ts @@ -120,6 +120,7 @@ describe('AwsS3Publish', () => { }); it('should fail to publish a directory', async () => { + expect.assertions(3); const wrongPathToGeneratedDirectory = path.join( rootDir, 'wrong', diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts index d8a112a403..cf1ea60592 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts @@ -111,6 +111,7 @@ describe('publishing with valid credentials', () => { }); it('should fail to publish a directory', async () => { + expect.assertions(1); const wrongPathToGeneratedDirectory = path.join( rootDir, 'wrong', diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts index e0d56db8cc..10da0d476a 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts @@ -117,6 +117,8 @@ describe('GoogleGCSPublish', () => { }); it('should fail to publish a directory', async () => { + expect.assertions(3); + const wrongPathToGeneratedDirectory = path.join( rootDir, 'wrong', From 1a8e19486f65a5dcd8d1a9d721f6cd5498c757d7 Mon Sep 17 00:00:00 2001 From: Olle Lundberg Date: Fri, 19 Feb 2021 12:04:06 +0100 Subject: [PATCH 180/270] Add acast to adopters --- ADOPTERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index 363c092198..d76e24ef04 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -19,3 +19,4 @@ | [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. | | [Expedia Group](https://www.expediagroup.com) | [Mike Turner](mailto:miturner@expediagroup.com), [Sneha Kumar](mailto:snkumar@expediagroup.com), [@guillermomanzo](https://github.com/guillermomanzo) | EG Common Developer Toolkit | | [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go | +| [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling | From 085736eca363be713611cf898c1adc75a9c4f72e Mon Sep 17 00:00:00 2001 From: Olle Lundberg Date: Fri, 19 Feb 2021 12:36:09 +0100 Subject: [PATCH 181/270] Sort vocabulary file --- .github/styles/vocab.txt | 190 +++++++++++++++++++-------------------- 1 file changed, 95 insertions(+), 95 deletions(-) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index c5a0b02529..34ec6adac8 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -1,31 +1,113 @@ +Apdex +Api +Autoscaling +Avro +Balachandran +Bigtable +Billett +Blackbox +Chai +Changesets +Chanwit +Codecov +Codehilite +Config +Discoverability +Dockerfile +Dockerize +Docusaurus +Dominik +Ek +Env +Expedia +Figma +Firekube +Fiverr +Fredrik +Georgoulas +GitHub +GitLab +Grafana +GraphQL +Gustavsson +Hackathons +Henneke +Heroku +Hostname +Iain +Ioannis +JavaScript +Kaewkasi +Knex +Kumar +Lerna +Luxon +Malus +Minikube +Mkdocs +Monorepo +Namespaces +Niklas +OAuth +Okta +Oldsberg +Onboarding +Patrik +Phoen +Pomaceous +Preprarer +Protobuf +Proxying +Raghunandan +Readme +Recharts +Redash +Repo +Rollbar +Rollup +Rosaceae +Routable +Scaffolder +Serverless +Sinon +Sneha +Snyk +Splunk +Spotifiers +Spotify +Superfences +Talkdesk +Telenor +Templater +Templaters +Thauer +Tolerations +Tuite +Voi +WWW +Wealthsimple +Weaveworks +Webpack +Zalando +Zhou +Zolotusky abc adamdmharvey andrewthauer -Apdex api -Api apis args asciidoc async -Autoscaling autoscaling -Avro backrub -Balachandran benjdlambert -Bigtable -Billett -Blackbox bool boolean builtins -Chai changeset changesets -Changesets chanwit -Chanwit ci cisphobia cissexist @@ -34,14 +116,11 @@ cli cloudbuild cncf codeblocks -Codecov codehilite -Codehilite codeowners composability composable config -Config configmaps configs const @@ -56,97 +135,61 @@ devops devs dhenneke discoverability -Discoverability dls docgen -Dockerfile -Dockerize dockerode -Docusaurus -Dominik dtuite dzolotusky -Ek -etag env -Env esbuild eslint -Expedia +etag facto failover -Figma -Firekube -Fiverr freben -Fredrik -Georgoulas gitbeaker -GitHub -GitLab -Grafana -GraphQL graphql graphviz -Gustavsson -Hackathons haproxy -Henneke -Heroku horizontalpodautoscalers -Hostname hotspots html http https -Iain img incentivised inlined inlinehilite interop -Ioannis -JavaScript jq js json jsx -Kaewkasi -Knex kubectl kubernetes -Kumar learnings lerna -Lerna lockfile -Luxon magiclink mailto maintainership -Malus md microsite middleware minikube -Minikube misconfiguration misconfigured misgendering mkdocs -Mkdocs monorepo -Monorepo monorepos msw namespace namespaces -Namespaces namespacing neuro newrelic nginx -Niklas nodegit nohoist nonces @@ -154,49 +197,30 @@ noop npm nvarchar nvm -OAuth octokit oidc -Okta -Oldsberg onboarding -Onboarding pagerduty parallelization -Patrik -Phoen plantuml -Pomaceous postgres postpack pre prebaked preconfigured prepack -Preprarer productional -Protobuf proxying -Proxying pygments pymdownx -Raghunandan rankdir readme -Readme -Recharts -Redash replicasets repo -Repo repos rerender rollbar -Rollbar -Rollup -Rosaceae routable -Routable rst rsync rugvip @@ -204,19 +228,11 @@ ruleset sam scaffolded scaffolder -Scaffolder semlas semver -Serverless -Sinon -Sneha -Snyk sourcemaps sparklines -Splunk -Spotifiers spotify -Spotify sqlite squidfunk src @@ -227,30 +243,22 @@ subcomponents subkey subtree superfences -Superfences superset talkdesk -Talkdesk tasklist techdocs -Telenor templated templater -Templater templaters -Templaters -Thauer toc tolerations -Tolerations toolchain toolsets tooltip tooltips touchpoints -transpiled transpilation -Tuite +transpiled ui unmanaged unregister @@ -260,16 +268,8 @@ url utils validators varchar -Voi -Wealthsimple -Weaveworks -Webpack winston www -WWW xyz yaml -Zalando -Zhou -Zolotusky zoomable From 56544eac812c2021b03b142bc433125a013fc301 Mon Sep 17 00:00:00 2001 From: Olle Lundberg Date: Fri, 19 Feb 2021 12:36:58 +0100 Subject: [PATCH 182/270] Add Olle and Lundberg to vocab --- .github/styles/vocab.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 34ec6adac8..41ec95e9aa 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -41,6 +41,7 @@ Kaewkasi Knex Kumar Lerna +Lundberg Luxon Malus Minikube @@ -51,6 +52,7 @@ Niklas OAuth Okta Oldsberg +Olle Onboarding Patrik Phoen From 1c06cb3126d6824cfa6916e486d74484fdf0e5fd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 19 Feb 2021 12:57:49 +0100 Subject: [PATCH 183/270] app-backend: clarify schema serialization troubleshooting steps --- .changeset/seven-kangaroos-work.md | 5 +++++ plugins/app-backend/src/lib/config.ts | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 .changeset/seven-kangaroos-work.md diff --git a/.changeset/seven-kangaroos-work.md b/.changeset/seven-kangaroos-work.md new file mode 100644 index 0000000000..6e4ac95c9d --- /dev/null +++ b/.changeset/seven-kangaroos-work.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app-backend': patch +--- + +Clarify troubleshooting steps for schema serialization issues. diff --git a/plugins/app-backend/src/lib/config.ts b/plugins/app-backend/src/lib/config.ts index 0607956894..4fe383414f 100644 --- a/plugins/app-backend/src/lib/config.ts +++ b/plugins/app-backend/src/lib/config.ts @@ -95,8 +95,9 @@ export async function readConfigs(options: ReadOptions): Promise { appConfigs.push(...frontendConfigs); } catch (error) { throw new Error( - 'Invalid schema embedded in the app bundle, to fix this issue you need ' + - `to correct the schema and then rebuild the app bundle. ${error}`, + 'Invalid app bundle schema. If this error is unexpected you need to run `yarn build` in the app. ' + + `If that doesn't help you should make sure your config schema is correct and rebuild the app bundle again. ` + + `Caused by the following schema error, ${error}`, ); } } From 0261f772c348dfb628d6bd7812ff71f62846009c Mon Sep 17 00:00:00 2001 From: Jeff Feng <46946747+fengypants@users.noreply.github.com> Date: Fri, 19 Feb 2021 10:04:46 -0500 Subject: [PATCH 184/270] Added tip for viewing data in demo.backstage.io Default view of demo.backstage.io is Owned/Starred software, so it looks like there is no data. Added tip to click "All" to see example software in the catalog. (Maybe default to "All" or add some to "Owned/Starred" in the future?) --- microsite/pages/en/demos.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/microsite/pages/en/demos.js b/microsite/pages/en/demos.js index 47ea1ee9ee..86cb6c786d 100644 --- a/microsite/pages/en/demos.js +++ b/microsite/pages/en/demos.js @@ -21,6 +21,8 @@ const Background = props => { To explore the UI and basic features of Backstage firsthand, go to: demo.backstage.io. + (Tip: click “All” to view all the example components in the + service catalog.) Watch the videos below to get an introduction to Backstage and to From d6593abe6f414248d6ae172afd63201c78617313 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Fri, 19 Feb 2021 16:42:11 +0100 Subject: [PATCH 185/270] Remove columns from cards where it's duplicate information Resolves #4553 Signed-off-by: Oliver Sand --- .changeset/healthy-schools-kneel.md | 7 +++++++ .../src/components/ApisCards/HasApisCard.tsx | 13 +++++++++++-- .../HasComponentsCard/HasComponentsCard.tsx | 10 +++++++++- .../HasSubcomponentsCard/HasSubcomponentsCard.tsx | 10 +++++++++- .../components/HasSystemsCard/HasSystemsCard.tsx | 8 +++++++- 5 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 .changeset/healthy-schools-kneel.md diff --git a/.changeset/healthy-schools-kneel.md b/.changeset/healthy-schools-kneel.md new file mode 100644 index 0000000000..fa7b98a256 --- /dev/null +++ b/.changeset/healthy-schools-kneel.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-api-docs': patch +'@backstage/plugin-catalog': patch +--- + +Remove domain column from `HasSystemsCard` and system from `HasComponentsCard`, +`HasSubcomponentsCard`, and `HasApisCard`. diff --git a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx index 9f1d487b65..c3e636b1f1 100644 --- a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx @@ -20,6 +20,7 @@ import { InfoCard, Link, Progress, + TableColumn, WarningPanel, } from '@backstage/core'; import { @@ -28,12 +29,20 @@ import { useRelatedEntities, } from '@backstage/plugin-catalog-react'; import React from 'react'; -import { apiEntityColumns } from './presets'; +import { createSpecApiTypeColumn } from './presets'; type Props = { variant?: 'gridItem'; }; +const columns: TableColumn[] = [ + EntityTable.columns.createEntityRefColumn({ defaultKind: 'API' }), + EntityTable.columns.createOwnerColumn(), + EntityTable.columns.createSpecLifecycleColumn(), + createSpecApiTypeColumn(), + EntityTable.columns.createMetadataDescriptionColumn(), +]; + export const HasApisCard = ({ variant = 'gridItem' }: Props) => { const { entity } = useEntity(); const { entities, loading, error } = useRelatedEntities(entity, { @@ -73,7 +82,7 @@ export const HasApisCard = ({ variant = 'gridItem' }: Props) => {
} - columns={apiEntityColumns} + columns={columns} entities={entities as ApiEntity[]} /> ); diff --git a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx index 6485d4f525..eb8e96d9b3 100644 --- a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx +++ b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx @@ -33,6 +33,14 @@ type Props = { variant?: 'gridItem'; }; +const columns = [ + EntityTable.columns.createEntityRefColumn({ defaultKind: 'component' }), + EntityTable.columns.createOwnerColumn(), + EntityTable.columns.createSpecTypeColumn(), + EntityTable.columns.createSpecLifecycleColumn(), + EntityTable.columns.createMetadataDescriptionColumn(), +]; + export const HasComponentsCard = ({ variant = 'gridItem' }: Props) => { const { entity } = useEntity(); const { entities, loading, error } = useRelatedEntities(entity, { @@ -72,7 +80,7 @@ export const HasComponentsCard = ({ variant = 'gridItem' }: Props) => {
} - columns={EntityTable.componentEntityColumns} + columns={columns} entities={entities as ComponentEntity[]} /> ); diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx index c2635b68e1..778ec1dec5 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx @@ -33,6 +33,14 @@ type Props = { variant?: 'gridItem'; }; +const columns = [ + EntityTable.columns.createEntityRefColumn({ defaultKind: 'component' }), + EntityTable.columns.createOwnerColumn(), + EntityTable.columns.createSpecTypeColumn(), + EntityTable.columns.createSpecLifecycleColumn(), + EntityTable.columns.createMetadataDescriptionColumn(), +]; + export const HasSubcomponentsCard = ({ variant = 'gridItem' }: Props) => { const { entity } = useEntity(); const { entities, loading, error } = useRelatedEntities(entity, { @@ -72,7 +80,7 @@ export const HasSubcomponentsCard = ({ variant = 'gridItem' }: Props) => {
} - columns={EntityTable.componentEntityColumns} + columns={columns} entities={entities as ComponentEntity[]} /> ); diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx index 9faa640060..5b18711d44 100644 --- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx +++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx @@ -33,6 +33,12 @@ type Props = { variant?: 'gridItem'; }; +const columns = [ + EntityTable.columns.createEntityRefColumn({ defaultKind: 'system' }), + EntityTable.columns.createOwnerColumn(), + EntityTable.columns.createMetadataDescriptionColumn(), +]; + export const HasSystemsCard = ({ variant = 'gridItem' }: Props) => { const { entity } = useEntity(); const { entities, loading, error } = useRelatedEntities(entity, { @@ -71,7 +77,7 @@ export const HasSystemsCard = ({ variant = 'gridItem' }: Props) => {
} - columns={EntityTable.systemEntityColumns} + columns={columns} entities={entities as SystemEntity[]} /> ); From 437bac5490096849de9471a20a7325b7a51f235c Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Fri, 19 Feb 2021 16:54:44 +0100 Subject: [PATCH 186/270] Make the description column in the catalog table use up as much space as possible before hiding overflowing text Signed-off-by: Oliver Sand --- .changeset/olive-moons-melt.md | 7 +++++++ .../examples/components/petstore-component.yaml | 3 ++- .../src/components/ApiExplorerTable/ApiExplorerTable.tsx | 1 + .../catalog/src/components/CatalogTable/CatalogTable.tsx | 1 + 4 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .changeset/olive-moons-melt.md diff --git a/.changeset/olive-moons-melt.md b/.changeset/olive-moons-melt.md new file mode 100644 index 0000000000..415c4cb8cb --- /dev/null +++ b/.changeset/olive-moons-melt.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog': patch +'@backstage/plugin-api-docs': patch +--- + +Make the description column in the catalog table and api-docs table use up as +much space as possible before hiding overflowing text. diff --git a/packages/catalog-model/examples/components/petstore-component.yaml b/packages/catalog-model/examples/components/petstore-component.yaml index 48286f9b74..878fabd551 100644 --- a/packages/catalog-model/examples/components/petstore-component.yaml +++ b/packages/catalog-model/examples/components/petstore-component.yaml @@ -2,7 +2,8 @@ apiVersion: backstage.io/v1alpha1 kind: Component metadata: name: petstore - description: Petstore + # This is an extra long description + description: The Petstore is an example API used to show features of the OpenAPI spec. links: - url: https://github.com/swagger-api/swagger-petstore title: GitHub Repo diff --git a/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx index 729f659b56..5b9808e26d 100644 --- a/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx +++ b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx @@ -99,6 +99,7 @@ const columns: TableColumn[] = [ placement="bottom-start" /> ), + width: 'auto', }, { title: 'Tags', diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 91f187855a..ce2203bfbb 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -98,6 +98,7 @@ const columns: TableColumn[] = [ placement="bottom-start" /> ), + width: 'auto', }, { title: 'Tags', From 88f1f1b607f021a64393b3760f551852aedc217e Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Fri, 19 Feb 2021 16:56:56 +0100 Subject: [PATCH 187/270] Truncate and show ellipsis with tooltip if content of `createMetadataDescriptionColumn` is too wide Signed-off-by: Oliver Sand --- .changeset/gentle-zoos-pump.md | 6 ++++++ .../catalog-react/src/components/EntityTable/columns.tsx | 8 +++++++- .../src/components/EntityTable/presets.test.tsx | 4 ++-- 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 .changeset/gentle-zoos-pump.md diff --git a/.changeset/gentle-zoos-pump.md b/.changeset/gentle-zoos-pump.md new file mode 100644 index 0000000000..77e996cbcc --- /dev/null +++ b/.changeset/gentle-zoos-pump.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Truncate and show ellipsis with tooltip if content of +`createMetadataDescriptionColumn` is too wide. diff --git a/plugins/catalog-react/src/components/EntityTable/columns.tsx b/plugins/catalog-react/src/components/EntityTable/columns.tsx index 7ca99b1100..7f5417df4e 100644 --- a/plugins/catalog-react/src/components/EntityTable/columns.tsx +++ b/plugins/catalog-react/src/components/EntityTable/columns.tsx @@ -20,7 +20,7 @@ import { RELATION_OWNED_BY, RELATION_PART_OF, } from '@backstage/catalog-model'; -import { TableColumn } from '@backstage/core'; +import { OverflowTooltip, TableColumn } from '@backstage/core'; import React from 'react'; import { getEntityRelations } from '../../utils'; import { @@ -139,6 +139,12 @@ export function createMetadataDescriptionColumn< return { title: 'Description', field: 'metadata.description', + render: entity => ( + + ), width: 'auto', }; } diff --git a/plugins/catalog-react/src/components/EntityTable/presets.test.tsx b/plugins/catalog-react/src/components/EntityTable/presets.test.tsx index b6b8c26e37..0f87c4132f 100644 --- a/plugins/catalog-react/src/components/EntityTable/presets.test.tsx +++ b/plugins/catalog-react/src/components/EntityTable/presets.test.tsx @@ -74,7 +74,7 @@ describe('systemEntityColumns', () => { expect(getByText('my-namespace/my-system')).toBeInTheDocument(); expect(getByText('my-namespace/my-domain')).toBeInTheDocument(); expect(getByText('Test')).toBeInTheDocument(); - expect(getByText('Some description')).toBeInTheDocument(); + expect(getByText(/Some/)).toBeInTheDocument(); }); }); }); @@ -131,7 +131,7 @@ describe('componentEntityColumns', () => { expect(getByText('Test')).toBeInTheDocument(); expect(getByText('production')).toBeInTheDocument(); expect(getByText('service')).toBeInTheDocument(); - expect(getByText('Some description')).toBeInTheDocument(); + expect(getByText(/Some/)).toBeInTheDocument(); }); }); }); From e799e74d4ea5362f11b8003ec72f1e81c6d5380c Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Fri, 19 Feb 2021 18:00:42 +0100 Subject: [PATCH 188/270] Fix `OverflowTooltip` cutting of the bottom of letters like "g" and "y" Signed-off-by: Oliver Sand --- .changeset/curvy-ducks-tease.md | 5 +++++ .../src/components/OverflowTooltip/OverflowTooltip.tsx | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 .changeset/curvy-ducks-tease.md diff --git a/.changeset/curvy-ducks-tease.md b/.changeset/curvy-ducks-tease.md new file mode 100644 index 0000000000..43e73f542a --- /dev/null +++ b/.changeset/curvy-ducks-tease.md @@ -0,0 +1,5 @@ +--- +'@backstage/core': patch +--- + +Fix `OverflowTooltip` cutting off the bottom of letters like "g" and "y". diff --git a/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx b/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx index 9bbe7aeb81..faf064f9f4 100644 --- a/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx +++ b/packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Tooltip, TooltipProps } from '@material-ui/core'; +import { makeStyles, Tooltip, TooltipProps } from '@material-ui/core'; import React, { useState } from 'react'; import TextTruncate, { TextTruncateProps } from 'react-text-truncate'; @@ -26,8 +26,15 @@ type Props = { placement?: TooltipProps['placement']; }; +const useStyles = makeStyles({ + container: { + overflow: 'visible !important', + }, +}); + export const OverflowTooltip = (props: Props) => { const [hover, setHover] = useState(false); + const classes = useStyles(); const handleToggled = (truncated: boolean) => { setHover(truncated); @@ -43,6 +50,7 @@ export const OverflowTooltip = (props: Props) => { text={props.text} line={props.line} onToggled={handleToggled} + containerClassName={classes.container} /> ); From a45dd99b9084c660ef57f7e240df98f3e43a3ed3 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Fri, 19 Feb 2021 13:28:54 -0500 Subject: [PATCH 189/270] Remove unused deps --- plugins/scaffolder/package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index c5b264154d..ebb7f2afad 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -38,14 +38,12 @@ "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.45", "@rjsf/core": "^2.4.0", "@rjsf/material-ui": "^2.4.0", "classnames": "^2.2.6", "git-url-parse": "^11.4.4", "humanize-duration": "^3.25.1", "luxon": "^1.25.0", - "moment": "^2.26.0", "react": "^16.13.1", "react-dom": "^16.13.1", "react-lazylog": "^4.5.2", From 38c8c8a538084374fe917e63cba276b6a96e8093 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Fri, 19 Feb 2021 14:12:21 -0500 Subject: [PATCH 190/270] Changing loading and error msg --- .../ScaffolderPage/ScaffolderPage.tsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index 74582d0762..4462c333b1 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -156,21 +156,24 @@ export const ScaffolderPageContents = () => {
- {!matchingEntities && loading && } - {matchingEntities && !matchingEntities.length && ( - - Shoot! Looks like you don't have any templates. Check out the - documentation{' '} - - here! - - - )} + + {loading && } + {error && ( {error.message} )} + + {!error && !loading && matchingEntities && !matchingEntities.length && ( + + No templates found that match your filter. Learn more about{' '} + + adding templates + . + + )} + {matchingEntities && matchingEntities?.length > 0 && From e488f050266ba21db2fb85a9058ac8b8d3e38bc3 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Fri, 19 Feb 2021 14:13:08 -0500 Subject: [PATCH 191/270] add changeset --- .changeset/curly-poems-boil.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/curly-poems-boil.md diff --git a/.changeset/curly-poems-boil.md b/.changeset/curly-poems-boil.md new file mode 100644 index 0000000000..22d5df4665 --- /dev/null +++ b/.changeset/curly-poems-boil.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Update messages that process during loading, error, and no templates found. +Remove unused dependencies. From d171e60c2cec8d4c7881ee25faea2ca77dd7b962 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Fri, 19 Feb 2021 14:14:07 -0500 Subject: [PATCH 192/270] Prettier --- .../ScaffolderPage/ScaffolderPage.tsx | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index 4462c333b1..e4ebc75b2e 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -156,7 +156,6 @@ export const ScaffolderPageContents = () => {
- {loading && } {error && ( @@ -165,14 +164,18 @@ export const ScaffolderPageContents = () => { )} - {!error && !loading && matchingEntities && !matchingEntities.length && ( - - No templates found that match your filter. Learn more about{' '} - - adding templates - . - - )} + {!error && + !loading && + matchingEntities && + !matchingEntities.length && ( + + No templates found that match your filter. Learn more about{' '} + + adding templates + + . + + )} {matchingEntities && From 1407b34c6eb4d946e7287ac03d42bf60f766f950 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 19 Feb 2021 21:35:19 +0100 Subject: [PATCH 193/270] core-api: more informative error for missing ApiContext --- .changeset/gentle-buses-exist.md | 6 ++++++ packages/core-api/src/apis/system/ApiProvider.test.tsx | 8 ++++---- packages/core-api/src/apis/system/ApiProvider.tsx | 10 ++++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 .changeset/gentle-buses-exist.md diff --git a/.changeset/gentle-buses-exist.md b/.changeset/gentle-buses-exist.md new file mode 100644 index 0000000000..a32f0bf8f0 --- /dev/null +++ b/.changeset/gentle-buses-exist.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-api': patch +'@backstage/core': patch +--- + +More informative error message for missing ApiContext. diff --git a/packages/core-api/src/apis/system/ApiProvider.test.tsx b/packages/core-api/src/apis/system/ApiProvider.test.tsx index ac64aca3a7..71269697c2 100644 --- a/packages/core-api/src/apis/system/ApiProvider.test.tsx +++ b/packages/core-api/src/apis/system/ApiProvider.test.tsx @@ -108,11 +108,11 @@ describe('ApiProvider', () => { withLogCollector(['error'], () => { expect(() => { render(); - }).toThrow('No ApiProvider available in react context'); + }).toThrow(/^No ApiProvider available in react context. /); }).error, ).toEqual([ expect.stringMatching( - /^Error: Uncaught \[Error: No ApiProvider available in react context\]/, + /^Error: Uncaught \[Error: No ApiProvider available in react context. /, ), expect.stringMatching( /^The above error occurred in the component/, @@ -123,11 +123,11 @@ describe('ApiProvider', () => { withLogCollector(['error'], () => { expect(() => { render(); - }).toThrow('No ApiProvider available in react context'); + }).toThrow(/^No ApiProvider available in react context. /); }).error, ).toEqual([ expect.stringMatching( - /^Error: Uncaught \[Error: No ApiProvider available in react context\]/, + /^Error: Uncaught \[Error: No ApiProvider available in react context. /, ), expect.stringMatching( /^The above error occurred in the component/, diff --git a/packages/core-api/src/apis/system/ApiProvider.tsx b/packages/core-api/src/apis/system/ApiProvider.tsx index 91d35e5ee7..d41730cb59 100644 --- a/packages/core-api/src/apis/system/ApiProvider.tsx +++ b/packages/core-api/src/apis/system/ApiProvider.tsx @@ -24,6 +24,12 @@ import PropTypes from 'prop-types'; import { ApiRef, ApiHolder, TypesToApiRefs } from './types'; import { ApiAggregator } from './ApiAggregator'; +const missingHolderMessage = + 'No ApiProvider available in react context. ' + + 'A common cause of this error is that multiple versions of @backstage/core-api are installed. ' + + `You can check if that is the case using 'yarn backstage-cli versions:check', and can in many cases ` + + `fix the issue either with the --fix flag or using 'yarn backstage-cli versions:bump'`; + type ApiProviderProps = { apis: ApiHolder; children: ReactNode; @@ -50,7 +56,7 @@ export function useApiHolder(): ApiHolder { const apiHolder = useContext(Context); if (!apiHolder) { - throw new Error('No ApiProvider available in react context'); + throw new Error(missingHolderMessage); } return apiHolder; @@ -74,7 +80,7 @@ export function withApis(apis: TypesToApiRefs) { const apiHolder = useContext(Context); if (!apiHolder) { - throw new Error('No ApiProvider available in react context'); + throw new Error(missingHolderMessage); } const impls = {} as T; From c46bc07d0e6d9d62ccffd32124c1783d2db4ee4b Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Fri, 19 Feb 2021 15:41:40 -0500 Subject: [PATCH 194/270] Return material-ui/lab --- plugins/scaffolder/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index ebb7f2afad..2018887013 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -38,6 +38,7 @@ "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.45", "@rjsf/core": "^2.4.0", "@rjsf/material-ui": "^2.4.0", "classnames": "^2.2.6", From b3126588dc274e41a0a488c52e8f1962f1999510 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Fri, 19 Feb 2021 16:02:08 -0500 Subject: [PATCH 195/270] Add note about sidebar --- plugins/cost-insights/README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/plugins/cost-insights/README.md b/plugins/cost-insights/README.md index 86adb8673f..4d7ef9701a 100644 --- a/plugins/cost-insights/README.md +++ b/plugins/cost-insights/README.md @@ -56,6 +56,38 @@ export const apis = [ export { plugin as CostInsights } from '@backstage/plugin-cost-insights'; ``` +5. Add Cost Insights to your app Sidebar. + +To expose the plugin to your users, you can integrate the `cost-insights` route anyway that suits your application, but most commonly it is added to the Sidebar. + +```ts +// packages/app/src/sidebar.tsx ++ import MoneyIcon from '@material-ui/icons/MonetizationOn'; + + ... + + export const AppSidebar = () => ( + + + + + {/* Global nav, not org-specific */} + + + + + + ++ + {/* End global nav */} + + + + + + ); +``` + ## Configuration Cost Insights has only two required configuration fields: a map of cloud `products` for showing cost breakdowns and `engineerCost` - the average yearly cost of an engineer including benefits. Products must be defined as keys on the `products` field. From acc1da48cd8136be1f05dd302f966bb143761bca Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Fri, 19 Feb 2021 16:03:20 -0500 Subject: [PATCH 196/270] Make codefence a diff --- plugins/cost-insights/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/cost-insights/README.md b/plugins/cost-insights/README.md index 4d7ef9701a..c10d483f3f 100644 --- a/plugins/cost-insights/README.md +++ b/plugins/cost-insights/README.md @@ -60,7 +60,7 @@ export { plugin as CostInsights } from '@backstage/plugin-cost-insights'; To expose the plugin to your users, you can integrate the `cost-insights` route anyway that suits your application, but most commonly it is added to the Sidebar. -```ts +```diff // packages/app/src/sidebar.tsx + import MoneyIcon from '@material-ui/icons/MonetizationOn'; From d0760ecdf3c599a8e65757f908137735f0785689 Mon Sep 17 00:00:00 2001 From: Oscar Hernandez Date: Fri, 19 Feb 2021 16:26:55 -0600 Subject: [PATCH 197/270] Scaffolder: Cleanup --- .changeset/four-owls-raise.md | 7 + plugins/catalog-react/src/hooks/index.ts | 1 + .../src/hooks/useStarredEntities.test.tsx | 0 .../src/hooks/useStarredEntities.ts | 0 .../components/CatalogPage/CatalogPage.tsx | 8 +- .../components/CatalogTable/CatalogTable.tsx | 3 +- .../FavouriteEntity/FavouriteEntity.tsx | 2 +- .../src/hooks/useStarredEntities.test.tsx | 121 ------------------ .../ResultsFilter/ResultsFilter.tsx | 16 +-- .../ScaffolderFilter/AllServicesCount.tsx | 33 ----- .../ScaffolderPage/ScaffolderPage.tsx | 8 +- .../src/filter/EntityFilterGroupsProvider.tsx | 1 + plugins/scaffolder/src/filter/context.ts | 1 + .../src/hooks/useStarredEntities.ts | 75 ----------- 14 files changed, 27 insertions(+), 249 deletions(-) create mode 100644 .changeset/four-owls-raise.md rename plugins/{scaffolder => catalog-react}/src/hooks/useStarredEntities.test.tsx (100%) rename plugins/{catalog => catalog-react}/src/hooks/useStarredEntities.ts (100%) delete mode 100644 plugins/catalog/src/hooks/useStarredEntities.test.tsx delete mode 100644 plugins/scaffolder/src/components/ScaffolderFilter/AllServicesCount.tsx delete mode 100644 plugins/scaffolder/src/hooks/useStarredEntities.ts diff --git a/.changeset/four-owls-raise.md b/.changeset/four-owls-raise.md new file mode 100644 index 0000000000..6e8824cb11 --- /dev/null +++ b/.changeset/four-owls-raise.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog': minor +'@backstage/plugin-catalog-react': minor +'@backstage/plugin-scaffolder': minor +--- + +Moved common useStarredEntities hook to plugin-catalog-react diff --git a/plugins/catalog-react/src/hooks/index.ts b/plugins/catalog-react/src/hooks/index.ts index 2ecf6e9a90..d964c22a3b 100644 --- a/plugins/catalog-react/src/hooks/index.ts +++ b/plugins/catalog-react/src/hooks/index.ts @@ -16,3 +16,4 @@ export { EntityContext, useEntity, useEntityFromUrl } from './useEntity'; export { useEntityCompoundName } from './useEntityCompoundName'; export { useRelatedEntities } from './useRelatedEntities'; +export { useStarredEntities } from './useStarredEntities'; diff --git a/plugins/scaffolder/src/hooks/useStarredEntities.test.tsx b/plugins/catalog-react/src/hooks/useStarredEntities.test.tsx similarity index 100% rename from plugins/scaffolder/src/hooks/useStarredEntities.test.tsx rename to plugins/catalog-react/src/hooks/useStarredEntities.test.tsx diff --git a/plugins/catalog/src/hooks/useStarredEntities.ts b/plugins/catalog-react/src/hooks/useStarredEntities.ts similarity index 100% rename from plugins/catalog/src/hooks/useStarredEntities.ts rename to plugins/catalog-react/src/hooks/useStarredEntities.ts diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index ed51316dcd..327bc2e472 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -23,14 +23,18 @@ import { useApi, useRouteRef, } from '@backstage/core'; -import { catalogApiRef, isOwnerOf } from '@backstage/plugin-catalog-react'; +import { + catalogApiRef, + isOwnerOf, + useStarredEntities, +} from '@backstage/plugin-catalog-react'; + import { Button, makeStyles } from '@material-ui/core'; import SettingsIcon from '@material-ui/icons/Settings'; import StarIcon from '@material-ui/icons/Star'; import React, { useCallback, useMemo, useState } from 'react'; import { Link as RouterLink } from 'react-router-dom'; import { EntityFilterGroupsProvider, useFilteredEntities } from '../../filter'; -import { useStarredEntities } from '../../hooks/useStarredEntities'; import { createComponentRouteRef } from '../../routes'; import { ButtonGroup, diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 91f187855a..12d97e91a6 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -32,13 +32,14 @@ import { EntityRefLinks, formatEntityRefTitle, getEntityRelations, + useStarredEntities, } from '@backstage/plugin-catalog-react'; + import { Chip } from '@material-ui/core'; import Edit from '@material-ui/icons/Edit'; import OpenInNew from '@material-ui/icons/OpenInNew'; import React from 'react'; import { findLocationForEntityMeta } from '../../data/utils'; -import { useStarredEntities } from '../../hooks/useStarredEntities'; import { createEditLink } from '../createEditLink'; import { favouriteEntityIcon, diff --git a/plugins/catalog/src/components/FavouriteEntity/FavouriteEntity.tsx b/plugins/catalog/src/components/FavouriteEntity/FavouriteEntity.tsx index 970ce32ece..1c414414aa 100644 --- a/plugins/catalog/src/components/FavouriteEntity/FavouriteEntity.tsx +++ b/plugins/catalog/src/components/FavouriteEntity/FavouriteEntity.tsx @@ -15,10 +15,10 @@ */ import React, { ComponentProps } from 'react'; +import { useStarredEntities } from '@backstage/plugin-catalog-react'; import { IconButton, Tooltip, withStyles } from '@material-ui/core'; import StarBorder from '@material-ui/icons/StarBorder'; import Star from '@material-ui/icons/Star'; -import { useStarredEntities } from '../../hooks/useStarredEntities'; import { Entity } from '@backstage/catalog-model'; type Props = ComponentProps & { entity: Entity }; diff --git a/plugins/catalog/src/hooks/useStarredEntities.test.tsx b/plugins/catalog/src/hooks/useStarredEntities.test.tsx deleted file mode 100644 index 78d2c4a58f..0000000000 --- a/plugins/catalog/src/hooks/useStarredEntities.test.tsx +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 React, { PropsWithChildren } from 'react'; -import { renderHook, act } from '@testing-library/react-hooks'; -import { useStarredEntities } from './useStarredEntities'; -import { - ApiProvider, - ApiRegistry, - storageApiRef, - WebStorage, - StorageApi, -} from '@backstage/core'; -import { MockErrorApi } from '@backstage/test-utils'; -import { Entity } from '@backstage/catalog-model'; - -describe('useStarredEntities', () => { - let mockStorage: StorageApi | undefined; - - const mockEntity: Entity = { - apiVersion: '1', - kind: 'Component', - metadata: { - name: 'mock', - }, - }; - - const secondMockEntity: Entity = { - apiVersion: '1', - kind: 'Component', - metadata: { - namespace: 'test', - name: 'mock2', - }, - }; - - const wrapper = ({ children }: PropsWithChildren<{}>) => { - return ( - - {children} - - ); - }; - - beforeEach(() => { - mockStorage = new WebStorage('@backstage', new MockErrorApi()).forBucket( - Date.now().toString(), // TODO(blam): need something that changes every test run for now until the MockStorage is implemented - ); - }); - it('should return an empty set for when there is no items in storage', async () => { - const { result } = renderHook(() => useStarredEntities(), { wrapper }); - - expect(result.current.starredEntities.size).toBe(0); - }); - it('should return a set with the current items when there is items in storage', async () => { - const expectedIds = ['i', 'am', 'some', 'test', 'ids']; - const store = mockStorage?.forBucket('settings'); - await store?.set('starredEntities', expectedIds); - - const { result } = renderHook(() => useStarredEntities(), { wrapper }); - - for (const item of expectedIds) { - expect(result.current.starredEntities.has(item)).toBeTruthy(); - } - }); - it('should listen to changes when the storage is set elsewhere', async () => { - const { result, waitForNextUpdate } = renderHook( - () => useStarredEntities(), - { wrapper }, - ); - - expect(result.current.starredEntities.size).toBe(0); - expect(result.current.isStarredEntity(mockEntity)).toBeFalsy(); - - // Make this happen after awaiting for the next update so we can - // catch when the hook re-renders with the latest data - setTimeout(() => result.current.toggleStarredEntity(mockEntity), 1); - - await waitForNextUpdate(); - - expect(result.current.starredEntities.size).toBe(1); - expect(result.current.isStarredEntity(mockEntity)).toBeTruthy(); - }); - - it('should write new entries to the local store when adding a togglging entity', async () => { - const { result } = renderHook(() => useStarredEntities(), { wrapper }); - - act(() => { - result.current.toggleStarredEntity(mockEntity); - }); - - expect(result.current.isStarredEntity(mockEntity)).toBeTruthy(); - expect(result.current.isStarredEntity(secondMockEntity)).toBeFalsy(); - }); - - it('should remove an existing entity when toggling entries', async () => { - const { result } = renderHook(() => useStarredEntities(), { wrapper }); - - act(() => { - result.current.toggleStarredEntity(mockEntity); - result.current.toggleStarredEntity(secondMockEntity); - result.current.toggleStarredEntity(mockEntity); - }); - - expect(result.current.isStarredEntity(mockEntity)).toBeFalsy(); - expect(result.current.isStarredEntity(secondMockEntity)).toBeTruthy(); - }); -}); diff --git a/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.tsx b/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.tsx index 19c88a87f7..301c6b01d8 100644 --- a/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.tsx +++ b/plugins/scaffolder/src/components/ResultsFilter/ResultsFilter.tsx @@ -25,7 +25,7 @@ import { Theme, Typography, } from '@material-ui/core'; -import React, { useCallback, useContext, useState } from 'react'; +import React, { useContext } from 'react'; import { filterGroupsContext } from '../../filter/context'; const useStyles = makeStyles(theme => ({ @@ -59,20 +59,12 @@ type Props = { export const ResultsFilter = ({ availableCategories }: Props) => { const classes = useStyles(); - const [selectedCategories, setSelectedCategories] = useState([]); const context = useContext(filterGroupsContext); if (!context) { throw new Error(`Must be used inside an EntityFilterGroupsProvider`); } - const setSelectedCatgoriesFilter = context?.setSelectedCategories; - const updateSelectedCategories = useCallback( - (categories: string[]) => { - setSelectedCategories(categories); - setSelectedCatgoriesFilter(categories); - }, - [setSelectedCategories, setSelectedCatgoriesFilter], - ); + const { selectedCategories, setSelectedCategories } = context; return ( <> @@ -80,7 +72,7 @@ export const ResultsFilter = ({ availableCategories }: Props) => { Refine Results {' '} - +
@@ -95,7 +87,7 @@ export const ResultsFilter = ({ availableCategories }: Props) => { dense button onClick={() => - updateSelectedCategories( + setSelectedCategories( selectedCategories.includes(category) ? selectedCategories.filter( selectedCategory => selectedCategory !== category, diff --git a/plugins/scaffolder/src/components/ScaffolderFilter/AllServicesCount.tsx b/plugins/scaffolder/src/components/ScaffolderFilter/AllServicesCount.tsx deleted file mode 100644 index efacfa4320..0000000000 --- a/plugins/scaffolder/src/components/ScaffolderFilter/AllServicesCount.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 { useApi } from '@backstage/core'; -import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import { CircularProgress, useTheme } from '@material-ui/core'; -import React from 'react'; -import { useAsync } from 'react-use'; - -export const AllServicesCount = () => { - const theme = useTheme(); - const catalogApi = useApi(catalogApiRef); - const { value, loading } = useAsync(() => catalogApi.getEntities()); - - if (loading) { - return ; - } - - return {value ?? length ?? '-'}; -}; diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index 74582d0762..2a5f3309a0 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -15,6 +15,7 @@ */ import React, { useEffect, useMemo, useState } from 'react'; +import { Link as RouterLink } from 'react-router-dom'; import { EntityMeta, TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { configApiRef, @@ -28,15 +29,14 @@ import { useApi, WarningPanel, } from '@backstage/core'; +import { useStarredEntities } from '@backstage/plugin-catalog-react'; import { Button, Grid, Link, makeStyles, Typography } from '@material-ui/core'; -import { Link as RouterLink } from 'react-router-dom'; +import StarIcon from '@material-ui/icons/Star'; import { EntityFilterGroupsProvider, useFilteredEntities } from '../../filter'; import { TemplateCard, TemplateCardProps } from '../TemplateCard'; import { ResultsFilter } from '../ResultsFilter/ResultsFilter'; import { ScaffolderFilter } from '../ScaffolderFilter'; import { ButtonGroup } from '../ScaffolderFilter/ScaffolderFilter'; -import StarIcon from '@material-ui/icons/Star'; -import { useStarredEntities } from '../../hooks/useStarredEntities'; import SearchToolbar from '../SearchToolbar/SearchToolbar'; const useStyles = makeStyles(theme => ({ @@ -104,7 +104,7 @@ export const ScaffolderPageContents = () => { ); const matchesQuery = (metadata: EntityMeta, query: string) => - `${metadata.title}`.toUpperCase().indexOf(query) !== -1 || + `${metadata.title}`.toUpperCase().includes(query) || metadata.tags?.join('').toUpperCase().indexOf(query) !== -1; useEffect(() => { diff --git a/plugins/scaffolder/src/filter/EntityFilterGroupsProvider.tsx b/plugins/scaffolder/src/filter/EntityFilterGroupsProvider.tsx index ba99a0294a..9e11525347 100644 --- a/plugins/scaffolder/src/filter/EntityFilterGroupsProvider.tsx +++ b/plugins/scaffolder/src/filter/EntityFilterGroupsProvider.tsx @@ -145,6 +145,7 @@ function useProvideEntityFilters(): FilterGroupsContext { setGroupSelectedFilters, setSelectedCategories, reload, + selectedCategories: selectedCategories.current, loading: !error && !entities, error, filterGroupStates, diff --git a/plugins/scaffolder/src/filter/context.ts b/plugins/scaffolder/src/filter/context.ts index f0a9d3755e..a7819be752 100644 --- a/plugins/scaffolder/src/filter/context.ts +++ b/plugins/scaffolder/src/filter/context.ts @@ -28,6 +28,7 @@ export type FilterGroupsContext = { setGroupSelectedFilters: (filterGroupId: string, filterIds: string[]) => void; setSelectedCategories: (categories: string[]) => void; reload: () => Promise; + selectedCategories: string[]; loading: boolean; error?: Error; filterGroupStates: { [filterGroupId: string]: FilterGroupStates }; diff --git a/plugins/scaffolder/src/hooks/useStarredEntities.ts b/plugins/scaffolder/src/hooks/useStarredEntities.ts deleted file mode 100644 index 7cbbbb7ce6..0000000000 --- a/plugins/scaffolder/src/hooks/useStarredEntities.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 '@backstage/catalog-model'; -import { storageApiRef, useApi } from '@backstage/core'; -import { useCallback, useEffect, useState } from 'react'; -import { useObservable } from 'react-use'; - -const buildEntityKey = (component: Entity) => - `entity:${component.kind}:${component.metadata.namespace ?? 'default'}:${ - component.metadata.name - }`; - -export const useStarredEntities = () => { - const storageApi = useApi(storageApiRef); - const settingsStore = storageApi.forBucket('settings'); - const rawStarredEntityKeys = - settingsStore.get('starredEntities') ?? []; - - const [starredEntities, setStarredEntities] = useState( - new Set(rawStarredEntityKeys), - ); - - const observedItems = useObservable( - settingsStore.observe$('starredEntities'), - ); - - useEffect(() => { - if (observedItems?.newValue) { - const currentValue = observedItems?.newValue ?? []; - setStarredEntities(new Set(currentValue)); - } - }, [observedItems?.newValue]); - - const toggleStarredEntity = useCallback( - (entity: Entity) => { - const entityKey = buildEntityKey(entity); - if (starredEntities.has(entityKey)) { - starredEntities.delete(entityKey); - } else { - starredEntities.add(entityKey); - } - - settingsStore.set('starredEntities', Array.from(starredEntities)); - }, - [starredEntities, settingsStore], - ); - - const isStarredEntity = useCallback( - (entity: Entity) => { - const entityKey = buildEntityKey(entity); - return starredEntities.has(entityKey); - }, - [starredEntities], - ); - - return { - starredEntities, - toggleStarredEntity, - isStarredEntity, - }; -}; From 272d96055077f1cee2420a35d61a6df2b32a8eb3 Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Fri, 19 Feb 2021 17:05:18 -0600 Subject: [PATCH 198/270] load k8s info with only label selector query [Docs](https://backstage.io/docs/features/kubernetes/configuration#surfacing-your-kubernetes-components-as-part-of-an-entity) say `label selector takes precedence over the annotation/service id.` but ``` annotations: 'backstage.io/kubernetes-id': dice-roller ``` is always required. updated logic to allow for only label selector (without backstage.io/kubernetes-id) --- plugins/kubernetes/src/Router.tsx | 28 ++++++++++++++++++---------- yarn.lock | 3 +-- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/plugins/kubernetes/src/Router.tsx b/plugins/kubernetes/src/Router.tsx index 36851b3c46..5197e4811d 100644 --- a/plugins/kubernetes/src/Router.tsx +++ b/plugins/kubernetes/src/Router.tsx @@ -23,6 +23,8 @@ import { KubernetesContent } from './components/KubernetesContent'; import { MissingAnnotationEmptyState } from '@backstage/core'; const KUBERNETES_ANNOTATION = 'backstage.io/kubernetes-id'; +const KUBERNETES_LABEL_SELECTOR_QUERY_ANNOTATION = + 'backstage.io/kubernetes-label-selector'; type Props = { /** @deprecated The entity is now grabbed from context instead */ @@ -35,16 +37,22 @@ export const Router = (_props: Props) => { const kubernetesAnnotationValue = entity.metadata.annotations?.[KUBERNETES_ANNOTATION]; - if (!kubernetesAnnotationValue) { - return ; + const kubernetesLabelSelectorQueryAnnotationValue = + entity.metadata.annotations?.[KUBERNETES_LABEL_SELECTOR_QUERY_ANNOTATION]; + + if ( + kubernetesAnnotationValue || + kubernetesLabelSelectorQueryAnnotationValue + ) { + return ( + + } + /> + + ); } - return ( - - } - /> - - ); + return ; }; diff --git a/yarn.lock b/yarn.lock index c32766faa3..90e7e8bb14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1874,7 +1874,6 @@ "@backstage/catalog-model" "^0.7.1" "@backstage/core" "^0.6.2" "@backstage/plugin-catalog-react" "^0.0.4" - "@backstage/plugin-scaffolder" "^0.5.1" "@backstage/theme" "^0.2.3" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" @@ -18238,7 +18237,7 @@ modify-values@^1.0.0: resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== -moment@^2.19.3, moment@^2.25.3, moment@^2.26.0, moment@^2.27.0: +moment@^2.19.3, moment@^2.25.3, moment@^2.27.0: version "2.29.1" resolved "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== From ba21797ca58b18678b155c4f7e0c8b12685e91de Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Fri, 19 Feb 2021 17:10:39 -0600 Subject: [PATCH 199/270] add changeset --- .changeset/afraid-weeks-sort.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/afraid-weeks-sort.md diff --git a/.changeset/afraid-weeks-sort.md b/.changeset/afraid-weeks-sort.md new file mode 100644 index 0000000000..c5ec608daf --- /dev/null +++ b/.changeset/afraid-weeks-sort.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-kubernetes': patch +--- + +k8s plugin now surfaces k8s components with only label selector query annotation. +Previously backstage.io/kubernetes-label-selector catalog entity annotation would only work if you also included backstage.io/kubernetes-id. +But backstage.io/kubernetes-id value was ignored From 0fc2a84352930da4c2166dc80536e7792767bb6c Mon Sep 17 00:00:00 2001 From: James Turley Date: Sat, 13 Feb 2021 11:37:41 +0000 Subject: [PATCH 200/270] Update CatalogTable actions to support custom URLs --- .../src/components/AboutCard/AboutCard.tsx | 2 +- .../CatalogTable/CatalogTable.test.tsx | 67 +++++++++++++++++++ .../components/CatalogTable/CatalogTable.tsx | 13 ++-- .../{createEditLink.ts => actions.ts} | 24 ++++++- yarn.lock | 3 +- 5 files changed, 99 insertions(+), 10 deletions(-) rename plugins/catalog/src/components/{createEditLink.ts => actions.ts} (80%) diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index 2d6cd6bca8..0f2d55295b 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -35,7 +35,7 @@ import ExtensionIcon from '@material-ui/icons/Extension'; import GitHubIcon from '@material-ui/icons/GitHub'; import React from 'react'; import { findLocationForEntityMeta } from '../../data/utils'; -import { createEditLink, determineUrlType } from '../createEditLink'; +import { createEditLink, determineUrlType } from '../actions'; import { AboutContent } from './AboutContent'; const useStyles = makeStyles({ diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx index 16c91c0d6c..5cb776746d 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx @@ -15,6 +15,7 @@ */ import { Entity } from '@backstage/catalog-model'; +import { act, fireEvent } from '@testing-library/react'; import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import * as React from 'react'; import { CatalogTable } from './CatalogTable'; @@ -38,6 +39,14 @@ const entities: Entity[] = [ ]; describe('CatalogTable component', () => { + beforeEach(() => { + window.open = jest.fn(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + it('should render error message when error is passed in props', async () => { const rendered = await renderWithEffects( wrapInTestApp( @@ -70,4 +79,62 @@ describe('CatalogTable component', () => { expect(rendered.getByText(/component2/)).toBeInTheDocument(); expect(rendered.getByText(/component3/)).toBeInTheDocument(); }); + + it('should use specified edit URL if in annotation', async () => { + const entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'component1', + annotations: { 'backstage.io/browser-edit-url': 'https://other.place' }, + }, + }; + + const { getByTitle } = await renderWithEffects( + wrapInTestApp( + , + ), + ); + + const editButton = getByTitle('Edit'); + + await act(async () => { + fireEvent.click(editButton); + }); + + expect(window.open).toHaveBeenCalledWith('https://other.place', '_blank'); + }); + + it('should use specified view URL if in annotation', async () => { + const entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'component1', + annotations: { 'backstage.io/browser-view-url': 'https://other.place' }, + }, + }; + + const { getByTitle } = await renderWithEffects( + wrapInTestApp( + , + ), + ); + + const viewButton = getByTitle('View'); + + await act(async () => { + fireEvent.click(viewButton); + }); + + expect(window.open).toHaveBeenCalledWith('https://other.place', '_blank'); + }); }); diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 12d97e91a6..ca40648ecb 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -34,13 +34,12 @@ import { getEntityRelations, useStarredEntities, } from '@backstage/plugin-catalog-react'; - import { Chip } from '@material-ui/core'; import Edit from '@material-ui/icons/Edit'; import OpenInNew from '@material-ui/icons/OpenInNew'; import React from 'react'; import { findLocationForEntityMeta } from '../../data/utils'; -import { createEditLink } from '../createEditLink'; +import { findViewUrl, findEditUrl } from '../actions'; import { favouriteEntityIcon, favouriteEntityTooltip, @@ -154,23 +153,25 @@ export const CatalogTable = ({ const actions: TableProps['actions'] = [ ({ entity }) => { const location = findLocationForEntityMeta(entity.metadata); + const url = findViewUrl(entity, location); return { icon: () => , tooltip: 'View', onClick: () => { - if (!location) return; - window.open(location.target, '_blank'); + if (!url) return; + window.open(url, '_blank'); }, }; }, ({ entity }) => { const location = findLocationForEntityMeta(entity.metadata); + const url = findEditUrl(entity, location); return { icon: () => , tooltip: 'Edit', onClick: () => { - if (!location) return; - window.open(createEditLink(location), '_blank'); + if (!url) return; + window.open(url, '_blank'); }, }; }, diff --git a/plugins/catalog/src/components/createEditLink.ts b/plugins/catalog/src/components/actions.ts similarity index 80% rename from plugins/catalog/src/components/createEditLink.ts rename to plugins/catalog/src/components/actions.ts index 57fc876704..10ee9f38a4 100644 --- a/plugins/catalog/src/components/createEditLink.ts +++ b/plugins/catalog/src/components/actions.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { LocationSpec } from '@backstage/catalog-model'; +import { LocationSpec, Entity } from '@backstage/catalog-model'; import parseGitUrl from 'git-url-parse'; /** @@ -75,3 +75,25 @@ export const determineUrlType = (url: string): string => { } return 'url'; }; + +export const findEditUrl = ( + { metadata }: Entity, + location?: LocationSpec, +): string | undefined => { + const annotations = metadata.annotations || {}; + + const editUrl = annotations['backstage.io/browser-edit-url']; + + if (editUrl) return editUrl; + + return location && createEditLink(location); +}; + +export const findViewUrl = ( + { metadata }: Entity, + location?: LocationSpec, +): string | undefined => { + const annotations = metadata.annotations || {}; + + return annotations['backstage.io/browser-view-url'] || location?.target; +}; diff --git a/yarn.lock b/yarn.lock index c32766faa3..90e7e8bb14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1874,7 +1874,6 @@ "@backstage/catalog-model" "^0.7.1" "@backstage/core" "^0.6.2" "@backstage/plugin-catalog-react" "^0.0.4" - "@backstage/plugin-scaffolder" "^0.5.1" "@backstage/theme" "^0.2.3" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" @@ -18238,7 +18237,7 @@ modify-values@^1.0.0: resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== -moment@^2.19.3, moment@^2.25.3, moment@^2.26.0, moment@^2.27.0: +moment@^2.19.3, moment@^2.25.3, moment@^2.27.0: version "2.29.1" resolved "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== From a51094aac81785c077c7a1f778618afbb5a67986 Mon Sep 17 00:00:00 2001 From: James Turley Date: Tue, 16 Feb 2021 15:40:32 +0000 Subject: [PATCH 201/270] Allow custom edit and view source links on AboutCard --- .../components/AboutCard/AboutCard.test.tsx | 38 +++++++++++++++++++ .../src/components/AboutCard/AboutCard.tsx | 17 +++++---- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index ada827115d..dfca528716 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -123,3 +123,41 @@ describe(' BitBucket', () => { ); }); }); + +describe(' custom links', () => { + it('renders info and "view source" link', () => { + const entity = { + apiVersion: 'v1', + kind: 'Component', + metadata: { + name: 'software', + annotations: { + 'backstage.io/managed-by-location': + 'bitbucket:https://bitbucket.org/backstage/backstage/src/master/software.yaml', + 'backstage.io/browser-edit-url': 'https://another.place', + 'backstage.io/browser-source-url': + 'https://another.place/backstage.git', + }, + }, + spec: { + owner: 'guest', + type: 'service', + lifecycle: 'production', + }, + }; + const { getByText } = render( + + + , + ); + expect(getByText('service')).toBeInTheDocument(); + expect(getByText('View Source').closest('a')).toHaveAttribute( + 'href', + 'https://another.place/backstage.git', + ); + expect(getByText('View Source').closest('a')).toHaveAttribute( + 'edithref', + 'https://another.place', + ); + }); +}); diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index 0f2d55295b..ee42281d31 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -35,7 +35,7 @@ import ExtensionIcon from '@material-ui/icons/Extension'; import GitHubIcon from '@material-ui/icons/GitHub'; import React from 'react'; import { findLocationForEntityMeta } from '../../data/utils'; -import { createEditLink, determineUrlType } from '../actions'; +import { findEditUrl, determineUrlType } from '../actions'; import { AboutContent } from './AboutContent'; const useStyles = makeStyles({ @@ -61,19 +61,22 @@ type CodeLinkInfo = { }; function getCodeLinkInfo(entity: Entity): CodeLinkInfo { + let sourceUrl = + entity.metadata?.annotations?.['backstage.io/browser-source-url']; const location = findLocationForEntityMeta(entity?.metadata); + const editUrl = findEditUrl(entity, location); + if (location) { + sourceUrl = sourceUrl || location.target; const type = - location.type === 'url' - ? determineUrlType(location.target) - : location.type; + location.type === 'url' ? determineUrlType(sourceUrl) : location.type; return { + edithref: editUrl, icon: iconMap[type], - edithref: createEditLink(location), - href: location.target, + href: sourceUrl, }; } - return {}; + return { edithref: editUrl, href: sourceUrl }; } type AboutCardProps = { From a4f863f8f859b19e4010a5833e1883fb8bbbdb62 Mon Sep 17 00:00:00 2001 From: James Turley Date: Thu, 18 Feb 2021 14:27:09 +0000 Subject: [PATCH 202/270] Document browser URL annotations --- .github/styles/vocab.txt | 1 + .../well-known-annotations.md | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 41ec95e9aa..2a8854b8a2 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -166,6 +166,7 @@ interop jq js json +jsonnet jsx kubectl kubernetes diff --git a/docs/features/software-catalog/well-known-annotations.md b/docs/features/software-catalog/well-known-annotations.md index 2cbb829554..795bf15621 100644 --- a/docs/features/software-catalog/well-known-annotations.md +++ b/docs/features/software-catalog/well-known-annotations.md @@ -70,6 +70,25 @@ The value of this annotation is a location reference string (see above). If this annotation is specified, it is expected to point to a repository that the TechDocs system can read and generate docs from. +### backstage.io/browser-view-url, backstage.io/browser-edit-url, backstage.io/browser-source-url + +```yaml +# Example: +metadata: + annotations: + backstage.io/browser-view-url: https://some.website/catalog-info.yaml + backstage.io/browser-edit-url: https://github.com/my-org/catalog/edit/master/my-service.jsonnet + backstage.io/browser-source-url: https://github.com/my-org/my-service +``` + +These annotations allow customising links from the catalog pages. The view URL +should point to the canonical metadata YAML that governs this entity. The edit +URL should point to the source file for the metadata. The source URL should +point to the source code of the entity itself (where relevant, i.e. when the +entity is a `Component`). In the example above, `my-org` generates its catalog +data from Jsonnet files in a monorepo, and so for the `my-service` component, we +need three custom links. + ### jenkins.io/github-folder ```yaml From e337085d3840d3d9c5e2e9934893ceade3e6fa6f Mon Sep 17 00:00:00 2001 From: James Turley Date: Fri, 19 Feb 2021 12:17:35 +0000 Subject: [PATCH 203/270] Use proper location ref for custom source location --- .../well-known-annotations.md | 24 +++++++++++----- .../catalog-model/src/location/annotation.ts | 2 ++ packages/catalog-model/src/location/index.ts | 6 +++- .../components/AboutCard/AboutCard.test.tsx | 5 ++-- .../src/components/AboutCard/AboutCard.tsx | 28 ++++++++++++++----- plugins/catalog/src/data/utils.ts | 10 +++++-- 6 files changed, 55 insertions(+), 20 deletions(-) diff --git a/docs/features/software-catalog/well-known-annotations.md b/docs/features/software-catalog/well-known-annotations.md index 795bf15621..3c8f836aed 100644 --- a/docs/features/software-catalog/well-known-annotations.md +++ b/docs/features/software-catalog/well-known-annotations.md @@ -70,7 +70,7 @@ The value of this annotation is a location reference string (see above). If this annotation is specified, it is expected to point to a repository that the TechDocs system can read and generate docs from. -### backstage.io/browser-view-url, backstage.io/browser-edit-url, backstage.io/browser-source-url +### backstage.io/browser-view-url, backstage.io/browser-edit-url ```yaml # Example: @@ -78,16 +78,26 @@ metadata: annotations: backstage.io/browser-view-url: https://some.website/catalog-info.yaml backstage.io/browser-edit-url: https://github.com/my-org/catalog/edit/master/my-service.jsonnet - backstage.io/browser-source-url: https://github.com/my-org/my-service ``` These annotations allow customising links from the catalog pages. The view URL should point to the canonical metadata YAML that governs this entity. The edit -URL should point to the source file for the metadata. The source URL should -point to the source code of the entity itself (where relevant, i.e. when the -entity is a `Component`). In the example above, `my-org` generates its catalog -data from Jsonnet files in a monorepo, and so for the `my-service` component, we -need three custom links. +URL should point to the source file for the metadata. In the example above, +`my-org` generates its catalog data from Jsonnet files in a monorepo, so the +view and edit links need changing. + +### backstage.io/source-location + +```yaml +# Example: +metadata: + annotations: + backstage.io/source-location: github:https://github.com/my-org/my-service +``` + +A `Location` reference that points to the source code of the entity (typically a +`Component`). Useful when catalog files do not get ingested from the source code +repository itself. ### jenkins.io/github-folder diff --git a/packages/catalog-model/src/location/annotation.ts b/packages/catalog-model/src/location/annotation.ts index 93f2fabea4..ba875c3edf 100644 --- a/packages/catalog-model/src/location/annotation.ts +++ b/packages/catalog-model/src/location/annotation.ts @@ -17,3 +17,5 @@ export const LOCATION_ANNOTATION = 'backstage.io/managed-by-location'; export const ORIGIN_LOCATION_ANNOTATION = 'backstage.io/managed-by-origin-location'; + +export const SOURCE_LOCATION_ANNOTATION = 'backstage.io/source-location'; diff --git a/packages/catalog-model/src/location/index.ts b/packages/catalog-model/src/location/index.ts index 8fd516120a..6cfb074613 100644 --- a/packages/catalog-model/src/location/index.ts +++ b/packages/catalog-model/src/location/index.ts @@ -20,4 +20,8 @@ export { locationSpecSchema, analyzeLocationSchema, } from './validation'; -export { LOCATION_ANNOTATION, ORIGIN_LOCATION_ANNOTATION } from './annotation'; +export { + LOCATION_ANNOTATION, + ORIGIN_LOCATION_ANNOTATION, + SOURCE_LOCATION_ANNOTATION, +} from './annotation'; diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index dfca528716..e99ec056d2 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -15,6 +15,7 @@ */ import { EntityProvider } from '@backstage/plugin-catalog-react'; +import { SOURCE_LOCATION_ANNOTATION } from '@backstage/catalog-model'; import { render } from '@testing-library/react'; import React from 'react'; import { AboutCard } from './AboutCard'; @@ -135,8 +136,8 @@ describe(' custom links', () => { 'backstage.io/managed-by-location': 'bitbucket:https://bitbucket.org/backstage/backstage/src/master/software.yaml', 'backstage.io/browser-edit-url': 'https://another.place', - 'backstage.io/browser-source-url': - 'https://another.place/backstage.git', + [SOURCE_LOCATION_ANNOTATION]: + 'url:https://another.place/backstage.git', }, }, spec: { diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index ee42281d31..6a13d78d14 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -16,7 +16,9 @@ import { Entity, + LocationSpec, ENTITY_DEFAULT_NAMESPACE, + SOURCE_LOCATION_ANNOTATION, RELATION_PROVIDES_API, } from '@backstage/catalog-model'; import { HeaderIconLinkRow } from '@backstage/core'; @@ -34,7 +36,7 @@ import EditIcon from '@material-ui/icons/Edit'; import ExtensionIcon from '@material-ui/icons/Extension'; import GitHubIcon from '@material-ui/icons/GitHub'; import React from 'react'; -import { findLocationForEntityMeta } from '../../data/utils'; +import { findLocationForEntityMeta, parseLocation } from '../../data/utils'; import { findEditUrl, determineUrlType } from '../actions'; import { AboutContent } from './AboutContent'; @@ -60,23 +62,35 @@ type CodeLinkInfo = { href?: string; }; +function getSourceLocationForEntity( + entity: Entity, + location?: LocationSpec, +): LocationSpec | undefined { + const annotation = entity.metadata?.annotations?.[SOURCE_LOCATION_ANNOTATION]; + const parsed = annotation && parseLocation(annotation); + + return parsed || location; +} + function getCodeLinkInfo(entity: Entity): CodeLinkInfo { - let sourceUrl = - entity.metadata?.annotations?.['backstage.io/browser-source-url']; const location = findLocationForEntityMeta(entity?.metadata); const editUrl = findEditUrl(entity, location); + let sourceLocation = getSourceLocationForEntity(entity, location); if (location) { - sourceUrl = sourceUrl || location.target; + sourceLocation = sourceLocation || location; const type = - location.type === 'url' ? determineUrlType(sourceUrl) : location.type; + sourceLocation.type === 'url' + ? determineUrlType(sourceLocation.target) + : sourceLocation.type; return { edithref: editUrl, icon: iconMap[type], - href: sourceUrl, + href: sourceLocation.target, }; } - return { edithref: editUrl, href: sourceUrl }; + + return { edithref: editUrl, href: sourceLocation?.target }; } type AboutCardProps = { diff --git a/plugins/catalog/src/data/utils.ts b/plugins/catalog/src/data/utils.ts index df14875092..ec63d94754 100644 --- a/plugins/catalog/src/data/utils.ts +++ b/plugins/catalog/src/data/utils.ts @@ -32,13 +32,17 @@ export function findLocationForEntityMeta( return undefined; } - const separatorIndex = annotation.indexOf(':'); + return parseLocation(annotation); +} + +export function parseLocation(reference: string): LocationSpec | undefined { + const separatorIndex = reference.indexOf(':'); if (separatorIndex === -1) { return undefined; } return { - type: annotation.substring(0, separatorIndex), - target: annotation.substring(separatorIndex + 1), + type: reference.substring(0, separatorIndex), + target: reference.substring(separatorIndex + 1), }; } From 9c521c53a7e8a44bedc3561ff3e62ab55c80b275 Mon Sep 17 00:00:00 2001 From: James Turley Date: Sat, 20 Feb 2021 11:07:08 +0000 Subject: [PATCH 204/270] Rename view/edit annotations --- docs/features/software-catalog/well-known-annotations.md | 6 +++--- plugins/catalog/src/components/AboutCard/AboutCard.test.tsx | 2 +- .../src/components/CatalogTable/CatalogTable.test.tsx | 4 ++-- plugins/catalog/src/components/actions.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/features/software-catalog/well-known-annotations.md b/docs/features/software-catalog/well-known-annotations.md index 3c8f836aed..8ec4813799 100644 --- a/docs/features/software-catalog/well-known-annotations.md +++ b/docs/features/software-catalog/well-known-annotations.md @@ -70,14 +70,14 @@ The value of this annotation is a location reference string (see above). If this annotation is specified, it is expected to point to a repository that the TechDocs system can read and generate docs from. -### backstage.io/browser-view-url, backstage.io/browser-edit-url +### backstage.io/view-url, backstage.io/edit-url ```yaml # Example: metadata: annotations: - backstage.io/browser-view-url: https://some.website/catalog-info.yaml - backstage.io/browser-edit-url: https://github.com/my-org/catalog/edit/master/my-service.jsonnet + backstage.io/view-url: https://some.website/catalog-info.yaml + backstage.io/edit-url: https://github.com/my-org/catalog/edit/master/my-service.jsonnet ``` These annotations allow customising links from the catalog pages. The view URL diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index e99ec056d2..393f3f2576 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -135,7 +135,7 @@ describe(' custom links', () => { annotations: { 'backstage.io/managed-by-location': 'bitbucket:https://bitbucket.org/backstage/backstage/src/master/software.yaml', - 'backstage.io/browser-edit-url': 'https://another.place', + 'backstage.io/edit-url': 'https://another.place', [SOURCE_LOCATION_ANNOTATION]: 'url:https://another.place/backstage.git', }, diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx index 5cb776746d..b77dd2b8a0 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx @@ -86,7 +86,7 @@ describe('CatalogTable component', () => { kind: 'Component', metadata: { name: 'component1', - annotations: { 'backstage.io/browser-edit-url': 'https://other.place' }, + annotations: { 'backstage.io/edit-url': 'https://other.place' }, }, }; @@ -115,7 +115,7 @@ describe('CatalogTable component', () => { kind: 'Component', metadata: { name: 'component1', - annotations: { 'backstage.io/browser-view-url': 'https://other.place' }, + annotations: { 'backstage.io/view-url': 'https://other.place' }, }, }; diff --git a/plugins/catalog/src/components/actions.ts b/plugins/catalog/src/components/actions.ts index 10ee9f38a4..c7fbfac8e2 100644 --- a/plugins/catalog/src/components/actions.ts +++ b/plugins/catalog/src/components/actions.ts @@ -82,7 +82,7 @@ export const findEditUrl = ( ): string | undefined => { const annotations = metadata.annotations || {}; - const editUrl = annotations['backstage.io/browser-edit-url']; + const editUrl = annotations['backstage.io/edit-url']; if (editUrl) return editUrl; @@ -95,5 +95,5 @@ export const findViewUrl = ( ): string | undefined => { const annotations = metadata.annotations || {}; - return annotations['backstage.io/browser-view-url'] || location?.target; + return annotations['backstage.io/view-url'] || location?.target; }; From dcdb9f06518e481a09a4e396ed9c0320b5224199 Mon Sep 17 00:00:00 2001 From: James Turley Date: Sat, 20 Feb 2021 11:26:37 +0000 Subject: [PATCH 205/270] Extract view/edit annotations to constants --- packages/catalog-model/src/entity/constants.ts | 6 ++++++ packages/catalog-model/src/entity/index.ts | 2 ++ .../src/components/AboutCard/AboutCard.test.tsx | 7 +++++-- .../src/components/CatalogTable/CatalogTable.test.tsx | 10 +++++++--- plugins/catalog/src/components/actions.ts | 11 ++++++++--- 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/catalog-model/src/entity/constants.ts b/packages/catalog-model/src/entity/constants.ts index 42ea2ae8ba..c8f88e3b0c 100644 --- a/packages/catalog-model/src/entity/constants.ts +++ b/packages/catalog-model/src/entity/constants.ts @@ -27,3 +27,9 @@ export const ENTITY_META_GENERATED_FIELDS = [ 'etag', 'generation', ] as const; + +/** + * Annotations for linking to entity from catalog pages. + */ +export const VIEW_URL_ANNOTATION = 'backstage.io/view-url'; +export const EDIT_URL_ANNOTATION = 'backstage.io/edit-url'; diff --git a/packages/catalog-model/src/entity/index.ts b/packages/catalog-model/src/entity/index.ts index e80e14f7a4..e267c607e1 100644 --- a/packages/catalog-model/src/entity/index.ts +++ b/packages/catalog-model/src/entity/index.ts @@ -17,6 +17,8 @@ export { ENTITY_DEFAULT_NAMESPACE, ENTITY_META_GENERATED_FIELDS, + VIEW_URL_ANNOTATION, + EDIT_URL_ANNOTATION, } from './constants'; export type { Entity, diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index 393f3f2576..449d3c1fc9 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -15,7 +15,10 @@ */ import { EntityProvider } from '@backstage/plugin-catalog-react'; -import { SOURCE_LOCATION_ANNOTATION } from '@backstage/catalog-model'; +import { + SOURCE_LOCATION_ANNOTATION, + EDIT_URL_ANNOTATION, +} from '@backstage/catalog-model'; import { render } from '@testing-library/react'; import React from 'react'; import { AboutCard } from './AboutCard'; @@ -135,7 +138,7 @@ describe(' custom links', () => { annotations: { 'backstage.io/managed-by-location': 'bitbucket:https://bitbucket.org/backstage/backstage/src/master/software.yaml', - 'backstage.io/edit-url': 'https://another.place', + [EDIT_URL_ANNOTATION]: 'https://another.place', [SOURCE_LOCATION_ANNOTATION]: 'url:https://another.place/backstage.git', }, diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx index b77dd2b8a0..ffd5f3d190 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx @@ -14,7 +14,11 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { + Entity, + VIEW_URL_ANNOTATION, + EDIT_URL_ANNOTATION, +} from '@backstage/catalog-model'; import { act, fireEvent } from '@testing-library/react'; import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import * as React from 'react'; @@ -86,7 +90,7 @@ describe('CatalogTable component', () => { kind: 'Component', metadata: { name: 'component1', - annotations: { 'backstage.io/edit-url': 'https://other.place' }, + annotations: { [EDIT_URL_ANNOTATION]: 'https://other.place' }, }, }; @@ -115,7 +119,7 @@ describe('CatalogTable component', () => { kind: 'Component', metadata: { name: 'component1', - annotations: { 'backstage.io/view-url': 'https://other.place' }, + annotations: { [VIEW_URL_ANNOTATION]: 'https://other.place' }, }, }; diff --git a/plugins/catalog/src/components/actions.ts b/plugins/catalog/src/components/actions.ts index c7fbfac8e2..13e235d46e 100644 --- a/plugins/catalog/src/components/actions.ts +++ b/plugins/catalog/src/components/actions.ts @@ -14,7 +14,12 @@ * limitations under the License. */ -import { LocationSpec, Entity } from '@backstage/catalog-model'; +import { + LocationSpec, + Entity, + EDIT_URL_ANNOTATION, + VIEW_URL_ANNOTATION, +} from '@backstage/catalog-model'; import parseGitUrl from 'git-url-parse'; /** @@ -82,7 +87,7 @@ export const findEditUrl = ( ): string | undefined => { const annotations = metadata.annotations || {}; - const editUrl = annotations['backstage.io/edit-url']; + const editUrl = annotations[EDIT_URL_ANNOTATION]; if (editUrl) return editUrl; @@ -95,5 +100,5 @@ export const findViewUrl = ( ): string | undefined => { const annotations = metadata.annotations || {}; - return annotations['backstage.io/view-url'] || location?.target; + return annotations[VIEW_URL_ANNOTATION] || location?.target; }; From 302fd47cac2227926d7481c25f6f1ce0854e7f3f Mon Sep 17 00:00:00 2001 From: James Turley Date: Sat, 20 Feb 2021 11:36:56 +0000 Subject: [PATCH 206/270] Simplify URL lookup functions --- .../catalog/src/components/AboutCard/AboutCard.tsx | 2 +- .../src/components/CatalogTable/CatalogTable.tsx | 7 ++----- plugins/catalog/src/components/actions.ts | 14 ++++++-------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index 6a13d78d14..64564f5ec4 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -74,7 +74,7 @@ function getSourceLocationForEntity( function getCodeLinkInfo(entity: Entity): CodeLinkInfo { const location = findLocationForEntityMeta(entity?.metadata); - const editUrl = findEditUrl(entity, location); + const editUrl = findEditUrl(entity); let sourceLocation = getSourceLocationForEntity(entity, location); if (location) { diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index ca40648ecb..7d693d1bc8 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -38,7 +38,6 @@ import { Chip } from '@material-ui/core'; import Edit from '@material-ui/icons/Edit'; import OpenInNew from '@material-ui/icons/OpenInNew'; import React from 'react'; -import { findLocationForEntityMeta } from '../../data/utils'; import { findViewUrl, findEditUrl } from '../actions'; import { favouriteEntityIcon, @@ -152,8 +151,7 @@ export const CatalogTable = ({ const actions: TableProps['actions'] = [ ({ entity }) => { - const location = findLocationForEntityMeta(entity.metadata); - const url = findViewUrl(entity, location); + const url = findViewUrl(entity); return { icon: () => , tooltip: 'View', @@ -164,8 +162,7 @@ export const CatalogTable = ({ }; }, ({ entity }) => { - const location = findLocationForEntityMeta(entity.metadata); - const url = findEditUrl(entity, location); + const url = findEditUrl(entity); return { icon: () => , tooltip: 'Edit', diff --git a/plugins/catalog/src/components/actions.ts b/plugins/catalog/src/components/actions.ts index 13e235d46e..72e1bf67ec 100644 --- a/plugins/catalog/src/components/actions.ts +++ b/plugins/catalog/src/components/actions.ts @@ -20,6 +20,7 @@ import { EDIT_URL_ANNOTATION, VIEW_URL_ANNOTATION, } from '@backstage/catalog-model'; +import { findLocationForEntityMeta } from '../data/utils'; import parseGitUrl from 'git-url-parse'; /** @@ -81,24 +82,21 @@ export const determineUrlType = (url: string): string => { return 'url'; }; -export const findEditUrl = ( - { metadata }: Entity, - location?: LocationSpec, -): string | undefined => { +export const findEditUrl = ({ metadata }: Entity): string | undefined => { const annotations = metadata.annotations || {}; const editUrl = annotations[EDIT_URL_ANNOTATION]; if (editUrl) return editUrl; + const location = findLocationForEntityMeta(metadata); + return location && createEditLink(location); }; -export const findViewUrl = ( - { metadata }: Entity, - location?: LocationSpec, -): string | undefined => { +export const findViewUrl = ({ metadata }: Entity): string | undefined => { const annotations = metadata.annotations || {}; + const location = findLocationForEntityMeta(metadata); return annotations[VIEW_URL_ANNOTATION] || location?.target; }; From bad21a085411106557164de0b51a4ffc5b62e6d6 Mon Sep 17 00:00:00 2001 From: James Turley Date: Sat, 20 Feb 2021 11:38:41 +0000 Subject: [PATCH 207/270] Changeset for catalog packages --- .changeset/honest-hounds-exist.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/honest-hounds-exist.md diff --git a/.changeset/honest-hounds-exist.md b/.changeset/honest-hounds-exist.md new file mode 100644 index 0000000000..8aadd84a95 --- /dev/null +++ b/.changeset/honest-hounds-exist.md @@ -0,0 +1,6 @@ +--- +'@backstage/catalog-model': patch +'@backstage/plugin-catalog': patch +--- + +Implement annotations for customising Entity URLs in the Catalog pages. From a8953a9c92e0bcf7cf7ce14fe86cf90bf4fe9fc4 Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Thu, 18 Feb 2021 22:58:57 -0500 Subject: [PATCH 208/270] feat: omit default namespace in catalog-import --- .changeset/dirty-buckets-flow.md | 5 ++ .../StepPrepareCreatePullRequest.test.tsx | 46 ++++++++++++++++++- .../StepPrepareCreatePullRequest.tsx | 3 +- 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 .changeset/dirty-buckets-flow.md diff --git a/.changeset/dirty-buckets-flow.md b/.changeset/dirty-buckets-flow.md new file mode 100644 index 0000000000..c0f0f030e2 --- /dev/null +++ b/.changeset/dirty-buckets-flow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': patch +--- + +Omits the entity namespace from a generated entity when it has not be explicitly defined in the analyze-location result. diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx index 0b38fdaf58..0f8df04968 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx @@ -21,7 +21,10 @@ import { act, render, RenderResult } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; import { AnalyzeResult, catalogImportApiRef } from '../../api'; -import { StepPrepareCreatePullRequest } from './StepPrepareCreatePullRequest'; +import { + generateEntities, + StepPrepareCreatePullRequest, +} from './StepPrepareCreatePullRequest'; describe('', () => { const catalogImportApi: jest.Mocked = { @@ -60,6 +63,7 @@ describe('', () => { kind: 'Component', metadata: { name: 'my-component', + namespace: 'default', }, spec: { owner: 'my-owner', @@ -284,4 +288,44 @@ spec: ][0], ).toMatchObject({ groups: ['Group:my-group'], groupsLoading: false }); }); + + describe('generateEntities', () => { + it.each([[undefined], [null]])( + 'should not include blank namespace for %s', + namespace => { + expect( + generateEntities( + [{ metadata: { namespace: namespace as any } }], + 'my-component', + 'group-1', + ), + ).toEqual([ + expect.objectContaining({ + metadata: expect.not.objectContaining({ + namespace: 'default', + }), + }), + ]); + }, + ); + + it.each([['default'], ['my-namespace']])( + 'should include explicit namespace %s', + namespace => { + expect( + generateEntities( + [{ metadata: { namespace } }], + 'my-component', + 'group-1', + ), + ).toEqual([ + expect.objectContaining({ + metadata: expect.objectContaining({ + namespace, + }), + }), + ]); + }, + ); + }); }); diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx index d9e7b962ab..0d08e27681 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx @@ -66,7 +66,7 @@ type Props = { ) => React.ReactNode; }; -function generateEntities( +export function generateEntities( entities: PartialEntity[], componentName: string, owner: string, @@ -78,7 +78,6 @@ function generateEntities( metadata: { ...e.metadata, name: componentName, - namespace: e.metadata?.namespace ?? 'default', }, spec: { ...e.spec, From 8b5f8f84cbc9e8e0c34e5d0bb1b26040e22ea4f5 Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Sat, 20 Feb 2021 11:18:32 -0500 Subject: [PATCH 209/270] update to use short form group entity references --- .changeset/dirty-buckets-flow.md | 3 ++- .../src/components/ImportStepper/defaults.tsx | 2 +- .../StepPrepareCreatePullRequest.test.tsx | 2 +- .../StepPrepareCreatePullRequest.tsx | 12 ++++++++---- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.changeset/dirty-buckets-flow.md b/.changeset/dirty-buckets-flow.md index c0f0f030e2..b76ab5d35c 100644 --- a/.changeset/dirty-buckets-flow.md +++ b/.changeset/dirty-buckets-flow.md @@ -2,4 +2,5 @@ '@backstage/plugin-catalog-import': patch --- -Omits the entity namespace from a generated entity when it has not be explicitly defined in the analyze-location result. +This updates the `catalog-import` plugin to omit the default metadata namespace +field and also use the short form entity reference format for selected group owners. diff --git a/plugins/catalog-import/src/components/ImportStepper/defaults.tsx b/plugins/catalog-import/src/components/ImportStepper/defaults.tsx index f2dd1d62f4..5252b0aeae 100644 --- a/plugins/catalog-import/src/components/ImportStepper/defaults.tsx +++ b/plugins/catalog-import/src/components/ImportStepper/defaults.tsx @@ -230,7 +230,7 @@ export function defaultGenerateStepper( errorHelperText="required value" textFieldProps={{ label: 'Entity Owner', - placeholder: 'Group:default/my-group', + placeholder: 'my-group', }} rules={{ required: true }} required diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx index 0f8df04968..606f9ff88e 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx @@ -286,7 +286,7 @@ spec: renderFormFieldsFn.mock.calls[ renderFormFieldsFn.mock.calls.length - 1 ][0], - ).toMatchObject({ groups: ['Group:my-group'], groupsLoading: false }); + ).toMatchObject({ groups: ['my-group'], groupsLoading: false }); }); describe('generateEntities', () => { diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx index 0d08e27681..333df76026 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx @@ -14,9 +14,12 @@ * limitations under the License. */ -import { Entity, serializeEntityRef } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { useApi } from '@backstage/core'; -import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import { + catalogApiRef, + formatEntityRefTitle, +} from '@backstage/plugin-catalog-react'; import { Box, FormHelperText, Grid, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React, { useCallback, useState } from 'react'; @@ -106,8 +109,9 @@ export const StepPrepareCreatePullRequest = ({ filter: { kind: 'group' }, }); - // TODO: defaultKind (=group), defaultNamespace (=same as entity) - return groupEntities.items.map(e => serializeEntityRef(e) as string).sort(); + return groupEntities.items + .map(e => formatEntityRefTitle(e, { defaultKind: 'group' })) + .sort(); }); const handleResult = useCallback( From 968b588f79a4d0d51d4c52060650c61991115f97 Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Sat, 20 Feb 2021 13:48:57 -0500 Subject: [PATCH 210/270] feat: support codeowners in catalog-import --- .changeset/silly-lemons-dream.md | 5 + .../src/components/ImportStepper/defaults.tsx | 144 ++++++++++-------- .../CheckboxField.tsx | 53 +++++++ .../PreparePullRequestForm.tsx | 6 +- .../StepPrepareCreatePullRequest.tsx | 15 +- .../StepPrepareCreatePullRequest/index.ts | 1 + 6 files changed, 157 insertions(+), 67 deletions(-) create mode 100644 .changeset/silly-lemons-dream.md create mode 100644 plugins/catalog-import/src/components/StepPrepareCreatePullRequest/CheckboxField.tsx diff --git a/.changeset/silly-lemons-dream.md b/.changeset/silly-lemons-dream.md new file mode 100644 index 0000000000..a93be80597 --- /dev/null +++ b/.changeset/silly-lemons-dream.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': patch +--- + +Allows the CodeOwnersProcessor to set the owner automatically within the catalog-import plugin. This adds an additional checkbox that overrides the group selector and will omit the owner option in the generated catalog file yaml. diff --git a/plugins/catalog-import/src/components/ImportStepper/defaults.tsx b/plugins/catalog-import/src/components/ImportStepper/defaults.tsx index 5252b0aeae..f55cf5e911 100644 --- a/plugins/catalog-import/src/components/ImportStepper/defaults.tsx +++ b/plugins/catalog-import/src/components/ImportStepper/defaults.tsx @@ -22,6 +22,7 @@ import { StepFinishImportLocation } from '../StepFinishImportLocation'; import { StepInitAnalyzeUrl } from '../StepInitAnalyzeUrl'; import { AutocompleteTextField, + CheckboxField, StepPrepareCreatePullRequest, } from '../StepPrepareCreatePullRequest'; import { StepPrepareSelectLocations } from '../StepPrepareSelectLocations'; @@ -169,74 +170,97 @@ export function defaultGenerateStepper( renderFormFields={({ control, errors, + watch, groupsLoading, groups, register, - }) => ( - <> - - Pull Request Details - + }) => { + const watchUseCodeowners = watch('useCodeowners', false); - + return ( + <> + + + Pull Request Details + + - + - - Entity Configuration - + - + + + Entity Configuration + + - - - )} + + + {!watchUseCodeowners && ( + + )} + + { + if (value) { + control.setValue('owner', ''); + } + }} + /> + + ); + }} /> ), }; diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/CheckboxField.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/CheckboxField.tsx new file mode 100644 index 0000000000..dbce1b5229 --- /dev/null +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/CheckboxField.tsx @@ -0,0 +1,53 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { Checkbox, FormControlLabel, FormHelperText } from '@material-ui/core'; +import React from 'react'; + +type Props = { + name: TFieldValue; + label: React.ReactNode; + inputRef: + | ((instance: HTMLInputElement | null) => void) + | React.RefObject + | null + | undefined; + onChange?: ( + event: React.ChangeEvent, + checked: boolean, + ) => void; + helperText?: React.ReactNode | string; +}; + +export const CheckboxField = ({ + name, + label, + inputRef, + onChange, + helperText, +}: Props) => { + return ( + <> + + } + label={label} + /> + {helperText && {helperText}} + + ); +}; diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreparePullRequestForm.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreparePullRequestForm.tsx index 9d59f4e10e..aed87a3b34 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreparePullRequestForm.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreparePullRequestForm.tsx @@ -32,7 +32,7 @@ type Props> = Pick< render: ( props: Pick< UseFormMethods, - 'errors' | 'register' | 'control' + 'errors' | 'register' | 'control' | 'formState' | 'watch' > & { values: UnpackNestedValue; }, @@ -55,13 +55,13 @@ export const PreparePullRequestForm = < onSubmit, render, }: Props) => { - const { handleSubmit, watch, control, register, errors } = useForm< + const { handleSubmit, control, register, errors, formState, watch } = useForm< TFieldValues >({ mode: 'onTouched', defaultValues }); return (
- {render({ values: watch(), errors, register, control })} + {render({ values: watch(), errors, register, control, formState, watch })}
); }; diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx index 333df76026..1966b8457f 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx @@ -48,6 +48,7 @@ type FormData = { body: string; componentName: string; owner: string; + useCodeowners: boolean; }; type Props = { @@ -62,7 +63,10 @@ type Props = { defaultBody: string; renderFormFields: ( - props: Pick, 'errors' | 'register' | 'control'> & { + props: Pick< + UseFormMethods, + 'errors' | 'register' | 'control' | 'formState' | 'watch' + > & { groups: string[]; groupsLoading: boolean; }, @@ -72,7 +76,7 @@ type Props = { export function generateEntities( entities: PartialEntity[], componentName: string, - owner: string, + owner?: string, ): Entity[] { return entities.map(e => ({ ...e, @@ -84,7 +88,7 @@ export function generateEntities( }, spec: { ...e.spec, - owner, + ...(owner ? { owner } : {}), }, })); } @@ -189,13 +193,16 @@ export const StepPrepareCreatePullRequest = ({ (analyzeResult.generatedEntities[0]?.spec?.owner as string) || '', componentName: analyzeResult.generatedEntities[0]?.metadata?.name || '', + useCodeowners: false, }} - render={({ values, errors, control, register }) => ( + render={({ values, errors, control, register, formState, watch }) => ( <> {renderFormFields({ errors, register, control, + formState, + watch, groups: groups ?? [], groupsLoading, })} diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/index.ts b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/index.ts index 119feee4a1..a0fc6cbc95 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/index.ts +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/index.ts @@ -15,6 +15,7 @@ */ export { AutocompleteTextField } from './AutocompleteTextField'; +export { CheckboxField } from './CheckboxField'; export { PreparePullRequestForm } from './PreparePullRequestForm'; export { PreviewCatalogInfoComponent } from './PreviewCatalogInfoComponent'; export { PreviewPullRequestComponent } from './PreviewPullRequestComponent'; From 436b76fbdddd675022ff41e7a2eeffc195bcc1c1 Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Sat, 20 Feb 2021 14:49:25 -0500 Subject: [PATCH 211/270] remove unecessary watch & formState --- .../src/components/ImportStepper/defaults.tsx | 176 +++++++++--------- .../CheckboxField.tsx | 53 ------ .../PreparePullRequestForm.tsx | 6 +- .../StepPrepareCreatePullRequest.tsx | 13 +- .../StepPrepareCreatePullRequest/index.ts | 1 - 5 files changed, 101 insertions(+), 148 deletions(-) delete mode 100644 plugins/catalog-import/src/components/StepPrepareCreatePullRequest/CheckboxField.tsx diff --git a/plugins/catalog-import/src/components/ImportStepper/defaults.tsx b/plugins/catalog-import/src/components/ImportStepper/defaults.tsx index f55cf5e911..dff2136aaa 100644 --- a/plugins/catalog-import/src/components/ImportStepper/defaults.tsx +++ b/plugins/catalog-import/src/components/ImportStepper/defaults.tsx @@ -15,14 +15,21 @@ */ import { ConfigApi } from '@backstage/core'; -import { Box, StepLabel, TextField, Typography } from '@material-ui/core'; +import { + Box, + Checkbox, + FormControlLabel, + FormHelperText, + StepLabel, + TextField, + Typography, +} from '@material-ui/core'; import React from 'react'; import { BackButton } from '../Buttons'; import { StepFinishImportLocation } from '../StepFinishImportLocation'; import { StepInitAnalyzeUrl } from '../StepInitAnalyzeUrl'; import { AutocompleteTextField, - CheckboxField, StepPrepareCreatePullRequest, } from '../StepPrepareCreatePullRequest'; import { StepPrepareSelectLocations } from '../StepPrepareSelectLocations'; @@ -168,99 +175,102 @@ export function defaultGenerateStepper( defaultTitle={title} defaultBody={body} renderFormFields={({ + values, control, errors, - watch, groupsLoading, groups, register, - }) => { - const watchUseCodeowners = watch('useCodeowners', false); + }) => ( + <> + + Pull Request Details + - return ( - <> - - - Pull Request Details - - + - + - + + Entity Configuration + - - - Entity Configuration - - + - - - {!watchUseCodeowners && ( - - )} - - { - if (value) { - control.setValue('owner', ''); - } + {!values.useCodeowners && ( + - - ); - }} + )} + + { + if (value) { + control.setValue('owner', ''); + } + }} + /> + } + label={ + <> + Use CODEOWNERS file as Entity Owner + + } + /> + + WARNING: This may fail is no CODEOWNERS file is found at + the target location. + + + )} /> ), }; diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/CheckboxField.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/CheckboxField.tsx deleted file mode 100644 index dbce1b5229..0000000000 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/CheckboxField.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2021 Spotify AB - * - * 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 { Checkbox, FormControlLabel, FormHelperText } from '@material-ui/core'; -import React from 'react'; - -type Props = { - name: TFieldValue; - label: React.ReactNode; - inputRef: - | ((instance: HTMLInputElement | null) => void) - | React.RefObject - | null - | undefined; - onChange?: ( - event: React.ChangeEvent, - checked: boolean, - ) => void; - helperText?: React.ReactNode | string; -}; - -export const CheckboxField = ({ - name, - label, - inputRef, - onChange, - helperText, -}: Props) => { - return ( - <> - - } - label={label} - /> - {helperText && {helperText}} - - ); -}; diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreparePullRequestForm.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreparePullRequestForm.tsx index aed87a3b34..9d59f4e10e 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreparePullRequestForm.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreparePullRequestForm.tsx @@ -32,7 +32,7 @@ type Props> = Pick< render: ( props: Pick< UseFormMethods, - 'errors' | 'register' | 'control' | 'formState' | 'watch' + 'errors' | 'register' | 'control' > & { values: UnpackNestedValue; }, @@ -55,13 +55,13 @@ export const PreparePullRequestForm = < onSubmit, render, }: Props) => { - const { handleSubmit, control, register, errors, formState, watch } = useForm< + const { handleSubmit, watch, control, register, errors } = useForm< TFieldValues >({ mode: 'onTouched', defaultValues }); return (
- {render({ values: watch(), errors, register, control, formState, watch })} + {render({ values: watch(), errors, register, control })}
); }; diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx index 1966b8457f..c34d5f374b 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx @@ -23,7 +23,7 @@ import { import { Box, FormHelperText, Grid, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React, { useCallback, useState } from 'react'; -import { UseFormMethods } from 'react-hook-form'; +import { UnpackNestedValue, UseFormMethods } from 'react-hook-form'; import { useAsync } from 'react-use'; import YAML from 'yaml'; import { AnalyzeResult, catalogImportApiRef } from '../../api'; @@ -63,10 +63,8 @@ type Props = { defaultBody: string; renderFormFields: ( - props: Pick< - UseFormMethods, - 'errors' | 'register' | 'control' | 'formState' | 'watch' - > & { + props: Pick, 'errors' | 'register' | 'control'> & { + values: UnpackNestedValue; groups: string[]; groupsLoading: boolean; }, @@ -195,14 +193,13 @@ export const StepPrepareCreatePullRequest = ({ analyzeResult.generatedEntities[0]?.metadata?.name || '', useCodeowners: false, }} - render={({ values, errors, control, register, formState, watch }) => ( + render={({ values, errors, control, register }) => ( <> {renderFormFields({ + values, errors, register, control, - formState, - watch, groups: groups ?? [], groupsLoading, })} diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/index.ts b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/index.ts index a0fc6cbc95..119feee4a1 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/index.ts +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/index.ts @@ -15,7 +15,6 @@ */ export { AutocompleteTextField } from './AutocompleteTextField'; -export { CheckboxField } from './CheckboxField'; export { PreparePullRequestForm } from './PreparePullRequestForm'; export { PreviewCatalogInfoComponent } from './PreviewCatalogInfoComponent'; export { PreviewPullRequestComponent } from './PreviewPullRequestComponent'; From 60d1bc3e71e0445ac06e2bc35f69e7d6f0267512 Mon Sep 17 00:00:00 2001 From: Tadashi Nemoto Date: Sun, 21 Feb 2021 13:17:05 +0900 Subject: [PATCH 212/270] Add changeset --- .changeset/strong-badgers-invent.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/strong-badgers-invent.md diff --git a/.changeset/strong-badgers-invent.md b/.changeset/strong-badgers-invent.md new file mode 100644 index 0000000000..b0e55be3e7 --- /dev/null +++ b/.changeset/strong-badgers-invent.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': minor +--- + +Fix Japanese Good Morning From b313e61ff31ff7eab0f913662d9abe94dd3c0d7a Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Sun, 21 Feb 2021 08:10:01 +0100 Subject: [PATCH 213/270] Update .changeset/strong-badgers-invent.md --- .changeset/strong-badgers-invent.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/strong-badgers-invent.md b/.changeset/strong-badgers-invent.md index b0e55be3e7..6369852675 100644 --- a/.changeset/strong-badgers-invent.md +++ b/.changeset/strong-badgers-invent.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-catalog': minor +'@backstage/plugin-catalog': patch --- Fix Japanese Good Morning From a1f5e65452faafe1e8772f8c81ea9b26bf83575f Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Sun, 21 Feb 2021 09:52:50 -0500 Subject: [PATCH 214/270] feat(config): support native casting for get functions --- .changeset/spotty-news-complain.md | 13 +++++++++++++ packages/config/src/reader.ts | 12 ++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 .changeset/spotty-news-complain.md diff --git a/.changeset/spotty-news-complain.md b/.changeset/spotty-news-complain.md new file mode 100644 index 0000000000..8aac7ebdf6 --- /dev/null +++ b/.changeset/spotty-news-complain.md @@ -0,0 +1,13 @@ +--- +'@backstage/config': patch +--- + +Adds an optional type to `config.get` & `config.getOptional`. This avoids the need for casting. For example: + +```ts +const config = useApi(configApiRef); + +const myConfig = config.get('myPlugin.complexConfig'); +// vs +const myConfig config.get('myPlugin.complexConfig') as SomeTypeDefinition; +``` diff --git a/packages/config/src/reader.ts b/packages/config/src/reader.ts index c690e0bbc9..99977d19cd 100644 --- a/packages/config/src/reader.ts +++ b/packages/config/src/reader.ts @@ -88,22 +88,22 @@ export class ConfigReader implements Config { return [...new Set([...localKeys, ...fallbackKeys])]; } - get(key?: string): JsonValue { + get(key?: string): T { const value = this.getOptional(key); if (value === undefined) { throw new Error(errors.missing(this.fullKey(key ?? ''))); } - return value; + return value as T; } - getOptional(key?: string): JsonValue | undefined { + getOptional(key?: string): T | undefined { const value = this.readValue(key); - const fallbackValue = this.fallback?.getOptional(key); + const fallbackValue = this.fallback?.getOptional(key); if (value === undefined) { return fallbackValue; } else if (fallbackValue === undefined) { - return value; + return value as T; } // Avoid merging arrays and primitive values, since that's how merging works for other @@ -113,7 +113,7 @@ export class ConfigReader implements Config { { value: cloneDeep(fallbackValue) }, { value }, (into, from) => (!isObject(from) || !isObject(into) ? from : undefined), - ).value; + ).value as T; } getConfig(key: string): ConfigReader { From 3b55f5f81fef3e7a1f79edaa6142e852e11c5923 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Sun, 21 Feb 2021 13:36:32 -0500 Subject: [PATCH 215/270] declare default properties using accessors --- .../src/alerts/ProjectGrowthAlert.test.tsx | 77 +++++++++++++++++++ .../src/alerts/ProjectGrowthAlert.tsx | 12 ++- .../alerts/UnlabeledDataflowAlert.test.tsx | 72 +++++++++++++++++ .../src/alerts/UnlabeledDataflowAlert.tsx | 17 ++-- 4 files changed, 169 insertions(+), 9 deletions(-) create mode 100644 plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx create mode 100644 plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx diff --git a/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx b/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx new file mode 100644 index 0000000000..22b55d85dd --- /dev/null +++ b/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import pluralize from 'pluralize'; +import { renderInTestApp } from '@backstage/test-utils'; +import { ProjectGrowthAlert } from './ProjectGrowthAlert'; +import { ProjectGrowthData } from '../types'; +import { + MockCurrencyProvider, + MockConfigProvider, + MockBillingDateProvider, +} from '../utils/tests'; + +const mockData: ProjectGrowthData = { + project: 'test-project', + periodStart: '2021-01-01', + periodEnd: '2021-02-01', + aggregation: [0, 0], + change: { + ratio: 0, + amount: 0 + }, + products: [ + { + id: 'product-a', + aggregation: [0, 0] + } + ], +}; + +// suppress recharts componentDidUpdate deprecation warnings +jest.spyOn(console, 'warn').mockImplementation(() => { }); + +async function renderInContext(children: JSX.Element) { + return renderInTestApp( + + + + {children} + + + + ) +} + +class CustomProjectGrowthAlert extends ProjectGrowthAlert { + get url() { + return 'path/to/resource'; + } + get title() { + return `Investigate cost growth in ${pluralize('project', this.data.products.length, true)}`; + } +} + +describe('ProjectGrowthAlert', () => { + describe('constructor', () => { + it('should create a project growth alert', async () => { + const alert = new ProjectGrowthAlert(mockData); + const { getByText, queryByText } = await renderInContext(alert.element); + + expect(alert.url).toBe('/cost-insights/investigating-growth'); + expect(alert.title).toBe('Investigate cost growth in project test-project'); + expect(alert.subtitle).toBe('Cost growth outpacing business growth is unsustainable long-term.'); + expect(getByText('1 product')).toBeInTheDocument(); + expect(queryByText('sorted by cost')).not.toBeInTheDocument(); + }); + + it('a subclass can inherit and override defaults using accessors', async () => { + const alert = new CustomProjectGrowthAlert(mockData); + const { getByText, queryByText } = await renderInContext(alert.element); + + expect(alert.url).toBe('path/to/resource'); + expect(alert.title).toBe('Investigate cost growth in 1 project'); + expect(alert.subtitle).toBe('Cost growth outpacing business growth is unsustainable long-term.'); + expect(getByText('1 product')).toBeInTheDocument(); + expect(queryByText('sorted by cost')).not.toBeInTheDocument(); + }); + }); +}) diff --git a/plugins/cost-insights/src/alerts/ProjectGrowthAlert.tsx b/plugins/cost-insights/src/alerts/ProjectGrowthAlert.tsx index 67eb1b5071..088e85939d 100644 --- a/plugins/cost-insights/src/alerts/ProjectGrowthAlert.tsx +++ b/plugins/cost-insights/src/alerts/ProjectGrowthAlert.tsx @@ -27,18 +27,22 @@ import { Alert, ProjectGrowthData } from '../types'; export class ProjectGrowthAlert implements Alert { data: ProjectGrowthData; - url = '/cost-insights/investigating-growth'; - subtitle = - 'Cost growth outpacing business growth is unsustainable long-term.'; - constructor(data: ProjectGrowthData) { this.data = data; } + get url() { + return '/cost-insights/investigating-growth'; + } + get title() { return `Investigate cost growth in project ${this.data.project}`; } + get subtitle() { + return 'Cost growth outpacing business growth is unsustainable long-term.'; + } + get element() { return ; } diff --git a/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx b/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx new file mode 100644 index 0000000000..d6cbd0006f --- /dev/null +++ b/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import pluralize from 'pluralize'; +import { renderInTestApp } from '@backstage/test-utils'; +import { UnlabeledDataflowAlert } from './UnlabeledDataflowAlert'; +import { UnlabeledDataflowData } from '../types'; +import { + MockCurrencyProvider, + MockConfigProvider, + MockBillingDateProvider, +} from '../utils/tests'; + +const mockData: UnlabeledDataflowData = { + periodStart: '2021-02-01', + periodEnd: '2021-03-31', + unlabeledCost: 0, + labeledCost: 0, + projects: [ + { + id: 'project-a', + labeledCost: 0, + unlabeledCost: 0 + } + ] +}; + +// suppress recharts componentDidUpdate deprecation warnings +jest.spyOn(console, 'warn').mockImplementation(() => { }); + +async function renderInContext(children: JSX.Element) { + return renderInTestApp( + + + + {children} + + + + ) +} + +class CustomUnlabeledDataflowAlert extends UnlabeledDataflowAlert { + get url() { + return 'path/to/resource'; + } + get title() { + return `Add labels to ${pluralize('workflow', this.data.projects.length, true)}`; + } +} + +describe('UnlabeledDataflowAlert', () => { + describe('constructor', () => { + it('should create an unlabeled dataflow alert', async () => { + const alert = new UnlabeledDataflowAlert(mockData); + const { getByText } = await renderInContext(alert.element); + + expect(alert.url).toBe('/cost-insights/labeling-jobs'); + expect(alert.title).toBe('Add labels to workflows'); + expect(alert.subtitle).toBe('Labels show in billing data, enabling cost insights for each workflow.'); + expect(getByText('Showing costs from 1 project with unlabeled Dataflow jobs in the last 30 days.')).toBeInTheDocument(); + }); + + it('a subclass can inherit and override defaults using accessors', async () => { + const alert = new CustomUnlabeledDataflowAlert(mockData); + const { getByText } = await renderInContext(alert.element); + + expect(alert.url).toBe('path/to/resource'); + expect(alert.title).toBe('Add labels to 1 workflow'); + expect(alert.subtitle).toBe('Labels show in billing data, enabling cost insights for each workflow.'); + expect(getByText('Showing costs from 1 project with unlabeled Dataflow jobs in the last 30 days.')).toBeInTheDocument(); + }); + }); +}) diff --git a/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.tsx b/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.tsx index e889e0e3d4..7d11f3cc81 100644 --- a/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.tsx +++ b/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.tsx @@ -28,15 +28,22 @@ export class UnlabeledDataflowAlert implements Alert { data: UnlabeledDataflowData; status?: AlertStatus; - url = '/cost-insights/labeling-jobs'; - title = 'Add labels to workflows'; - subtitle = - 'Labels show in billing data, enabling cost insights for each workflow.'; - constructor(data: UnlabeledDataflowData) { this.data = data; } + get url() { + return '/cost-insights/labeling-jobs'; + } + + get title() { + return 'Add labels to workflows'; + } + + get subtitle() { + return 'Labels show in billing data, enabling cost insights for each workflow.'; + } + get element() { return ; } From 38205492aa060f35682566b21f88d34a20625cb5 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Sun, 21 Feb 2021 13:38:35 -0500 Subject: [PATCH 216/270] changeset --- .changeset/cost-insights-fresh-radios-doubt.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cost-insights-fresh-radios-doubt.md diff --git a/.changeset/cost-insights-fresh-radios-doubt.md b/.changeset/cost-insights-fresh-radios-doubt.md new file mode 100644 index 0000000000..849f5140f6 --- /dev/null +++ b/.changeset/cost-insights-fresh-radios-doubt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': patch +--- + +Default alert properties can be overridden using accessors From 4594f7efc278e637b1f62aeb93987f1c87d8f365 Mon Sep 17 00:00:00 2001 From: "r.bideau" <7304827+rbideau@users.noreply.github.com> Date: Thu, 18 Feb 2021 17:13:00 +0100 Subject: [PATCH 217/270] Add google analytics scripts in template for standalone app Signed-off-by: r.bideau <7304827+rbideau@users.noreply.github.com> --- .changeset/mighty-masks-hear.md | 29 +++++++++++++++++++ .../packages/app/public/index.html | 16 ++++++++++ 2 files changed, 45 insertions(+) create mode 100644 .changeset/mighty-masks-hear.md diff --git a/.changeset/mighty-masks-hear.md b/.changeset/mighty-masks-hear.md new file mode 100644 index 0000000000..bb19be1420 --- /dev/null +++ b/.changeset/mighty-masks-hear.md @@ -0,0 +1,29 @@ +--- +'@backstage/create-app': patch +--- + +Add the google analytics scripts in the `index.html` template for new applications. + +To apply this change to an existing application, change the following in `packages\app\public\index.html`: + +```diff + <%= app.title %> + ++ <% if (app.googleAnalyticsTrackingId && typeof app.googleAnalyticsTrackingId ++ === 'string') { %> ++ ++ ++ <% } %> + +``` diff --git a/packages/create-app/templates/default-app/packages/app/public/index.html b/packages/create-app/templates/default-app/packages/app/public/index.html index ea9208ca57..5653173480 100644 --- a/packages/create-app/templates/default-app/packages/app/public/index.html +++ b/packages/create-app/templates/default-app/packages/app/public/index.html @@ -48,6 +48,22 @@ } <%= app.title %> + <% if (app.googleAnalyticsTrackingId && typeof app.googleAnalyticsTrackingId + === 'string') { %> + + + <% } %> From 24b59505d0d978f54a0fe049bbe1a203e928dddb Mon Sep 17 00:00:00 2001 From: "r.bideau" <7304827+rbideau@users.noreply.github.com> Date: Sun, 21 Feb 2021 20:41:44 +0100 Subject: [PATCH 218/270] Moves google analytics docs in an integration section - with a link to it in the observability page. - documents the default behavior and add a link to the GA developer guides Signed-off-by: r.bideau <7304827+rbideau@users.noreply.github.com> --- .../google-analytics/installation.md | 23 +++++++++++++++++++ docs/plugins/observability.md | 14 ++--------- 2 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 docs/integrations/google-analytics/installation.md diff --git a/docs/integrations/google-analytics/installation.md b/docs/integrations/google-analytics/installation.md new file mode 100644 index 0000000000..759cfa3ed1 --- /dev/null +++ b/docs/integrations/google-analytics/installation.md @@ -0,0 +1,23 @@ +--- +id: installation +title: Google Analytics Installation +# prettier-ignore +description: Adding Google Analytics to Your App +--- + +There is a basic Google Analytics integration built into Backstage. You can +enable it by adding the following to your app configuration: + +```yaml +app: + googleAnalyticsTrackingId: UA-000000-0 +``` + +Replace the tracking ID with your own. + +For more information, learn about Google Analytics +[here](https://marketingplatform.google.com/about/analytics/). + +The default behavior is only to send a pageview hit to Google Analytics. To +record more, look at the developer documentation +[here](https://developers.google.com/analytics/devguides/collection/gtagjs). diff --git a/docs/plugins/observability.md b/docs/plugins/observability.md index 8b6668606f..de6f4bf988 100644 --- a/docs/plugins/observability.md +++ b/docs/plugins/observability.md @@ -10,18 +10,8 @@ Backstage integrator. ## Google Analytics -There is a basic Google Analytics integration built into Backstage. You can -enable it by adding the following to your app configuration: - -```yaml -app: - googleAnalyticsTrackingId: UA-000000-0 -``` - -Replace the tracking ID with your own. - -For more information, learn about Google Analytics -[here](https://marketingplatform.google.com/about/analytics/). +See how to install Google Analytics in your app +[here](../integrations/google-analytics/installation.md) ## Logging From 0f1e10b1011f07d6cd2694a5fa5b80f90dd35ea6 Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Sun, 21 Feb 2021 15:13:21 -0500 Subject: [PATCH 219/270] fix(config): ensure config type reflects get --- packages/config/src/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/config/src/types.ts b/packages/config/src/types.ts index 840cc36173..92293a3f26 100644 --- a/packages/config/src/types.ts +++ b/packages/config/src/types.ts @@ -34,8 +34,8 @@ export type Config = { keys(): string[]; - get(key?: string): JsonValue; - getOptional(key?: string): JsonValue | undefined; + get(key?: string): T; + getOptional(key?: string): T | undefined; getConfig(key: string): Config; getOptionalConfig(key: string): Config | undefined; From 64f6f5255c1789740169c948ef089cdede9d65d7 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 21 Feb 2021 22:55:25 +0100 Subject: [PATCH 220/270] core-api: migrate app context to use a separate type and implementation --- packages/core-api/src/app/App.tsx | 37 +++++++++++++++++++++++- packages/core-api/src/app/AppContext.tsx | 18 ++++++------ packages/core-api/src/app/types.ts | 27 +++++++++++++++++ 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index f50bcdd015..0062a48853 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -64,6 +64,7 @@ import { AppThemeProvider } from './AppThemeProvider'; import { AppComponents, AppConfigLoader, + AppContext, AppOptions, AppRouteBinder, BackstageApp, @@ -139,6 +140,38 @@ function useConfigLoader( return { api: configReader }; } +class AppContextImpl implements AppContext { + constructor(private readonly app: PrivateAppImpl) {} + + getPlugins(): BackstagePlugin[] { + // eslint-disable-next-line no-console + console.warn('appContext.getPlugins() is deprecated and will be removed'); + return this.app.getPlugins(); + } + + getSystemIcon(key: string): IconComponent { + return this.app.getSystemIcon(key); + } + + getProvider(): React.ComponentType<{}> { + // eslint-disable-next-line no-console + console.warn('appContext.getProvider() is deprecated and will be removed'); + return this.app.getProvider(); + } + + getRouter(): React.ComponentType<{}> { + // eslint-disable-next-line no-console + console.warn('appContext.getRouter() is deprecated and will be removed'); + return this.app.getRouter(); + } + + getRoutes(): JSX.Element[] { + // eslint-disable-next-line no-console + console.warn('appContext.getRoutes() is deprecated and will be removed'); + return this.app.getRoutes(); + } +} + export class PrivateAppImpl implements BackstageApp { private apiHolder?: ApiHolder; private configApi?: ConfigApi; @@ -236,6 +269,8 @@ export class PrivateAppImpl implements BackstageApp { } getProvider(): ComponentType<{}> { + const appContext = new AppContextImpl(this); + const Provider = ({ children }: PropsWithChildren<{}>) => { const appThemeApi = useMemo( () => AppThemeSelector.createWithStorage(this.themes), @@ -273,7 +308,7 @@ export class PrivateAppImpl implements BackstageApp { return ( - + (undefined); +const Context = createContext(undefined); type Props = { - app: BackstageApp; + appContext: AppContext; }; export const AppContextProvider = ({ - app, + appContext, children, }: PropsWithChildren) => ( - + ); -export const useApp = (): BackstageApp => { - const app = useContext(Context); - if (!app) { +export const useApp = (): AppContext => { + const appContext = useContext(Context); + if (!appContext) { throw new Error('No app context available'); } - return app; + return appContext; }; diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts index 1970868ca6..6f96242d36 100644 --- a/packages/core-api/src/app/types.ts +++ b/packages/core-api/src/app/types.ts @@ -190,3 +190,30 @@ export type BackstageApp = { */ getRoutes(): JSX.Element[]; }; + +export type AppContext = { + /** + * @deprecated Will be removed + */ + getPlugins(): BackstagePlugin[]; + + /** + * Get a common or custom icon for this app. + */ + getSystemIcon(key: IconKey): IconComponent; + + /** + * @deprecated Will be removed + */ + getProvider(): ComponentType<{}>; + + /** + * @deprecated Will be removed + */ + getRouter(): ComponentType<{}>; + + /** + * @deprecated Will be removed + */ + getRoutes(): JSX.Element[]; +}; From 904280dd7269560a0dd08221b1b8ca80290f6948 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 21 Feb 2021 22:59:27 +0100 Subject: [PATCH 221/270] core-api: add app components to app context --- packages/core-api/src/app/App.tsx | 8 ++++++++ packages/core-api/src/app/types.ts | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index 0062a48853..07479ce19b 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -153,6 +153,10 @@ class AppContextImpl implements AppContext { return this.app.getSystemIcon(key); } + getComponents(): AppComponents { + return this.app.getComponents(); + } + getProvider(): React.ComponentType<{}> { // eslint-disable-next-line no-console console.warn('appContext.getProvider() is deprecated and will be removed'); @@ -206,6 +210,10 @@ export class PrivateAppImpl implements BackstageApp { return this.icons[key]; } + getComponents(): AppComponents { + return this.components; + } + getRoutes(): JSX.Element[] { const routes = new Array(); diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts index 6f96242d36..373839d308 100644 --- a/packages/core-api/src/app/types.ts +++ b/packages/core-api/src/app/types.ts @@ -202,6 +202,11 @@ export type AppContext = { */ getSystemIcon(key: IconKey): IconComponent; + /** + * Get the components registered for various purposes in the app. + */ + getComponents(): AppComponents; + /** * @deprecated Will be removed */ From c7fd1c6a2e470e0a2a11bca442ffe9881ffa8fad Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 21 Feb 2021 23:02:34 +0100 Subject: [PATCH 222/270] core-api: add NotFoundPage to FlatRoutes --- packages/core-api/src/routing/FlatRoutes.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/core-api/src/routing/FlatRoutes.tsx b/packages/core-api/src/routing/FlatRoutes.tsx index cefb347290..3702442811 100644 --- a/packages/core-api/src/routing/FlatRoutes.tsx +++ b/packages/core-api/src/routing/FlatRoutes.tsx @@ -14,8 +14,9 @@ * limitations under the License. */ -import { ReactNode, Children, isValidElement, Fragment } from 'react'; +import React, { ReactNode, Children, isValidElement, Fragment } from 'react'; import { useRoutes } from 'react-router-dom'; +import { useApp } from '../app'; type RouteObject = { path: string; @@ -71,6 +72,15 @@ type FlatRoutesProps = { }; export const FlatRoutes = (props: FlatRoutesProps): JSX.Element | null => { + const app = useApp(); + const { NotFoundErrorPage } = app.getComponents(); const routes = createRoutesFromChildren(props.children); + + // TODO(Rugvip): Possibly add a way to skip this, like a noNotFoundPage prop + routes.push({ + element: , + path: '/*', + }); + return useRoutes(routes); }; From 3a58084b656d782349f281241a8b8767c4df6964 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 22 Feb 2021 01:04:10 +0100 Subject: [PATCH 223/270] added changesets --- .changeset/chilly-eels-try.md | 6 ++++++ .changeset/silly-pandas-flash.md | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 .changeset/chilly-eels-try.md create mode 100644 .changeset/silly-pandas-flash.md diff --git a/.changeset/chilly-eels-try.md b/.changeset/chilly-eels-try.md new file mode 100644 index 0000000000..e682c567ea --- /dev/null +++ b/.changeset/chilly-eels-try.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-api': patch +'@backstage/core': patch +--- + +The `FlatRoutes` components now renders the not found page of the app if no routes are matched. diff --git a/.changeset/silly-pandas-flash.md b/.changeset/silly-pandas-flash.md new file mode 100644 index 0000000000..114b2386e0 --- /dev/null +++ b/.changeset/silly-pandas-flash.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-api': patch +'@backstage/core': patch +--- + +Created separate `AppContext` type to be returned from `useApp` rather than the `BackstageApp` itself. The `AppContext` type includes but deprecates `getPlugins`, `getProvider`, `getRouter`, and `getRoutes`. In addition, the `AppContext` adds a new `getComponents` method which providers access to the app components. From abd655e42d4ed416b70848ffdb1c4b99d189f13b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 22 Feb 2021 00:55:01 +0100 Subject: [PATCH 224/270] app: migrate to use top-level page extensions --- packages/app/src/App.tsx | 51 +++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 4a49db02d5..9a7c857167 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -17,7 +17,6 @@ import { AlertDisplay, createApp, - createRouteRef, FlatRoutes, OAuthRequestDialog, SignInPage, @@ -29,13 +28,12 @@ import { } from '@backstage/plugin-catalog'; import { CatalogImportPage } from '@backstage/plugin-catalog-import'; import { ExplorePage } from '@backstage/plugin-explore'; -import { Router as GraphiQLRouter } from '@backstage/plugin-graphiql'; -import { Router as LighthouseRouter } from '@backstage/plugin-lighthouse'; -import { Router as RegisterComponentRouter } from '@backstage/plugin-register-component'; +import { GraphiQLPage } from '@backstage/plugin-graphiql'; +import { LighthousePage } from '@backstage/plugin-lighthouse'; import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { Router as TechRadarRouter } from '@backstage/plugin-tech-radar'; -import { Router as DocsRouter } from '@backstage/plugin-techdocs'; -import { Router as SettingsRouter } from '@backstage/plugin-user-settings'; +import { TechRadarPage } from '@backstage/plugin-tech-radar'; +import { TechdocsPage } from '@backstage/plugin-techdocs'; +import { UserSettingsPage } from '@backstage/plugin-user-settings'; import React from 'react'; import { hot } from 'react-hot-loader/root'; import { Navigate, Route } from 'react-router'; @@ -45,6 +43,15 @@ import Root from './components/Root'; import { providers } from './identityProviders'; import * as plugins from './plugins'; import AlarmIcon from '@material-ui/icons/Alarm'; +import { ApiExplorerPage } from '@backstage/plugin-api-docs'; +import { GcpProjectsPage } from '@backstage/plugin-gcp-projects'; +import { NewRelicPage } from '@backstage/plugin-newrelic'; +import { SearchPage } from '@backstage/plugin-search'; +import { + CostInsightsLabelDataflowInstructionsPage, + CostInsightsPage, + CostInsightsProjectGrowthInstructionsPage, +} from '@backstage/plugin-cost-insights'; const app = createApp({ apis, @@ -74,12 +81,6 @@ const app = createApp({ const AppProvider = app.getProvider(); const AppRouter = app.getRouter(); -const deprecatedAppRoutes = app.getRoutes(); - -const catalogRouteRef = createRouteRef({ - path: '/catalog', - title: 'Service Catalog', -}); const routes = ( @@ -92,21 +93,29 @@ const routes = ( } /> - } /> + } /> } /> } /> } + element={} /> - } /> - } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> } + path="/cost-insights/investigating-growth" + element={} /> - } /> - {...deprecatedAppRoutes} + } + /> + } /> ); From 24779db8bfe0195869dfcd6f97184509d506f12f Mon Sep 17 00:00:00 2001 From: "r.bideau" <7304827+rbideau@users.noreply.github.com> Date: Mon, 22 Feb 2021 09:45:44 +0100 Subject: [PATCH 225/270] Add pageview to vocab Signed-off-by: r.bideau <7304827+rbideau@users.noreply.github.com> --- .github/styles/vocab.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 2a8854b8a2..bf6dfb4451 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -204,6 +204,7 @@ octokit oidc onboarding pagerduty +pageview parallelization plantuml postgres From 72722c1f6cfe43cf6b5b73dfa96ec888b018c941 Mon Sep 17 00:00:00 2001 From: "r.bideau" <7304827+rbideau@users.noreply.github.com> Date: Mon, 22 Feb 2021 10:31:59 +0100 Subject: [PATCH 226/270] Add Google Analytics installation to sidebars and mkdocs Signed-off-by: r.bideau <7304827+rbideau@users.noreply.github.com> --- docs/integrations/google-analytics/installation.md | 1 + microsite/sidebars.json | 5 +++++ mkdocs.yml | 2 ++ 3 files changed, 8 insertions(+) diff --git a/docs/integrations/google-analytics/installation.md b/docs/integrations/google-analytics/installation.md index 759cfa3ed1..a0e6f69d13 100644 --- a/docs/integrations/google-analytics/installation.md +++ b/docs/integrations/google-analytics/installation.md @@ -1,6 +1,7 @@ --- id: installation title: Google Analytics Installation +sidebar_label: Installation # prettier-ignore description: Adding Google Analytics to Your App --- diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 0b3c8f5915..799433b8ef 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -112,6 +112,11 @@ "type": "subcategory", "label": "LDAP", "ids": ["integrations/ldap/org"] + }, + { + "type": "subcategory", + "label": "Google Analytics", + "ids": ["integrations/google-analytics/installation"] } ], "Plugins": [ diff --git a/mkdocs.yml b/mkdocs.yml index 00824392a7..6b1971ce03 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -70,6 +70,8 @@ nav: - Org Data: 'integrations/github/org.md' - LDAP: - Org Data: 'integrations/ldap/org.md' + - Google Analytics: + - Installation: 'integrations/google-analytics/installation.md' - Plugins: - Overview: 'plugins/index.md' - Existing plugins: 'plugins/existing-plugins.md' From c7a4b73f7f9d0b6ddced5aa14f0efbae7dab03e8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 22 Feb 2021 13:20:11 +0100 Subject: [PATCH 227/270] core-api: move feature flag collection to app.getProvider() --- packages/core-api/src/app/App.tsx | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index 07479ce19b..3cf5abdb44 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -217,8 +217,6 @@ export class PrivateAppImpl implements BackstageApp { getRoutes(): JSX.Element[] { const routes = new Array(); - const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef)!; - const { NotFoundErrorPage } = this.components; for (const plugin of this.plugins.values()) { @@ -252,13 +250,6 @@ export class PrivateAppImpl implements BackstageApp { routes.push(); break; } - case 'feature-flag': { - featureFlagsApi.registerFlag({ - name: output.name, - pluginId: plugin.getId(), - }); - break; - } default: break; } @@ -278,6 +269,25 @@ export class PrivateAppImpl implements BackstageApp { getProvider(): ComponentType<{}> { const appContext = new AppContextImpl(this); + const apiHolder = this.getApiHolder(); + + const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef)!; + + for (const plugin of this.plugins.values()) { + for (const output of plugin.output()) { + switch (output.type) { + case 'feature-flag': { + featureFlagsApi.registerFlag({ + name: output.name, + pluginId: plugin.getId(), + }); + break; + } + default: + break; + } + } + } const Provider = ({ children }: PropsWithChildren<{}>) => { const appThemeApi = useMemo( @@ -315,7 +325,7 @@ export class PrivateAppImpl implements BackstageApp { this.configApi = loadedConfig.api; return ( - + Date: Mon, 22 Feb 2021 13:21:33 +0100 Subject: [PATCH 228/270] core-api: deprecate app.getRoutes --- packages/core-api/src/app/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts index 373839d308..69eb7f7189 100644 --- a/packages/core-api/src/app/types.ts +++ b/packages/core-api/src/app/types.ts @@ -187,6 +187,8 @@ export type BackstageApp = { /** * Routes component that contains all routes for plugin pages in the app. + * + * @deprecated Registering routes in plugins is deprecated and this method will be removed. */ getRoutes(): JSX.Element[]; }; From 1da01d95ed6c36eef8a01e0aced928f48b0f832c Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Mon, 22 Feb 2021 11:08:27 -0500 Subject: [PATCH 229/270] lint add accessors to vale add headers --- .github/styles/vocab.txt | 1 + .../src/alerts/ProjectGrowthAlert.test.tsx | 52 +++++++++++----- .../alerts/UnlabeledDataflowAlert.test.tsx | 60 ++++++++++++++----- 3 files changed, 84 insertions(+), 29 deletions(-) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 2a8854b8a2..c20c0b67c5 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -94,6 +94,7 @@ Zalando Zhou Zolotusky abc +accessors adamdmharvey andrewthauer api diff --git a/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx b/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx index 22b55d85dd..05a10bb2f0 100644 --- a/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx +++ b/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx @@ -1,3 +1,19 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 React from 'react'; import pluralize from 'pluralize'; import { renderInTestApp } from '@backstage/test-utils'; @@ -16,29 +32,27 @@ const mockData: ProjectGrowthData = { aggregation: [0, 0], change: { ratio: 0, - amount: 0 + amount: 0, }, products: [ { id: 'product-a', - aggregation: [0, 0] - } + aggregation: [0, 0], + }, ], }; // suppress recharts componentDidUpdate deprecation warnings -jest.spyOn(console, 'warn').mockImplementation(() => { }); +jest.spyOn(console, 'warn').mockImplementation(() => {}); async function renderInContext(children: JSX.Element) { return renderInTestApp( - - {children} - + {children} - - ) + , + ); } class CustomProjectGrowthAlert extends ProjectGrowthAlert { @@ -46,7 +60,11 @@ class CustomProjectGrowthAlert extends ProjectGrowthAlert { return 'path/to/resource'; } get title() { - return `Investigate cost growth in ${pluralize('project', this.data.products.length, true)}`; + return `Investigate cost growth in ${pluralize( + 'project', + this.data.products.length, + true, + )}`; } } @@ -57,8 +75,12 @@ describe('ProjectGrowthAlert', () => { const { getByText, queryByText } = await renderInContext(alert.element); expect(alert.url).toBe('/cost-insights/investigating-growth'); - expect(alert.title).toBe('Investigate cost growth in project test-project'); - expect(alert.subtitle).toBe('Cost growth outpacing business growth is unsustainable long-term.'); + expect(alert.title).toBe( + 'Investigate cost growth in project test-project', + ); + expect(alert.subtitle).toBe( + 'Cost growth outpacing business growth is unsustainable long-term.', + ); expect(getByText('1 product')).toBeInTheDocument(); expect(queryByText('sorted by cost')).not.toBeInTheDocument(); }); @@ -69,9 +91,11 @@ describe('ProjectGrowthAlert', () => { expect(alert.url).toBe('path/to/resource'); expect(alert.title).toBe('Investigate cost growth in 1 project'); - expect(alert.subtitle).toBe('Cost growth outpacing business growth is unsustainable long-term.'); + expect(alert.subtitle).toBe( + 'Cost growth outpacing business growth is unsustainable long-term.', + ); expect(getByText('1 product')).toBeInTheDocument(); expect(queryByText('sorted by cost')).not.toBeInTheDocument(); }); }); -}) +}); diff --git a/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx b/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx index d6cbd0006f..95e213f06a 100644 --- a/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx +++ b/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx @@ -1,3 +1,19 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 React from 'react'; import pluralize from 'pluralize'; import { renderInTestApp } from '@backstage/test-utils'; @@ -18,24 +34,22 @@ const mockData: UnlabeledDataflowData = { { id: 'project-a', labeledCost: 0, - unlabeledCost: 0 - } - ] + unlabeledCost: 0, + }, + ], }; // suppress recharts componentDidUpdate deprecation warnings -jest.spyOn(console, 'warn').mockImplementation(() => { }); +jest.spyOn(console, 'warn').mockImplementation(() => {}); async function renderInContext(children: JSX.Element) { return renderInTestApp( - - {children} - + {children} - - ) + , + ); } class CustomUnlabeledDataflowAlert extends UnlabeledDataflowAlert { @@ -43,7 +57,11 @@ class CustomUnlabeledDataflowAlert extends UnlabeledDataflowAlert { return 'path/to/resource'; } get title() { - return `Add labels to ${pluralize('workflow', this.data.projects.length, true)}`; + return `Add labels to ${pluralize( + 'workflow', + this.data.projects.length, + true, + )}`; } } @@ -55,8 +73,14 @@ describe('UnlabeledDataflowAlert', () => { expect(alert.url).toBe('/cost-insights/labeling-jobs'); expect(alert.title).toBe('Add labels to workflows'); - expect(alert.subtitle).toBe('Labels show in billing data, enabling cost insights for each workflow.'); - expect(getByText('Showing costs from 1 project with unlabeled Dataflow jobs in the last 30 days.')).toBeInTheDocument(); + expect(alert.subtitle).toBe( + 'Labels show in billing data, enabling cost insights for each workflow.', + ); + expect( + getByText( + 'Showing costs from 1 project with unlabeled Dataflow jobs in the last 30 days.', + ), + ).toBeInTheDocument(); }); it('a subclass can inherit and override defaults using accessors', async () => { @@ -65,8 +89,14 @@ describe('UnlabeledDataflowAlert', () => { expect(alert.url).toBe('path/to/resource'); expect(alert.title).toBe('Add labels to 1 workflow'); - expect(alert.subtitle).toBe('Labels show in billing data, enabling cost insights for each workflow.'); - expect(getByText('Showing costs from 1 project with unlabeled Dataflow jobs in the last 30 days.')).toBeInTheDocument(); + expect(alert.subtitle).toBe( + 'Labels show in billing data, enabling cost insights for each workflow.', + ); + expect( + getByText( + 'Showing costs from 1 project with unlabeled Dataflow jobs in the last 30 days.', + ), + ).toBeInTheDocument(); }); }); -}) +}); From ec504e7b4dce9e962cceee3c25e6268fdf3a0bff Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 22 Feb 2021 17:52:15 +0100 Subject: [PATCH 230/270] auth-backend: fix for refresh token being lost during microsoft login --- .changeset/breezy-jobs-brake.md | 5 +++++ plugins/auth-backend/src/providers/github/provider.ts | 4 ++-- plugins/auth-backend/src/providers/gitlab/provider.ts | 4 ++-- plugins/auth-backend/src/providers/microsoft/provider.ts | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 .changeset/breezy-jobs-brake.md diff --git a/.changeset/breezy-jobs-brake.md b/.changeset/breezy-jobs-brake.md new file mode 100644 index 0000000000..412cff3df5 --- /dev/null +++ b/.changeset/breezy-jobs-brake.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Fix for refresh token being lost during Microsoft login. diff --git a/plugins/auth-backend/src/providers/github/provider.ts b/plugins/auth-backend/src/providers/github/provider.ts index 438cbca07b..2b0db9c2d9 100644 --- a/plugins/auth-backend/src/providers/github/provider.ts +++ b/plugins/auth-backend/src/providers/github/provider.ts @@ -54,12 +54,12 @@ export class GithubAuthProvider implements OAuthHandlers { }, ( accessToken: any, - refreshToken: any, + _refreshToken: any, params: any, fullProfile: any, done: PassportDoneCallback, ) => { - done(undefined, { fullProfile, params, accessToken, refreshToken }); + done(undefined, { fullProfile, params, accessToken }); }, ); } diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index 7c24757833..630cad0554 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -51,12 +51,12 @@ export class GitlabAuthProvider implements OAuthHandlers { }, ( accessToken: any, - refreshToken: any, + _refreshToken: any, params: any, fullProfile: any, done: PassportDoneCallback, ) => { - done(undefined, { fullProfile, params, accessToken, refreshToken }); + done(undefined, { fullProfile, params, accessToken }); }, ); } diff --git a/plugins/auth-backend/src/providers/microsoft/provider.ts b/plugins/auth-backend/src/providers/microsoft/provider.ts index 4dee90d4c6..e6157c1a83 100644 --- a/plugins/auth-backend/src/providers/microsoft/provider.ts +++ b/plugins/auth-backend/src/providers/microsoft/provider.ts @@ -72,7 +72,7 @@ export class MicrosoftAuthProvider implements OAuthHandlers { fullProfile: passport.Profile, done: PassportDoneCallback, ) => { - done(undefined, { fullProfile, accessToken, refreshToken, params }); + done(undefined, { fullProfile, accessToken, params }, { refreshToken }); }, ); } From e780e119c9fe5ad7312c72f57fe0a888911807a3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 22 Feb 2021 18:07:51 +0100 Subject: [PATCH 231/270] cli: add missing file-loader dependency --- .changeset/ninety-falcons-applaud.md | 5 +++++ packages/cli/package.json | 1 + yarn.lock | 17 +++++++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 .changeset/ninety-falcons-applaud.md diff --git a/.changeset/ninety-falcons-applaud.md b/.changeset/ninety-falcons-applaud.md new file mode 100644 index 0000000000..7d6c941053 --- /dev/null +++ b/.changeset/ninety-falcons-applaud.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Add missing `file-loader` dependency which could cause issues with loading images and other assets. diff --git a/packages/cli/package.json b/packages/cli/package.json index 5be3a2c695..a5dddefcd6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -74,6 +74,7 @@ "eslint-plugin-react": "^7.12.4", "eslint-plugin-react-hooks": "^4.0.0", "express": "^4.17.1", + "file-loader": "^6.2.0", "fork-ts-checker-webpack-plugin": "^4.0.5", "fs-extra": "^9.0.0", "handlebars": "^4.7.3", diff --git a/yarn.lock b/yarn.lock index 90e7e8bb14..bbe7673ed6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12924,6 +12924,14 @@ file-loader@^6.0.0: loader-utils "^2.0.0" schema-utils "^2.7.1" +file-loader@^6.2.0: + version "6.2.0" + resolved "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" + integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + file-system-cache@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/file-system-cache/-/file-system-cache-1.0.5.tgz#84259b36a2bbb8d3d6eb1021d3132ffe64cfff4f" @@ -22752,6 +22760,15 @@ schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0, schema-utils@^2.7 ajv "^6.12.4" ajv-keywords "^3.5.2" +schema-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef" + integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA== + dependencies: + "@types/json-schema" "^7.0.6" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + screenfull@^5.0.0: version "5.0.2" resolved "https://registry.npmjs.org/screenfull/-/screenfull-5.0.2.tgz#b9acdcf1ec676a948674df5cd0ff66b902b0bed7" From d8fe494bd1eaa39ba54caa121a2d66be9e1cfd86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 22 Feb 2021 20:37:02 +0100 Subject: [PATCH 232/270] Deprecation message for POST /entities --- plugins/catalog-backend/src/service/router.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/catalog-backend/src/service/router.ts b/plugins/catalog-backend/src/service/router.ts index 17cfc77d8b..5936e3a747 100644 --- a/plugins/catalog-backend/src/service/router.ts +++ b/plugins/catalog-backend/src/service/router.ts @@ -60,6 +60,16 @@ export async function createRouter( res.status(200).json(entities.map(fieldMapper)); }) .post('/entities', async (req, res) => { + /* + * NOTE: THIS METHOD IS DEPRECATED AND NOT RECOMMENDED TO USE + * + * Posting entities to this method has unclear semantics and will not + * properly subject them to limitations, processing, or resolution of + * relations. + * + * It stays around in the service for the time being, but may be + * removed or change semantics at any time without prior notice. + */ const body = await requireRequestBody(req); const [result] = await entitiesCatalog.batchAddOrUpdateEntities([ { entity: body as Entity, relations: [] }, From 5c2e2863f45b3e3238b364197f5bc82d63faab0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 19 Feb 2021 11:41:23 +0100 Subject: [PATCH 233/270] Added the proper type parameters to entityRouteRef --- .changeset/selfish-onions-count.md | 5 +++++ plugins/catalog-react/src/routes.ts | 1 + 2 files changed, 6 insertions(+) create mode 100644 .changeset/selfish-onions-count.md diff --git a/.changeset/selfish-onions-count.md b/.changeset/selfish-onions-count.md new file mode 100644 index 0000000000..ee4ea23ae8 --- /dev/null +++ b/.changeset/selfish-onions-count.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Added the proper type parameters to entityRouteRef. diff --git a/plugins/catalog-react/src/routes.ts b/plugins/catalog-react/src/routes.ts index 0fced32f59..7470b8030e 100644 --- a/plugins/catalog-react/src/routes.ts +++ b/plugins/catalog-react/src/routes.ts @@ -31,6 +31,7 @@ export const entityRoute = createRouteRef({ icon: NoIcon, path: ':namespace/:kind/:name/*', title: 'Entity', + params: ['namespace', 'kind', 'name'], }); export const entityRouteRef = entityRoute; From 5aa4ceea6ebedcdc81b8cc9d8ddd0fb317c52f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 22 Feb 2021 20:28:15 +0100 Subject: [PATCH 234/270] Make sure to provide dummy routes for all external routes of plugins given to DevApp --- .changeset/bright-dolphins-mate.md | 5 +++ packages/dev-utils/src/devApp/render.tsx | 45 ++++++++++++++++-------- 2 files changed, 36 insertions(+), 14 deletions(-) create mode 100644 .changeset/bright-dolphins-mate.md diff --git a/.changeset/bright-dolphins-mate.md b/.changeset/bright-dolphins-mate.md new file mode 100644 index 0000000000..ac099c88f0 --- /dev/null +++ b/.changeset/bright-dolphins-mate.md @@ -0,0 +1,5 @@ +--- +'@backstage/dev-utils': patch +--- + +Make sure to provide dummy routes for all external routes of plugins given to DevApp diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx index e1bdf4d0e9..0c3a1395a6 100644 --- a/packages/dev-utils/src/devApp/render.tsx +++ b/packages/dev-utils/src/devApp/render.tsx @@ -14,26 +14,30 @@ * limitations under the License. */ -import { hot } from 'react-hot-loader'; -import React, { ComponentType, ReactNode } from 'react'; -import ReactDOM from 'react-dom'; -import BookmarkIcon from '@material-ui/icons/Bookmark'; import { + AlertDisplay, + AnyApiFactory, + ApiFactory, + attachComponentData, createApp, - SidebarPage, + createPlugin, + createRouteRef, + FlatRoutes, + IconComponent, + OAuthRequestDialog, + RouteRef, Sidebar, SidebarItem, + SidebarPage, SidebarSpacer, - ApiFactory, - createPlugin, - AlertDisplay, - OAuthRequestDialog, - AnyApiFactory, - IconComponent, - FlatRoutes, - attachComponentData, } from '@backstage/core'; +import { Box } from '@material-ui/core'; +import BookmarkIcon from '@material-ui/icons/Bookmark'; import SentimentDissatisfiedIcon from '@material-ui/icons/SentimentDissatisfied'; +import React, { ComponentType, ReactNode } from 'react'; +import ReactDOM from 'react-dom'; +import { hot } from 'react-hot-loader'; +import { Route } from 'react-router'; const GatheringRoute: (props: { path: string; @@ -128,9 +132,22 @@ class DevAppBuilder { * Build a DevApp component using the resources registered so far */ build(): ComponentType<{}> { + const dummyRouteRef = createRouteRef({ title: 'Page of another plugin' }); + const DummyPage = () => Page belonging to another plugin.; + attachComponentData(DummyPage, 'core.mountPoint', dummyRouteRef); + const app = createApp({ apis: this.apis, plugins: this.plugins, + bindRoutes: ({ bind }) => { + for (const plugin of this.plugins ?? []) { + const targets: Record> = {}; + for (const routeKey of Object.keys(plugin.externalRoutes)) { + targets[routeKey] = dummyRouteRef; + } + bind(plugin.externalRoutes, targets); + } + }, }); const AppProvider = app.getProvider(); @@ -145,13 +162,13 @@ class DevAppBuilder { {this.rootChildren} - {sidebar} {this.routes} {deprecatedAppRoutes} + } /> From 7a1b2ba0e70819ea5d63d4914eafb6ced21e7d71 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 22 Feb 2021 13:01:59 +0100 Subject: [PATCH 235/270] create-app: migrate top-level routes to use extensions --- .changeset/stale-ducks-sing.md | 56 +++++++++++++++++++ .../default-app/packages/app/src/App.tsx | 23 ++++---- 2 files changed, 66 insertions(+), 13 deletions(-) create mode 100644 .changeset/stale-ducks-sing.md diff --git a/.changeset/stale-ducks-sing.md b/.changeset/stale-ducks-sing.md new file mode 100644 index 0000000000..59eb956fd0 --- /dev/null +++ b/.changeset/stale-ducks-sing.md @@ -0,0 +1,56 @@ +--- +'@backstage/create-app': patch +--- + +Migrated away from using deprecated routes and router components at top-level in the app, and instead use routable extension pages. + +To apply this change to an existing app, make the following changes to `packages/app/src/App.tsx`: + +Update imports and remove the usage of the deprecated `app.getRoutes()`. + +```diff +-import { Router as DocsRouter } from '@backstage/plugin-techdocs'; ++import { TechdocsPage } from '@backstage/plugin-techdocs'; + import { CatalogImportPage } from '@backstage/plugin-catalog-import'; +-import { Router as TechRadarRouter } from '@backstage/plugin-tech-radar'; +-import { SearchPage as SearchRouter } from '@backstage/plugin-search'; +-import { Router as SettingsRouter } from '@backstage/plugin-user-settings'; ++import { TechRadarPage } from '@backstage/plugin-tech-radar'; ++import { SearchPage } from '@backstage/plugin-search'; ++import { UserSettingsPage } from '@backstage/plugin-user-settings'; ++import { ApiExplorerPage } from '@backstage/plugin-api-docs'; + import { EntityPage } from './components/catalog/EntityPage'; + import { scaffolderPlugin, ScaffolderPage } from '@backstage/plugin-scaffolder'; + + + const AppProvider = app.getProvider(); + const AppRouter = app.getRouter(); +-const deprecatedAppRoutes = app.getRoutes(); +``` + +As well as update or add the following routes: + +```diff + } /> +- } /> ++ } /> ++ } /> + } ++ element={} + /> + } /> +- } +- /> +- } /> +- {deprecatedAppRoutes} ++ } /> ++ } /> +``` + +If you have added additional plugins with registered routes or are using `Router` components from other plugins, these should be migrated to use the `*Page` components as well. See [this commit](https://github.com/backstage/backstage/commit/abd655e42d4ed416b70848ffdb1c4b99d189f13b) for more examples of how to migrate. + +For more information and the background to this change, see the [composability system migration docs](https://backstage.io/docs/plugins/composability). diff --git a/packages/create-app/templates/default-app/packages/app/src/App.tsx b/packages/create-app/templates/default-app/packages/app/src/App.tsx index cf31647ce0..d4f1178899 100644 --- a/packages/create-app/templates/default-app/packages/app/src/App.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/App.tsx @@ -15,11 +15,12 @@ import { CatalogIndexPage, CatalogEntityPage, } from '@backstage/plugin-catalog'; -import { Router as DocsRouter } from '@backstage/plugin-techdocs'; +import { TechdocsPage } from '@backstage/plugin-techdocs'; import { CatalogImportPage } from '@backstage/plugin-catalog-import'; -import { Router as TechRadarRouter } from '@backstage/plugin-tech-radar'; -import { SearchPage as SearchRouter } from '@backstage/plugin-search'; -import { Router as SettingsRouter } from '@backstage/plugin-user-settings'; +import { TechRadarPage } from '@backstage/plugin-tech-radar'; +import { SearchPage } from '@backstage/plugin-search'; +import { UserSettingsPage } from '@backstage/plugin-user-settings'; +import { ApiExplorerPage } from '@backstage/plugin-api-docs'; import { EntityPage } from './components/catalog/EntityPage'; import { scaffolderPlugin, ScaffolderPage } from '@backstage/plugin-scaffolder'; @@ -36,7 +37,6 @@ const app = createApp({ const AppProvider = app.getProvider(); const AppRouter = app.getRouter(); -const deprecatedAppRoutes = app.getRoutes(); const App = () => ( @@ -54,19 +54,16 @@ const App = () => ( > - } /> + } /> } /> + } /> } + element={} /> } /> - } - /> - } /> - {deprecatedAppRoutes} + } /> + } /> From 3af994c81f65d40bbb29d381d1ad195d21f4ca2b Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Mon, 22 Feb 2021 12:42:09 -0800 Subject: [PATCH 236/270] Expose a configuration option for the oidc scope --- .changeset/sweet-experts-whisper.md | 5 +++ .../src/providers/oidc/provider.ts | 45 +++++++++++-------- 2 files changed, 31 insertions(+), 19 deletions(-) create mode 100644 .changeset/sweet-experts-whisper.md diff --git a/.changeset/sweet-experts-whisper.md b/.changeset/sweet-experts-whisper.md new file mode 100644 index 0000000000..ca2f89ceb7 --- /dev/null +++ b/.changeset/sweet-experts-whisper.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Expose a configuration option for the oidc scope diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index 0ec8d0bbf7..c7df8c1e7e 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -55,14 +55,17 @@ type AuthResult = { export type Options = OAuthProviderOptions & { metadataUrl: string; + scope?: string; tokenSignedResponseAlg?: string; }; export class OidcAuthProvider implements OAuthHandlers { private readonly implementation: Promise; + private readonly scope?: string; constructor(options: Options) { this.implementation = this.setupStrategy(options); + this.scope = options.scope; } async start(req: OAuthStartRequest): Promise { @@ -70,7 +73,7 @@ export class OidcAuthProvider implements OAuthHandlers { return await executeRedirectStrategy(req, strategy, { accessType: 'offline', prompt: 'none', - scope: req.scope, + scope: req.scope || this.scope || '', state: encodeState(req.state), }); } @@ -79,28 +82,29 @@ export class OidcAuthProvider implements OAuthHandlers { req: express.Request, ): Promise<{ response: OAuthResponse; refreshToken?: string }> { const { strategy } = await this.implementation; + const strategyResponse = await executeFrameHandlerStrategy< + AuthResult, + PrivateInfo + >(req, strategy); const { result: { userinfo, tokenset }, privateInfo, - } = await executeFrameHandlerStrategy( - req, - strategy, - ); - + } = strategyResponse; + const identityResponse = await this.populateIdentity({ + profile: { + displayName: userinfo.name, + email: userinfo.email, + picture: userinfo.picture, + }, + providerInfo: { + idToken: tokenset.id_token, + accessToken: tokenset.access_token || '', + scope: tokenset.scope || '', + expiresInSeconds: tokenset.expires_in, + }, + }); return { - response: await this.populateIdentity({ - profile: { - displayName: userinfo.name, - email: userinfo.email, - picture: userinfo.picture, - }, - providerInfo: { - idToken: tokenset.id_token, - accessToken: tokenset.access_token || '', - scope: tokenset.scope || '', - expiresInSeconds: tokenset.expires_in, - }, - }), + response: identityResponse, refreshToken: privateInfo.refreshToken, }; } @@ -133,6 +137,7 @@ export class OidcAuthProvider implements OAuthHandlers { redirect_uris: [options.callbackUrl], response_types: ['code'], id_token_signed_response_alg: options.tokenSignedResponseAlg || 'RS256', + scope: options.scope || '', }); const strategy = new OidcStrategy( @@ -188,6 +193,7 @@ export const createOidcProvider = ( const tokenSignedResponseAlg = envConfig.getString( 'tokenSignedResponseAlg', ); + const scope = envConfig.getOptionalString('scope'); const provider = new OidcAuthProvider({ clientId, @@ -195,6 +201,7 @@ export const createOidcProvider = ( callbackUrl, tokenSignedResponseAlg, metadataUrl, + scope, }); return OAuthAdapter.fromConfig(globalConfig, provider, { From f31e1061ca3d0055699e1bc7ff39742789584ef2 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Mon, 22 Feb 2021 16:39:06 -0800 Subject: [PATCH 237/270] Replaced the config jest mock with a ConfigReader in the oidc provider test --- .../src/providers/oidc/provider.test.ts | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/plugins/auth-backend/src/providers/oidc/provider.test.ts b/plugins/auth-backend/src/providers/oidc/provider.test.ts index a746c0573b..9a23f701e5 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.test.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.test.ts @@ -21,7 +21,7 @@ import { ClientMetadata, IssuerMetadata } from 'openid-client'; import { createOidcProvider, OidcAuthProvider } from './provider'; import { JWT, JWK } from 'jose'; import { AuthProviderFactoryOptions } from '../types'; -import { Config } from '@backstage/config'; +import { Config, ConfigReader } from '@backstage/config'; import { OAuthAdapter } from '../../lib/oauth'; const issuerMetadata = { @@ -103,23 +103,18 @@ describe('OidcAuthProvider', () => { const scope = nock('https://oidc.test') .get('/.well-known/openid-configuration') .reply(200, issuerMetadata); + const config: Config = new ConfigReader({ + testEnv: { + ...clientMetadata, + metadataUrl: 'https://oidc.test/.well-known/openid-configuration', + }, + }); const options = { globalConfig: { appUrl: 'https://oidc.test', baseUrl: 'https://oidc.test', }, - config: ({ - keys: jest.fn(() => ['test']), - getConfig: jest.fn(() => ({ - getString: (key: string) => { - const conf = { - ...clientMetadata, - metadataUrl: 'https://oidc.test/.well-known/openid-configuration', - } as any; - return conf[key] as string; - }, - })), - } as any) as Config, + config, } as AuthProviderFactoryOptions; const provider = createOidcProvider()(options) as OAuthAdapter; expect(provider.start).toBeDefined(); From 3b22f4b977a924ec2e59fe8a855a3e99397d12d8 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Mon, 22 Feb 2021 21:11:46 -0500 Subject: [PATCH 238/270] Add Jenkins logo --- plugins/jenkins/src/assets/JenkinsLogo.svg | 283 ++++++++++++++++++ .../BuildsPage/lib/CITable/CITable.tsx | 8 +- 2 files changed, 287 insertions(+), 4 deletions(-) create mode 100644 plugins/jenkins/src/assets/JenkinsLogo.svg diff --git a/plugins/jenkins/src/assets/JenkinsLogo.svg b/plugins/jenkins/src/assets/JenkinsLogo.svg new file mode 100644 index 0000000000..4a4538daf6 --- /dev/null +++ b/plugins/jenkins/src/assets/JenkinsLogo.svg @@ -0,0 +1,283 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx b/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx index 2d8f43ca80..3044094ccc 100644 --- a/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx +++ b/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { Box, IconButton, Link, Typography, Tooltip } from '@material-ui/core'; import RetryIcon from '@material-ui/icons/Replay'; -import GitHubIcon from '@material-ui/icons/GitHub'; +import JenkinsLogo from '../../../../assets/JenkinsLogo.svg'; import { generatePath, Link as RouterLink } from 'react-router-dom'; import { Table, TableColumn } from '@backstage/core'; import { JenkinsRunStatus } from '../Status'; @@ -231,9 +231,9 @@ export const CITableView = ({ onChangeRowsPerPage={onChangePageSize} title={ - - - {projectName} + Jenkins logo + + Project: {projectName} } columns={generatedColumns} From 5e67788a300fbdfa1f95f5e752cee53ac693b9d1 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Mon, 22 Feb 2021 21:12:18 -0500 Subject: [PATCH 239/270] Use new Backstage breadcrumb --- .../components/BuildWithStepsPage/BuildWithStepsPage.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx b/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx index d8f728bb27..23618dec9e 100644 --- a/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx +++ b/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx @@ -15,10 +15,9 @@ */ import React from 'react'; import { useParams } from 'react-router-dom'; -import { Content, Link } from '@backstage/core'; +import { Breadcrumbs, Content, Link } from '@backstage/core'; import { Typography, - Breadcrumbs, Paper, TableContainer, Table, @@ -36,7 +35,6 @@ import ExternalLinkIcon from '@material-ui/icons/Launch'; const useStyles = makeStyles(theme => ({ root: { maxWidth: 720, - margin: theme.spacing(2), }, table: { padding: theme.spacing(1), @@ -57,9 +55,10 @@ const BuildWithStepsView = () => { return (
- Jobs + Projects Run +
From 0452ba238c9afdfd72e57901638e0cedd60f77d7 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Mon, 22 Feb 2021 21:15:39 -0500 Subject: [PATCH 240/270] Add changeset --- .changeset/three-pumpkins-watch.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/three-pumpkins-watch.md diff --git a/.changeset/three-pumpkins-watch.md b/.changeset/three-pumpkins-watch.md new file mode 100644 index 0000000000..bc686c77c4 --- /dev/null +++ b/.changeset/three-pumpkins-watch.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-jenkins': patch +--- + +Add Jenkins logo to project page. +Move to new Backstage breadcrumb component. +Change references of deprecated "Job" terminology to "Project" per [Jenkins Glossary](https://www.jenkins.io/doc/book/glossary/). From db75434e40c3a2b7ed5ca03316dd55f339a8e493 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Mon, 22 Feb 2021 21:21:17 -0500 Subject: [PATCH 241/270] Update doc refs of deprecated naming --- plugins/jenkins/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/jenkins/README.md b/plugins/jenkins/README.md index 154fcf913d..13ff9b3547 100644 --- a/plugins/jenkins/README.md +++ b/plugins/jenkins/README.md @@ -50,7 +50,7 @@ metadata: name: 'your-component' description: 'a description' annotations: - jenkins.io/github-folder: 'folder-name/job-name' + jenkins.io/github-folder: 'folder-name/project-name' spec: type: service lifecycle: experimental @@ -84,5 +84,5 @@ YWRtaW46MTFlYzI1NmU0Mzg1MDFjM2Y1Yzc2Yjc1MWE3ZTQ3YWY4Mw== is the base64 of user a ## Limitations -- Only works with organization folder jobs backed by GitHub -- No pagination support currently, limited to 50 jobs - don't run this on a Jenkins with lots of builds +- Only works with organization folder projects backed by GitHub +- No pagination support currently, limited to 50 projects - don't run this on a Jenkins with lots of builds From c70702c048c6ba77926689ecaac488969c0d04af Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Mon, 22 Feb 2021 22:48:01 -0500 Subject: [PATCH 242/270] Bullet changeset --- .changeset/three-pumpkins-watch.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/three-pumpkins-watch.md b/.changeset/three-pumpkins-watch.md index bc686c77c4..cb059a5381 100644 --- a/.changeset/three-pumpkins-watch.md +++ b/.changeset/three-pumpkins-watch.md @@ -2,6 +2,6 @@ '@backstage/plugin-jenkins': patch --- -Add Jenkins logo to project page. -Move to new Backstage breadcrumb component. -Change references of deprecated "Job" terminology to "Project" per [Jenkins Glossary](https://www.jenkins.io/doc/book/glossary/). +- Add Jenkins logo to project page. +- Move to new Backstage breadcrumb component. +- Change references of deprecated "Job" terminology to "Project" per [Jenkins Glossary](https://www.jenkins.io/doc/book/glossary/). From 4875f2da2ce7a4a60d9e6300e35bb831e8b97839 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Feb 2021 04:38:56 +0000 Subject: [PATCH 243/270] chore(deps): bump @material-ui/icons from 4.9.1 to 4.11.2 Bumps [@material-ui/icons](https://github.com/mui-org/material-ui/tree/HEAD/packages/material-ui-icons) from 4.9.1 to 4.11.2. - [Release notes](https://github.com/mui-org/material-ui/releases) - [Changelog](https://github.com/mui-org/material-ui/blob/v4.11.2/CHANGELOG.md) - [Commits](https://github.com/mui-org/material-ui/commits/v4.11.2/packages/material-ui-icons) Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index bbe7673ed6..29500d6dd4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3845,9 +3845,9 @@ react-transition-group "^4.4.0" "@material-ui/icons@^4.9.1": - version "4.9.1" - resolved "https://registry.npmjs.org/@material-ui/icons/-/icons-4.9.1.tgz#fdeadf8cb3d89208945b33dbc50c7c616d0bd665" - integrity sha512-GBitL3oBWO0hzBhvA9KxqcowRUsA0qzwKkURyC8nppnC3fw54KPKZ+d4V1Eeg/UnDRSzDaI9nGCdel/eh9AQMg== + version "4.11.2" + resolved "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.2.tgz#b3a7353266519cd743b6461ae9fdfcb1b25eb4c5" + integrity sha512-fQNsKX2TxBmqIGJCSi3tGTO/gZ+eJgWmMJkgDiOfyNaunNaxcklJQFaFogYcFl0qFuaEz1qaXYXboa/bUXVSOQ== dependencies: "@babel/runtime" "^7.4.4" From bc1cba61f57c81db0b1bd1f5ce28b422dc5e8305 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Feb 2021 04:39:51 +0000 Subject: [PATCH 244/270] chore(deps-dev): bump @types/xml2js from 0.4.7 to 0.4.8 Bumps [@types/xml2js](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/xml2js) from 0.4.7 to 0.4.8. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/xml2js) Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index bbe7673ed6..9509a6a4ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6893,9 +6893,9 @@ "@types/node" "*" "@types/xml2js@^0.4.7": - version "0.4.7" - resolved "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.7.tgz#cd5b6c67bbec741ac625718a76e6cb99bc34365e" - integrity sha512-f5VOKSMEE0O+/L54FHwA/a7vcx9mHeSDM71844yHCOhh8Cin2xQa0UFw0b7Vc5hoZ3Ih6ZHaDobjfLih4tWPNw== + version "0.4.8" + resolved "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.8.tgz#84c120c864a5976d0b5cf2f930a75d850fc2b03a" + integrity sha512-EyvT83ezOdec7BhDaEcsklWy7RSIdi6CNe95tmOAK0yx/Lm30C9K75snT3fYayK59ApC2oyW+rcHErdG05FHJA== dependencies: "@types/node" "*" From 18f65d030f342d4ba4623a93b21c1e29180d888e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Feb 2021 04:40:52 +0000 Subject: [PATCH 245/270] chore(deps): bump apollo-server-express from 2.16.1 to 2.21.0 Bumps [apollo-server-express](https://github.com/apollographql/apollo-server/tree/HEAD/packages/apollo-server-express) from 2.16.1 to 2.21.0. - [Release notes](https://github.com/apollographql/apollo-server/releases) - [Changelog](https://github.com/apollographql/apollo-server/blob/main/CHANGELOG.md) - [Commits](https://github.com/apollographql/apollo-server/commits/apollo-server-express@2.21.0/packages/apollo-server-express) Signed-off-by: dependabot[bot] --- yarn.lock | 264 +++++++++++++++++++++++++----------------------------- 1 file changed, 120 insertions(+), 144 deletions(-) diff --git a/yarn.lock b/yarn.lock index bbe7673ed6..1ff72fb001 100644 --- a/yarn.lock +++ b/yarn.lock @@ -44,6 +44,19 @@ dependencies: xss "^1.0.6" +"@apollographql/graphql-upload-8-fork@^8.1.3": + version "8.1.3" + resolved "https://registry.npmjs.org/@apollographql/graphql-upload-8-fork/-/graphql-upload-8-fork-8.1.3.tgz#a0d4e0d5cec8e126d78bd915c264d6b90f5784bc" + integrity sha512-ssOPUT7euLqDXcdVv3Qs4LoL4BPtfermW1IOouaqEmj36TpHYDmYDIbKoSQxikd9vtMumFnP87OybH7sC9fJ6g== + dependencies: + "@types/express" "*" + "@types/fs-capacitor" "*" + "@types/koa" "*" + busboy "^0.3.1" + fs-capacitor "^2.0.4" + http-errors "^1.7.3" + object-path "^0.11.4" + "@ardatan/aggregate-error@0.0.1": version "0.0.1" resolved "https://registry.npmjs.org/@ardatan/aggregate-error/-/aggregate-error-0.0.1.tgz#1403ac5de10d8ca689fc1f65844c27179ae1d44f" @@ -5783,7 +5796,14 @@ resolved "https://registry.npmjs.org/@types/core-js/-/core-js-2.5.4.tgz#fc42ebde7d9cfa7c5f2668f117449b02348e41fd" integrity sha512-Xwy8o12ak+iYgFr/KCVaVK5Sy+jFMiiPAID3+ObvMlBzy26XQJw5xu+a6rlHsrJENXj/AwJOGsJpVohUjAzSKQ== -"@types/cors@^2.8.4", "@types/cors@^2.8.6": +"@types/cors@2.8.8": + version "2.8.8" + resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.8.tgz#317a8d8561995c60e35b9e0fcaa8d36660c98092" + integrity sha512-fO3gf3DxU2Trcbr75O7obVndW/X5k8rJNZkLXlQWStTHhP71PkRqjwPIEI0yMnJdg9R9OasjU+Bsr+Hr1xy/0w== + dependencies: + "@types/express" "*" + +"@types/cors@^2.8.6": version "2.8.9" resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.9.tgz#4bd1fcac72eca8d5bec93e76c7fdcbdc1bc2cd4a" integrity sha512-zurD1ibz21BRlAOIKP8yhrxlqKx6L9VCwkB5kMiP6nZAhoF5MvC7qS1qPA7nRcr1GJolfkQC7/EAL4hdYejLtg== @@ -5884,7 +5904,7 @@ resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.5": +"@types/express-serve-static-core@*", "@types/express-serve-static-core@4.17.18", "@types/express-serve-static-core@^4.17.5": version "4.17.18" resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.18.tgz#8371e260f40e0e1ca0c116a9afcd9426fa094c40" integrity sha512-m4JTwx5RUBNZvky/JJ8swEJPKFd8si08pPF2PfizYjGZOKr/svUWPcoUmLow6MmPzhasphB7gSTINY67xn3JNA== @@ -5973,16 +5993,6 @@ dependencies: "@types/node" "*" -"@types/graphql-upload@^8.0.0": - version "8.0.3" - resolved "https://registry.npmjs.org/@types/graphql-upload/-/graphql-upload-8.0.3.tgz#b371edb5f305a2a1f7b7843a890a2a7adc55c3ec" - integrity sha512-hmLg9pCU/GmxBscg8GCr1vmSoEmbItNNxdD5YH2TJkXm//8atjwuprB+xJBK714JG1dkxbbhp5RHX+Pz1KsCMA== - dependencies: - "@types/express" "*" - "@types/fs-capacitor" "*" - "@types/koa" "*" - graphql "^14.5.3" - "@types/hast@^2.0.0": version "2.3.1" resolved "https://registry.npmjs.org/@types/hast/-/hast-2.3.1.tgz#b16872f2a6144c7025f296fb9636a667ebb79cd9" @@ -7471,43 +7481,21 @@ anymatch@^3.0.3, anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" -apollo-cache-control@^0.11.1: - version "0.11.1" - resolved "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.11.1.tgz#3bce0924ae7322a8b9f7ca1e2fb036d1fc9f1df5" - integrity sha512-6iHa8TkcKt4rx5SKRzDNjUIpCQX+7/FlZwD7vRh9JDnM4VH8SWhpj8fUR3CiEY8Kuc4ChXnOY8bCcMju5KPnIQ== +apollo-cache-control@^0.11.6: + version "0.11.6" + resolved "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.11.6.tgz#f7bdf924272af47ac474cf3f3f35cfc038cc9485" + integrity sha512-YZ+uuIG+fPy+mkpBS2qKF0v1qlzZ3PW6xZVaDukeK3ed3iAs4L/2YnkTqau3OmoF/VPzX2FmSkocX/OVd59YSw== dependencies: - apollo-server-env "^2.4.5" - apollo-server-plugin-base "^0.9.1" + apollo-server-env "^3.0.0" + apollo-server-plugin-base "^0.10.4" -apollo-datasource@^0.7.2: - version "0.7.2" - resolved "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.7.2.tgz#1662ee93453a9b89af6f73ce561bde46b41ebf31" - integrity sha512-ibnW+s4BMp4K2AgzLEtvzkjg7dJgCaw9M5b5N0YKNmeRZRnl/I/qBTQae648FsRKgMwTbRQIvBhQ0URUFAqFOw== +apollo-datasource@^0.7.3: + version "0.7.3" + resolved "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.7.3.tgz#c824eb1457bdee5a3173ced0e35e594547e687a0" + integrity sha512-PE0ucdZYjHjUyXrFWRwT02yLcx2DACsZ0jm1Mp/0m/I9nZu/fEkvJxfsryXB6JndpmQO77gQHixf/xGCN976kA== dependencies: - apollo-server-caching "^0.5.2" - apollo-server-env "^2.4.5" - -apollo-engine-reporting-protobuf@^0.5.2: - version "0.5.2" - resolved "https://registry.npmjs.org/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.5.2.tgz#b01812508a1c583328a8dc603769bc63b8895e7e" - integrity sha512-4wm9FR3B7UvJxcK/69rOiS5CAJPEYKufeRWb257ZLfX7NGFTMqvbc1hu4q8Ch7swB26rTpkzfsftLED9DqH9qg== - dependencies: - "@apollo/protobufjs" "^1.0.3" - -apollo-engine-reporting@^2.3.0: - version "2.3.0" - resolved "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-2.3.0.tgz#3bb59f81aaf6b967ed098896a4a60a053b4eed5a" - integrity sha512-SbcPLFuUZcRqDEZ6mSs8uHM9Ftr8yyt2IEu0JA8c3LNBmYXSLM7MHqFe80SVcosYSTBgtMz8mLJO8orhYoSYZw== - dependencies: - apollo-engine-reporting-protobuf "^0.5.2" - apollo-graphql "^0.5.0" - apollo-server-caching "^0.5.2" - apollo-server-env "^2.4.5" - apollo-server-errors "^2.4.2" - apollo-server-plugin-base "^0.9.1" - apollo-server-types "^0.5.1" - async-retry "^1.2.1" - uuid "^8.0.0" + apollo-server-caching "^0.5.3" + apollo-server-env "^3.0.0" apollo-env@^0.6.5: version "0.6.5" @@ -7519,12 +7507,22 @@ apollo-env@^0.6.5: node-fetch "^2.2.0" sha.js "^2.4.11" -apollo-graphql@^0.5.0: - version "0.5.0" - resolved "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.5.0.tgz#7e9152093211b58352aa6504d8d39ec7241d6872" - integrity sha512-YSdF/BKPbsnQpxWpmCE53pBJX44aaoif31Y22I/qKpB6ZSGzYijV5YBoCL5Q15H2oA/v/02Oazh9lbp4ek3eig== +apollo-env@^0.6.6: + version "0.6.6" + resolved "https://registry.npmjs.org/apollo-env/-/apollo-env-0.6.6.tgz#d7880805c4e96ee3d4142900a405176a04779438" + integrity sha512-hXI9PjJtzmD34XviBU+4sPMOxnifYrHVmxpjykqI/dUD2G3yTiuRaiQqwRwB2RCdwC1Ug/jBfoQ/NHDTnnjndQ== dependencies: - apollo-env "^0.6.5" + "@types/node-fetch" "2.5.7" + core-js "^3.0.1" + node-fetch "^2.2.0" + sha.js "^2.4.11" + +apollo-graphql@^0.6.0: + version "0.6.1" + resolved "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.6.1.tgz#d0bf0aff76f445de3da10e08f6974f1bf65f5753" + integrity sha512-ZRXAV+k+hboCVS+FW86FW/QgnDR7gm/xMUwJPGXEbV53OLGuQQdIT0NCYK7AzzVkCfsbb7NJ3mmEclkZY9uuxQ== + dependencies: + apollo-env "^0.6.6" lodash.sortby "^4.7.0" apollo-link-http-common@^0.2.14: @@ -7546,6 +7544,13 @@ apollo-link@^1.2.12, apollo-link@^1.2.14: tslib "^1.9.3" zen-observable-ts "^0.8.21" +apollo-reporting-protobuf@^0.6.2: + version "0.6.2" + resolved "https://registry.npmjs.org/apollo-reporting-protobuf/-/apollo-reporting-protobuf-0.6.2.tgz#5572866be9b77f133916532b10e15fbaa4158304" + integrity sha512-WJTJxLM+MRHNUxt1RTl4zD0HrLdH44F2mDzMweBj1yHL0kSt8I1WwoiF/wiGVSpnG48LZrBegCaOJeuVbJTbtw== + dependencies: + "@apollo/protobufjs" "^1.0.3" + apollo-server-caching@0.5.1: version "0.5.1" resolved "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.5.1.tgz#5cd0536ad5473abb667cc82b59bc56b96fb35db6" @@ -7553,45 +7558,48 @@ apollo-server-caching@0.5.1: dependencies: lru-cache "^5.0.0" -apollo-server-caching@^0.5.2: - version "0.5.2" - resolved "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.5.2.tgz#bef5d5e0d48473a454927a66b7bb947a0b6eb13e" - integrity sha512-HUcP3TlgRsuGgeTOn8QMbkdx0hLPXyEJehZIPrcof0ATz7j7aTPA4at7gaiFHCo8gk07DaWYGB3PFgjboXRcWQ== +apollo-server-caching@^0.5.3: + version "0.5.3" + resolved "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.5.3.tgz#cf42a77ad09a46290a246810075eaa029b5305e1" + integrity sha512-iMi3087iphDAI0U2iSBE9qtx9kQoMMEWr6w+LwXruBD95ek9DWyj7OeC2U/ngLjRsXM43DoBDXlu7R+uMjahrQ== dependencies: - lru-cache "^5.0.0" + lru-cache "^6.0.0" -apollo-server-core@^2.16.1: - version "2.16.1" - resolved "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.16.1.tgz#5b5b8245ab9c0cb6c2367ec19ab855dea6ccea3a" - integrity sha512-nuwn5ZBbmzPwDetb3FgiFFJlNK7ZBFg8kis/raymrjd3eBGdNcOyMTJDl6J9673X9Xqp+dXQmFYDW/G3G8S1YA== +apollo-server-core@^2.16.1, apollo-server-core@^2.21.0: + version "2.21.0" + resolved "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.21.0.tgz#12ee11aee61fa124f11b1d73cae2e068112a3a53" + integrity sha512-GtIiq2F0dVDLzzIuO5+dK/pGq/sGxYlKCqAuQQqzYg0fvZ7fukyluXtcTe0tMI+FJZjU0j0WnKgiLsboCoAlPQ== dependencies: "@apollographql/apollo-tools" "^0.4.3" "@apollographql/graphql-playground-html" "1.6.26" - "@types/graphql-upload" "^8.0.0" + "@apollographql/graphql-upload-8-fork" "^8.1.3" "@types/ws" "^7.0.0" - apollo-cache-control "^0.11.1" - apollo-datasource "^0.7.2" - apollo-engine-reporting "^2.3.0" - apollo-server-caching "^0.5.2" - apollo-server-env "^2.4.5" + apollo-cache-control "^0.11.6" + apollo-datasource "^0.7.3" + apollo-graphql "^0.6.0" + apollo-reporting-protobuf "^0.6.2" + apollo-server-caching "^0.5.3" + apollo-server-env "^3.0.0" apollo-server-errors "^2.4.2" - apollo-server-plugin-base "^0.9.1" - apollo-server-types "^0.5.1" - apollo-tracing "^0.11.1" + apollo-server-plugin-base "^0.10.4" + apollo-server-types "^0.6.3" + apollo-tracing "^0.12.2" + async-retry "^1.2.1" fast-json-stable-stringify "^2.0.0" - graphql-extensions "^0.12.4" - graphql-tag "^2.9.2" - graphql-tools "^4.0.0" - graphql-upload "^8.0.2" + graphql-extensions "^0.12.8" + graphql-tag "^2.11.0" + graphql-tools "^4.0.8" loglevel "^1.6.7" + lru-cache "^6.0.0" sha.js "^2.4.11" subscriptions-transport-ws "^0.9.11" + uuid "^8.0.0" ws "^6.0.0" -apollo-server-env@^2.4.5: - version "2.4.5" - resolved "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.5.tgz#73730b4f0439094a2272a9d0caa4079d4b661d5f" - integrity sha512-nfNhmGPzbq3xCEWT8eRpoHXIPNcNy3QcEoBlzVMjeglrBGryLG2LXwBSPnVmTRRrzUYugX0ULBtgE3rBFNoUgA== +apollo-server-env@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-3.0.0.tgz#0157c51f52b63aee39af190760acf789ffc744d9" + integrity sha512-tPSN+VttnPsoQAl/SBVUpGbLA97MXG990XIwq6YUnJyAixrrsjW1xYG7RlaOqetxm80y5mBZKLrRDiiSsW/vog== dependencies: node-fetch "^2.1.2" util.promisify "^1.0.0" @@ -7602,42 +7610,43 @@ apollo-server-errors@^2.4.2: integrity sha512-FeGxW3Batn6sUtX3OVVUm7o56EgjxDlmgpTLNyWcLb0j6P8mw9oLNyAm3B+deHA4KNdNHO5BmHS2g1SJYjqPCQ== apollo-server-express@^2.16.1: - version "2.16.1" - resolved "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.16.1.tgz#7438bca590ef8577d24d20ba0b6d582c120c0146" - integrity sha512-Oq5YNcaMYnRk6jDmA9LWf8oSd2KHDVe7jQ4wtooAvG9FVUD+FaFBgSkytXHMvtifQh2wdF07Ri8uDLMz6IQjTw== + version "2.21.0" + resolved "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.21.0.tgz#29bd4ec728e1992da240c5956c3ce6d95c1d252e" + integrity sha512-zbOSNGuxUjlOFZnRrbMpga3pKDEroitF4NAqoVxgBivx7v2hGsE7rljct3PucTx2cMN90AyYe3cU4oA8jBxZIQ== dependencies: "@apollographql/graphql-playground-html" "1.6.26" "@types/accepts" "^1.3.5" "@types/body-parser" "1.19.0" - "@types/cors" "^2.8.4" + "@types/cors" "2.8.8" "@types/express" "4.17.7" + "@types/express-serve-static-core" "4.17.18" accepts "^1.3.5" - apollo-server-core "^2.16.1" - apollo-server-types "^0.5.1" + apollo-server-core "^2.21.0" + apollo-server-types "^0.6.3" body-parser "^1.18.3" cors "^2.8.4" express "^4.17.1" graphql-subscriptions "^1.0.0" - graphql-tools "^4.0.0" + graphql-tools "^4.0.8" parseurl "^1.3.2" subscriptions-transport-ws "^0.9.16" type-is "^1.6.16" -apollo-server-plugin-base@^0.9.1: - version "0.9.1" - resolved "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.9.1.tgz#a62ae9ab4e89790fd4cc5d123bb616da34e8e5fb" - integrity sha512-kvrX4Z3FdpjrZdHkyl5iY2A1Wvp4b6KQp00DeZqss7GyyKNUBKr80/7RQgBLEw7EWM7WB19j459xM/TjvW0FKQ== +apollo-server-plugin-base@^0.10.4: + version "0.10.4" + resolved "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.10.4.tgz#fbf73f64f95537ca9f9639dd7c535eb5eeb95dcd" + integrity sha512-HRhbyHgHFTLP0ImubQObYhSgpmVH4Rk1BinnceZmwudIVLKrqayIVOELdyext/QnSmmzg5W7vF3NLGBcVGMqDg== dependencies: - apollo-server-types "^0.5.1" + apollo-server-types "^0.6.3" -apollo-server-types@^0.5.1: - version "0.5.1" - resolved "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.5.1.tgz#091c09652894d6532db9ba873574443adabf85b9" - integrity sha512-my2cPw+DAb2qVnIuBcsRKGyS28uIc2vjFxa1NpRoJZe9gK0BWUBk7wzXnIzWy3HZ5Er11e/40MPTUesNfMYNVA== +apollo-server-types@^0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.6.3.tgz#f7aa25ff7157863264d01a77d7934aa6e13399e8" + integrity sha512-aVR7SlSGGY41E1f11YYz5bvwA89uGmkVUtzMiklDhZ7IgRJhysT5Dflt5IuwDxp+NdQkIhVCErUXakopocFLAg== dependencies: - apollo-engine-reporting-protobuf "^0.5.2" - apollo-server-caching "^0.5.2" - apollo-server-env "^2.4.5" + apollo-reporting-protobuf "^0.6.2" + apollo-server-caching "^0.5.3" + apollo-server-env "^3.0.0" apollo-server@^2.16.1: version "2.16.1" @@ -7650,13 +7659,13 @@ apollo-server@^2.16.1: graphql-subscriptions "^1.0.0" graphql-tools "^4.0.0" -apollo-tracing@^0.11.1: - version "0.11.2" - resolved "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.11.2.tgz#14308b176e021f5e6ec3ee670f8f96e9fbfdb50c" - integrity sha512-QjmRd2ozGD+PfmF6U9w/w6jrclYSBNczN6Bzppr8qA5somEGl5pqdprIZYL28H0IapZiutA3x6p6ZVF/cVX8wA== +apollo-tracing@^0.12.2: + version "0.12.2" + resolved "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.12.2.tgz#a261c3970bb421b6dadf50cd85d75b2567a7e52c" + integrity sha512-SYN4o0C0wR1fyS3+P0FthyvsQVHFopdmN3IU64IaspR/RZScPxZ3Ae8uu++fTvkQflAkglnFM0aX6DkZERBp6w== dependencies: - apollo-server-env "^2.4.5" - apollo-server-plugin-base "^0.9.1" + apollo-server-env "^3.0.0" + apollo-server-plugin-base "^0.10.4" apollo-upload-client@^13.0.0: version "13.0.0" @@ -14000,14 +14009,14 @@ graphql-config@^3.0.2: string-env-interpolation "1.0.1" tslib "^2.0.0" -graphql-extensions@^0.12.4: - version "0.12.4" - resolved "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.12.4.tgz#c0aa49a20f983a2da641526d1e505996bd2b4188" - integrity sha512-GnR4LiWk3s2bGOqIh6V1JgnSXw2RCH4NOgbCFEWvB6JqWHXTlXnLZ8bRSkCiD4pltv7RHUPWqN/sGh8R6Ae/ag== +graphql-extensions@^0.12.8: + version "0.12.8" + resolved "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.12.8.tgz#9cdc2c43d8fe5e0f6c3177a004ac011da2a8aa0f" + integrity sha512-xjsSaB6yKt9jarFNNdivl2VOx52WySYhxPgf8Y16g6GKZyAzBoIFiwyGw5PJDlOSUa6cpmzn6o7z8fVMbSAbkg== dependencies: "@apollographql/apollo-tools" "^0.4.3" - apollo-server-env "^2.4.5" - apollo-server-types "^0.5.1" + apollo-server-env "^3.0.0" + apollo-server-types "^0.6.3" graphql-language-service-interface@^2.4.0: version "2.4.0" @@ -14065,11 +14074,6 @@ graphql-tag@^2.11.0: resolved "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.11.0.tgz#1deb53a01c46a7eb401d6cb59dec86fa1cccbffd" integrity sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA== -graphql-tag@^2.9.2: - version "2.10.4" - resolved "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.4.tgz#2f301a98219be8b178a6453bb7e33b79b66d8f83" - integrity sha512-O7vG5BT3w6Sotc26ybcvLKNTdfr4GfsIVMD+LdYqXCeJIYPRyp8BIsDOUtxw7S1PYvRw5vH3278J2EDezR6mfA== - graphql-tools@5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/graphql-tools/-/graphql-tools-5.0.0.tgz#67281c834a0e29f458adba8018f424816fa627e9" @@ -14084,7 +14088,7 @@ graphql-tools@5.0.0: tslib "^1.11.1" uuid "^7.0.3" -graphql-tools@^4.0.0: +graphql-tools@^4.0.0, graphql-tools@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/graphql-tools/-/graphql-tools-4.0.8.tgz#e7fb9f0d43408fb0878ba66b522ce871bafe9d30" integrity sha512-MW+ioleBrwhRjalKjYaLQbr+920pHBgy9vM/n47sswtns8+96sRn5M/G+J1eu7IMeKWiN/9p6tmwCHU7552VJg== @@ -14100,28 +14104,11 @@ graphql-type-json@^0.3.2: resolved "https://registry.npmjs.org/graphql-type-json/-/graphql-type-json-0.3.2.tgz#f53a851dbfe07bd1c8157d24150064baab41e115" integrity sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg== -graphql-upload@^8.0.2: - version "8.1.0" - resolved "https://registry.npmjs.org/graphql-upload/-/graphql-upload-8.1.0.tgz#6d0ab662db5677a68bfb1f2c870ab2544c14939a" - integrity sha512-U2OiDI5VxYmzRKw0Z2dmfk0zkqMRaecH9Smh1U277gVgVe9Qn+18xqf4skwr4YJszGIh7iQDZ57+5ygOK9sM/Q== - dependencies: - busboy "^0.3.1" - fs-capacitor "^2.0.4" - http-errors "^1.7.3" - object-path "^0.11.4" - graphql@15.4.0, graphql@^15.3.0: version "15.4.0" resolved "https://registry.npmjs.org/graphql/-/graphql-15.4.0.tgz#e459dea1150da5a106486ba7276518b5295a4347" integrity sha512-EB3zgGchcabbsU9cFe1j+yxdzKQKAbGUWRb13DsrsMN1yyfmmIq+2+L5MqVWcDCE4V89R5AyUOi7sMOGxdsYtA== -graphql@^14.5.3: - version "14.7.0" - resolved "https://registry.npmjs.org/graphql/-/graphql-14.7.0.tgz#7fa79a80a69be4a31c27dda824dc04dac2035a72" - integrity sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA== - dependencies: - iterall "^1.2.2" - growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" @@ -15690,7 +15677,7 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -iterall@^1.1.3, iterall@^1.2.1, iterall@^1.2.2, iterall@^1.3.0: +iterall@^1.1.3, iterall@^1.2.1, iterall@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea" integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== @@ -23960,7 +23947,7 @@ stylis@3.5.0: resolved "https://registry.npmjs.org/stylis/-/stylis-3.5.0.tgz#016fa239663d77f868fef5b67cf201c4b7c701e1" integrity sha512-pP7yXN6dwMzAR29Q0mBrabPCe0/mNO1MSr93bhay+hcZondvMMTpeGyd8nbhYJdyperNT2DRxONQuUGcJr5iPw== -subscriptions-transport-ws@0.9.18: +subscriptions-transport-ws@0.9.18, subscriptions-transport-ws@^0.9.11, subscriptions-transport-ws@^0.9.16: version "0.9.18" resolved "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.18.tgz#bcf02320c911fbadb054f7f928e51c6041a37b97" integrity sha512-tztzcBTNoEbuErsVQpTN2xUNN/efAZXyCyL5m3x4t6SKrEiTL2N8SaKWBFWM4u56pL79ULif3zjyeq+oV+nOaA== @@ -23971,17 +23958,6 @@ subscriptions-transport-ws@0.9.18: symbol-observable "^1.0.4" ws "^5.2.0" -subscriptions-transport-ws@^0.9.11, subscriptions-transport-ws@^0.9.16: - version "0.9.17" - resolved "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.17.tgz#e30e40f0caae0d2781903c01a8cb51b6e2682098" - integrity sha512-hNHi2N80PBz4T0V0QhnnsMGvG3XDFDS9mS6BhZ3R12T6EBywC8d/uJscsga0cVO4DKtXCkCRrWm2sOYrbOdhEA== - dependencies: - backo2 "^1.0.2" - eventemitter3 "^3.1.0" - iterall "^1.2.1" - symbol-observable "^1.0.4" - ws "^5.2.0" - sucrase@^3.16.0: version "3.17.0" resolved "https://registry.npmjs.org/sucrase/-/sucrase-3.17.0.tgz#d9fe5d7e359d884cdb31130358fbdfc18bfb4c24" From b6c4f485d1f335608d99a8a305ffac224d70307d Mon Sep 17 00:00:00 2001 From: Joel Low Date: Tue, 23 Feb 2021 14:09:11 +0800 Subject: [PATCH 246/270] fix(saml-auth): Add default option for getBackstageIdentity Signed-off-by: Joel Low --- .changeset/shy-vans-return.md | 5 +++++ .../core-api/src/apis/implementations/auth/saml/SamlAuth.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/shy-vans-return.md diff --git a/.changeset/shy-vans-return.md b/.changeset/shy-vans-return.md new file mode 100644 index 0000000000..d0df497cd6 --- /dev/null +++ b/.changeset/shy-vans-return.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-api': patch +--- + +Fix error when querying Backstage Identity with SAML authentication diff --git a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts index e05f6266d6..af97ad8f36 100644 --- a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts +++ b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts @@ -83,7 +83,7 @@ class SamlAuth implements ProfileInfoApi, BackstageIdentityApi, SessionApi { } async getBackstageIdentity( - options: AuthRequestOptions, + options: AuthRequestOptions = {}, ): Promise { const session = await this.sessionManager.getSession(options); return session?.backstageIdentity; From 42c8ebb793396d8f0ac7abee31933a6483761898 Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Mon, 22 Feb 2021 22:02:06 -0800 Subject: [PATCH 247/270] fix: add aws auth provider on FE kubernetes plugin --- .changeset/metal-pianos-repeat.md | 5 ++++ .../AwsKubernetesAuthProvider.ts | 27 +++++++++++++++++++ .../KubernetesAuthProviders.ts | 2 ++ 3 files changed, 34 insertions(+) create mode 100644 .changeset/metal-pianos-repeat.md create mode 100644 plugins/kubernetes/src/kubernetes-auth-provider/AwsKubernetesAuthProvider.ts diff --git a/.changeset/metal-pianos-repeat.md b/.changeset/metal-pianos-repeat.md new file mode 100644 index 0000000000..e24320938a --- /dev/null +++ b/.changeset/metal-pianos-repeat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes': patch +--- + +Support AWS auth provider on kubernetes FE plugin diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/AwsKubernetesAuthProvider.ts b/plugins/kubernetes/src/kubernetes-auth-provider/AwsKubernetesAuthProvider.ts new file mode 100644 index 0000000000..cab81f3622 --- /dev/null +++ b/plugins/kubernetes/src/kubernetes-auth-provider/AwsKubernetesAuthProvider.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { KubernetesAuthProvider } from './types'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend'; + +export class AwsKubernetesAuthProvider implements KubernetesAuthProvider { + async decorateRequestBodyForAuth( + requestBody: KubernetesRequestBody, + ): Promise { + // No-op, with aws auth, server's AWS credentials are used for access + return requestBody; + } +} diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts index 9f64af9c2e..dc9d9b5e0d 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts @@ -19,6 +19,7 @@ import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend'; import { KubernetesAuthProvider, KubernetesAuthProvidersApi } from './types'; import { GoogleKubernetesAuthProvider } from './GoogleKubernetesAuthProvider'; import { ServiceAccountKubernetesAuthProvider } from './ServiceAccountKubernetesAuthProvider'; +import { AwsKubernetesAuthProvider } from './AwsKubernetesAuthProvider'; export class KubernetesAuthProviders implements KubernetesAuthProvidersApi { private readonly kubernetesAuthProviderMap: Map< @@ -36,6 +37,7 @@ export class KubernetesAuthProviders implements KubernetesAuthProvidersApi { 'serviceAccount', new ServiceAccountKubernetesAuthProvider(), ); + this.kubernetesAuthProviderMap.set('aws', new AwsKubernetesAuthProvider()); } async decorateRequestBodyForAuth( From 449cddaa56ad3aeebec185b69965e7a75cc60a6a Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 23 Feb 2021 09:01:13 +0100 Subject: [PATCH 248/270] chore: reworking authorization for bitbucket so we can use either appPassword or token --- .../scaffolder/stages/publish/bitbucket.ts | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/bitbucket.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/bitbucket.ts index 373920c005..eb89a4defc 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/bitbucket.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/bitbucket.ts @@ -98,10 +98,6 @@ export class BitbucketPublisher implements PublisherBase { const { project, name, description } = opts; let response: Response; - const buffer = Buffer.from( - `${this.config.username}:${this.config.appPassword}`, - 'utf8', - ); const options: RequestInit = { method: 'POST', @@ -111,7 +107,7 @@ export class BitbucketPublisher implements PublisherBase { is_private: this.config.repoVisibility === 'private', }), headers: { - Authorization: `Basic ${buffer.toString('base64')}`, + Authorization: this.getAuthorizationHeader(), 'Content-Type': 'application/json', }, }; @@ -132,13 +128,32 @@ export class BitbucketPublisher implements PublisherBase { } } - // TODO use the urlReader to get the defautl branch + // TODO use the urlReader to get the default branch const catalogInfoUrl = `${r.links.html.href}/src/master/catalog-info.yaml`; return { remoteUrl, catalogInfoUrl }; } throw new Error(`Not a valid response code ${await response.text()}`); } + private getAuthorizationHeader(): string { + if (this.config.username && this.config.appPassword) { + const buffer = Buffer.from( + `${this.config.username}:${this.config.appPassword}`, + 'utf8', + ); + + return `Basic ${buffer.toString('base64')}`; + } + + if (this.config.token) { + return `Bearer ${this.config.token}`; + } + + throw new Error( + `Authorization has not been provided for Bitbucket. Please add username + appPassword or token to the integration config `, + ); + } + private async createBitbucketServerRepository(opts: { project: string; name: string; @@ -155,10 +170,11 @@ export class BitbucketPublisher implements PublisherBase { is_private: this.config.repoVisibility === 'private', }), headers: { - Authorization: `Bearer ${this.config.token}`, + Authorization: this.getAuthorizationHeader(), 'Content-Type': 'application/json', }, }; + try { response = await fetch( `https://${this.config.host}/rest/api/1.0/projects/${project}/repos`, From 599aeb70d51042ab62a9e6cf51580eccd1b82ff5 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 23 Feb 2021 09:06:19 +0100 Subject: [PATCH 249/270] chore: updating logging --- .../src/scaffolder/stages/publish/bitbucket.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/bitbucket.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/bitbucket.ts index eb89a4defc..102a3a7d02 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/bitbucket.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/bitbucket.ts @@ -150,7 +150,7 @@ export class BitbucketPublisher implements PublisherBase { } throw new Error( - `Authorization has not been provided for Bitbucket. Please add username + appPassword or token to the integration config `, + `Authorization has not been provided for Bitbucket. Please add either username + appPassword or token to the Integrations config`, ); } From f31b76b4443a567bd4163c0b984d831855b57738 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 23 Feb 2021 09:11:31 +0100 Subject: [PATCH 250/270] chore(changeset): added changeset for scaffolder-backend --- .changeset/hungry-frogs-flow.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/hungry-frogs-flow.md diff --git a/.changeset/hungry-frogs-flow.md b/.changeset/hungry-frogs-flow.md new file mode 100644 index 0000000000..e41fe71627 --- /dev/null +++ b/.changeset/hungry-frogs-flow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Consider both authentication methods for both onprem and cloud bitbucket From 1536c41b34150a4c8e41a006b1021dcbd5772d35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Feb 2021 08:13:36 +0000 Subject: [PATCH 251/270] chore(deps): bump graphql from 15.4.0 to 15.5.0 Bumps [graphql](https://github.com/graphql/graphql-js) from 15.4.0 to 15.5.0. - [Release notes](https://github.com/graphql/graphql-js/releases) - [Commits](https://github.com/graphql/graphql-js/compare/v15.4.0...v15.5.0) Signed-off-by: dependabot[bot] --- plugins/graphiql/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index 48e87877d4..8cb32e2fa9 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -37,7 +37,7 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "graphiql": "^1.0.0-alpha.10", - "graphql": "15.4.0", + "graphql": "15.5.0", "react": "^16.13.1", "react-dom": "^16.13.1", "react-use": "^15.3.3" diff --git a/yarn.lock b/yarn.lock index d10f91ab30..133dc045bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14104,10 +14104,10 @@ graphql-type-json@^0.3.2: resolved "https://registry.npmjs.org/graphql-type-json/-/graphql-type-json-0.3.2.tgz#f53a851dbfe07bd1c8157d24150064baab41e115" integrity sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg== -graphql@15.4.0, graphql@^15.3.0: - version "15.4.0" - resolved "https://registry.npmjs.org/graphql/-/graphql-15.4.0.tgz#e459dea1150da5a106486ba7276518b5295a4347" - integrity sha512-EB3zgGchcabbsU9cFe1j+yxdzKQKAbGUWRb13DsrsMN1yyfmmIq+2+L5MqVWcDCE4V89R5AyUOi7sMOGxdsYtA== +graphql@15.5.0, graphql@^15.3.0: + version "15.5.0" + resolved "https://registry.npmjs.org/graphql/-/graphql-15.5.0.tgz#39d19494dbe69d1ea719915b578bf920344a69d5" + integrity sha512-OmaM7y0kaK31NKG31q4YbD2beNYa6jBBKtMFT6gLYJljHLJr42IqJ8KX08u3Li/0ifzTU5HjmoOOrwa5BRLeDA== growly@^1.3.0: version "1.3.0" From 315f909bc3990ed45062de3a51d7d316f0af35f7 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 23 Feb 2021 09:43:16 +0100 Subject: [PATCH 252/270] chore(changeset): hmm spelling... --- .changeset/hungry-frogs-flow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/hungry-frogs-flow.md b/.changeset/hungry-frogs-flow.md index e41fe71627..91a6ab0892 100644 --- a/.changeset/hungry-frogs-flow.md +++ b/.changeset/hungry-frogs-flow.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend': patch --- -Consider both authentication methods for both onprem and cloud bitbucket +Consider both authentication methods for both `onprem` and `cloud` BitBucket From 4ae0467c2faf36336f8bb5fe20245c50ed82b97b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 23 Feb 2021 09:28:48 +0000 Subject: [PATCH 253/270] Version Packages --- .changeset/blue-taxis-behave.md | 5 - .changeset/breezy-jobs-brake.md | 5 - .changeset/bright-dolphins-mate.md | 5 - .changeset/chilly-eels-try.md | 6 - .changeset/clever-tomatoes-change.md | 53 ------ .../cost-insights-fresh-radios-doubt.md | 5 - .changeset/curly-poems-boil.md | 6 - .changeset/curvy-ducks-tease.md | 5 - .changeset/cyan-dingos-watch.md | 5 - .changeset/cyan-feet-hide.md | 5 - .changeset/dingo-dongo.md | 39 ----- .changeset/dirty-buckets-flow.md | 6 - .changeset/dry-ads-matter.md | 6 - .changeset/eight-chefs-reply.md | 5 - .changeset/four-owls-raise.md | 7 - .changeset/gentle-buses-exist.md | 6 - .changeset/gentle-zoos-pump.md | 6 - .changeset/healthy-schools-kneel.md | 7 - .changeset/honest-hounds-exist.md | 6 - .changeset/itchy-rivers-judge.md | 5 - .changeset/kind-eels-run.md | 7 - .changeset/mighty-masks-hear.md | 29 ---- .changeset/ninety-falcons-applaud.md | 5 - .changeset/olive-moons-melt.md | 7 - .changeset/popular-donuts-fetch.md | 5 - .changeset/popular-nails-agree.md | 6 - .changeset/selfish-onions-count.md | 5 - .changeset/seven-kangaroos-work.md | 5 - .changeset/shy-vans-return.md | 5 - .changeset/silly-lemons-dream.md | 5 - .changeset/silly-pandas-flash.md | 6 - .changeset/sixty-chicken-ring.md | 7 - .changeset/small-bikes-enjoy.md | 6 - .changeset/spotty-news-complain.md | 13 -- .changeset/strong-badgers-invent.md | 5 - .changeset/techdocs-wild-zoos-do.md | 5 - .changeset/tricky-birds-appear.md | 5 - .changeset/unlucky-ducks-report.md | 5 - .changeset/weak-foxes-explain.md | 86 --------- packages/app/CHANGELOG.md | 59 +++++++ packages/app/package.json | 54 +++--- packages/backend/CHANGELOG.md | 22 +++ packages/backend/package.json | 22 +-- packages/catalog-model/CHANGELOG.md | 8 + packages/catalog-model/package.json | 6 +- packages/cli/CHANGELOG.md | 9 + packages/cli/package.json | 12 +- packages/config/CHANGELOG.md | 14 ++ packages/config/package.json | 2 +- packages/core-api/CHANGELOG.md | 11 ++ packages/core-api/package.json | 8 +- packages/core/CHANGELOG.md | 21 +++ packages/core/package.json | 10 +- packages/create-app/CHANGELOG.md | 164 ++++++++++++++++++ packages/create-app/package.json | 44 ++--- packages/dev-utils/CHANGELOG.md | 22 +++ packages/dev-utils/package.json | 12 +- packages/techdocs-common/CHANGELOG.md | 11 ++ packages/techdocs-common/package.json | 8 +- packages/test-utils/CHANGELOG.md | 11 ++ packages/test-utils/package.json | 6 +- plugins/api-docs/CHANGELOG.md | 24 +++ plugins/api-docs/package.json | 14 +- plugins/app-backend/CHANGELOG.md | 8 + plugins/app-backend/package.json | 6 +- plugins/auth-backend/CHANGELOG.md | 10 ++ plugins/auth-backend/package.json | 8 +- plugins/catalog-backend/CHANGELOG.md | 10 ++ plugins/catalog-backend/package.json | 10 +- plugins/catalog-import/CHANGELOG.md | 22 +++ plugins/catalog-import/package.json | 14 +- plugins/catalog-react/CHANGELOG.md | 24 +++ plugins/catalog-react/package.json | 12 +- plugins/catalog/CHANGELOG.md | 65 +++++++ plugins/catalog/package.json | 14 +- plugins/circleci/CHANGELOG.md | 19 ++ plugins/circleci/package.json | 14 +- plugins/cloudbuild/CHANGELOG.md | 19 ++ plugins/cloudbuild/package.json | 14 +- plugins/cost-insights/CHANGELOG.md | 16 ++ plugins/cost-insights/package.json | 12 +- plugins/explore/CHANGELOG.md | 20 +++ plugins/explore/package.json | 14 +- plugins/fossa/CHANGELOG.md | 19 ++ plugins/fossa/package.json | 14 +- plugins/gcp-projects/package.json | 8 +- plugins/github-actions/CHANGELOG.md | 19 ++ plugins/github-actions/package.json | 14 +- plugins/gitops-profiles/package.json | 8 +- plugins/graphiql/package.json | 8 +- plugins/jenkins/CHANGELOG.md | 19 ++ plugins/jenkins/package.json | 14 +- plugins/kafka/CHANGELOG.md | 19 ++ plugins/kafka/package.json | 14 +- plugins/kubernetes-backend/CHANGELOG.md | 10 ++ plugins/kubernetes-backend/package.json | 8 +- plugins/kubernetes/CHANGELOG.md | 23 +++ plugins/kubernetes/package.json | 18 +- plugins/lighthouse/CHANGELOG.md | 21 +++ plugins/lighthouse/package.json | 16 +- plugins/newrelic/package.json | 8 +- plugins/org/CHANGELOG.md | 22 +++ plugins/org/package.json | 16 +- plugins/pagerduty/CHANGELOG.md | 19 ++ plugins/pagerduty/package.json | 14 +- plugins/register-component/CHANGELOG.md | 19 ++ plugins/register-component/package.json | 14 +- plugins/rollbar/CHANGELOG.md | 19 ++ plugins/rollbar/package.json | 14 +- plugins/scaffolder-backend/CHANGELOG.md | 60 +++++++ plugins/scaffolder-backend/package.json | 10 +- plugins/scaffolder/CHANGELOG.md | 112 ++++++++++++ plugins/scaffolder/package.json | 16 +- plugins/search/CHANGELOG.md | 19 ++ plugins/search/package.json | 14 +- plugins/sentry/CHANGELOG.md | 19 ++ plugins/sentry/package.json | 14 +- plugins/sonarqube/CHANGELOG.md | 19 ++ plugins/sonarqube/package.json | 14 +- plugins/splunk-on-call/CHANGELOG.md | 19 ++ plugins/splunk-on-call/package.json | 14 +- plugins/tech-radar/CHANGELOG.md | 14 ++ plugins/tech-radar/package.json | 10 +- plugins/techdocs-backend/CHANGELOG.md | 13 ++ plugins/techdocs-backend/package.json | 10 +- plugins/techdocs/CHANGELOG.md | 28 +++ plugins/techdocs/package.json | 20 +-- plugins/user-settings/package.json | 8 +- plugins/welcome/package.json | 8 +- 129 files changed, 1422 insertions(+), 731 deletions(-) delete mode 100644 .changeset/blue-taxis-behave.md delete mode 100644 .changeset/breezy-jobs-brake.md delete mode 100644 .changeset/bright-dolphins-mate.md delete mode 100644 .changeset/chilly-eels-try.md delete mode 100644 .changeset/clever-tomatoes-change.md delete mode 100644 .changeset/cost-insights-fresh-radios-doubt.md delete mode 100644 .changeset/curly-poems-boil.md delete mode 100644 .changeset/curvy-ducks-tease.md delete mode 100644 .changeset/cyan-dingos-watch.md delete mode 100644 .changeset/cyan-feet-hide.md delete mode 100644 .changeset/dingo-dongo.md delete mode 100644 .changeset/dirty-buckets-flow.md delete mode 100644 .changeset/dry-ads-matter.md delete mode 100644 .changeset/eight-chefs-reply.md delete mode 100644 .changeset/four-owls-raise.md delete mode 100644 .changeset/gentle-buses-exist.md delete mode 100644 .changeset/gentle-zoos-pump.md delete mode 100644 .changeset/healthy-schools-kneel.md delete mode 100644 .changeset/honest-hounds-exist.md delete mode 100644 .changeset/itchy-rivers-judge.md delete mode 100644 .changeset/kind-eels-run.md delete mode 100644 .changeset/mighty-masks-hear.md delete mode 100644 .changeset/ninety-falcons-applaud.md delete mode 100644 .changeset/olive-moons-melt.md delete mode 100644 .changeset/popular-donuts-fetch.md delete mode 100644 .changeset/popular-nails-agree.md delete mode 100644 .changeset/selfish-onions-count.md delete mode 100644 .changeset/seven-kangaroos-work.md delete mode 100644 .changeset/shy-vans-return.md delete mode 100644 .changeset/silly-lemons-dream.md delete mode 100644 .changeset/silly-pandas-flash.md delete mode 100644 .changeset/sixty-chicken-ring.md delete mode 100644 .changeset/small-bikes-enjoy.md delete mode 100644 .changeset/spotty-news-complain.md delete mode 100644 .changeset/strong-badgers-invent.md delete mode 100644 .changeset/techdocs-wild-zoos-do.md delete mode 100644 .changeset/tricky-birds-appear.md delete mode 100644 .changeset/unlucky-ducks-report.md delete mode 100644 .changeset/weak-foxes-explain.md diff --git a/.changeset/blue-taxis-behave.md b/.changeset/blue-taxis-behave.md deleted file mode 100644 index 5961c3b770..0000000000 --- a/.changeset/blue-taxis-behave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes-backend': patch ---- - -update kubernetes plugin backend function to use classes diff --git a/.changeset/breezy-jobs-brake.md b/.changeset/breezy-jobs-brake.md deleted file mode 100644 index 412cff3df5..0000000000 --- a/.changeset/breezy-jobs-brake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Fix for refresh token being lost during Microsoft login. diff --git a/.changeset/bright-dolphins-mate.md b/.changeset/bright-dolphins-mate.md deleted file mode 100644 index ac099c88f0..0000000000 --- a/.changeset/bright-dolphins-mate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/dev-utils': patch ---- - -Make sure to provide dummy routes for all external routes of plugins given to DevApp diff --git a/.changeset/chilly-eels-try.md b/.changeset/chilly-eels-try.md deleted file mode 100644 index e682c567ea..0000000000 --- a/.changeset/chilly-eels-try.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/core-api': patch -'@backstage/core': patch ---- - -The `FlatRoutes` components now renders the not found page of the app if no routes are matched. diff --git a/.changeset/clever-tomatoes-change.md b/.changeset/clever-tomatoes-change.md deleted file mode 100644 index c57953efb9..0000000000 --- a/.changeset/clever-tomatoes-change.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-scaffolder-backend': minor ---- - -# Stateless scaffolding - -The scaffolder has been redesigned to be horizontally scalable and to persistently store task state and execution logs in the database. - -Each scaffolder task is given a unique task ID which is persisted in the database. -Tasks are then picked up by a `TaskWorker` which performs the scaffolding steps. -Execution logs are also persisted in the database meaning you can now refresh the scaffolder task status page without losing information. - -The task status page is now dynamically created based on the step information stored in the database. -This allows for custom steps to be displayed once the next version of the scaffolder template schema is available. - -The task page is updated to display links to both the git repository and to the newly created catalog entity. - -Component registration has moved from the frontend into a separate registration step executed by the `TaskWorker`. This requires that a `CatalogClient` is passed to the scaffolder backend instead of the old `CatalogEntityClient`. - -Make sure to update `plugins/scaffolder.ts` - -```diff - import { - CookieCutter, - createRouter, - Preparers, - Publishers, - CreateReactAppTemplater, - Templaters, -- CatalogEntityClient, - } from '@backstage/plugin-scaffolder-backend'; - -+import { CatalogClient } from '@backstage/catalog-client'; - - const discovery = SingleHostDiscovery.fromConfig(config); --const entityClient = new CatalogEntityClient({ discovery }); -+const catalogClient = new CatalogClient({ discoveryApi: discovery }) - - return await createRouter({ - preparers, - templaters, - publishers, - logger, - config, - dockerClient, -- entityClient, - database, -+ catalogClient, - }); -``` - -As well as adding the `@backstage/catalog-client` packages as a dependency of your backend package. diff --git a/.changeset/cost-insights-fresh-radios-doubt.md b/.changeset/cost-insights-fresh-radios-doubt.md deleted file mode 100644 index 849f5140f6..0000000000 --- a/.changeset/cost-insights-fresh-radios-doubt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-cost-insights': patch ---- - -Default alert properties can be overridden using accessors diff --git a/.changeset/curly-poems-boil.md b/.changeset/curly-poems-boil.md deleted file mode 100644 index 22d5df4665..0000000000 --- a/.changeset/curly-poems-boil.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Update messages that process during loading, error, and no templates found. -Remove unused dependencies. diff --git a/.changeset/curvy-ducks-tease.md b/.changeset/curvy-ducks-tease.md deleted file mode 100644 index 43e73f542a..0000000000 --- a/.changeset/curvy-ducks-tease.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core': patch ---- - -Fix `OverflowTooltip` cutting off the bottom of letters like "g" and "y". diff --git a/.changeset/cyan-dingos-watch.md b/.changeset/cyan-dingos-watch.md deleted file mode 100644 index 7b15d30112..0000000000 --- a/.changeset/cyan-dingos-watch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/test-utils': patch ---- - -Allow `ExternalRouteRef` instances to be passed as a route ref to `mountedRoutes`. diff --git a/.changeset/cyan-feet-hide.md b/.changeset/cyan-feet-hide.md deleted file mode 100644 index a1d58d8252..0000000000 --- a/.changeset/cyan-feet-hide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-org': patch ---- - -Use the `pageTheme` to colour the OwnershipCard boxes with their respective theme colours. diff --git a/.changeset/dingo-dongo.md b/.changeset/dingo-dongo.md deleted file mode 100644 index 424c88f61c..0000000000 --- a/.changeset/dingo-dongo.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -'@backstage/plugin-catalog': minor -'@backstage/plugin-scaffolder': minor ---- - -The Scaffolder and Catalog plugins have been migrated to partially require use of the [new composability API](https://backstage.io/docs/plugins/composability). The Scaffolder used to register its pages using the deprecated route registration plugin API, but those registrations have been removed. This means you now need to add the Scaffolder plugin page to the app directly. - -The page is imported from the Scaffolder plugin and added to the `` component: - -```tsx -} /> -``` - -The Catalog plugin has also been migrated to use an [external route reference](https://backstage.io/docs/plugins/composability#binding-external-routes-in-the-app) to dynamically link to the create component page. This means you need to migrate the catalog plugin to use the new extension components, as well as bind the external route. - -To use the new extension components, replace existing usage of the `CatalogRouter` with the following: - -```tsx -} /> -}> - - -``` - -And to bind the external route from the catalog plugin to the scaffolder template index page, make sure you have the appropriate imports and add the following to the `createApp` call: - -```ts -import { catalogPlugin } from '@backstage/plugin-catalog'; -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; - -const app = createApp({ - // ... - bindRoutes({ bind }) { - bind(catalogPlugin.externalRoutes, { - createComponent: scaffolderPlugin.routes.root, - }); - }, -}); -``` diff --git a/.changeset/dirty-buckets-flow.md b/.changeset/dirty-buckets-flow.md deleted file mode 100644 index b76ab5d35c..0000000000 --- a/.changeset/dirty-buckets-flow.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog-import': patch ---- - -This updates the `catalog-import` plugin to omit the default metadata namespace -field and also use the short form entity reference format for selected group owners. diff --git a/.changeset/dry-ads-matter.md b/.changeset/dry-ads-matter.md deleted file mode 100644 index fc0a412d1e..0000000000 --- a/.changeset/dry-ads-matter.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch -'@backstage/plugin-techdocs-backend': patch ---- - -Got rid of some `attr` and cleaned up a bit in the TechDocs config schema. diff --git a/.changeset/eight-chefs-reply.md b/.changeset/eight-chefs-reply.md deleted file mode 100644 index 9e133e2722..0000000000 --- a/.changeset/eight-chefs-reply.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-explore': patch ---- - -Display the owner of a domain on the domain card. diff --git a/.changeset/four-owls-raise.md b/.changeset/four-owls-raise.md deleted file mode 100644 index 6e8824cb11..0000000000 --- a/.changeset/four-owls-raise.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-catalog': minor -'@backstage/plugin-catalog-react': minor -'@backstage/plugin-scaffolder': minor ---- - -Moved common useStarredEntities hook to plugin-catalog-react diff --git a/.changeset/gentle-buses-exist.md b/.changeset/gentle-buses-exist.md deleted file mode 100644 index a32f0bf8f0..0000000000 --- a/.changeset/gentle-buses-exist.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/core-api': patch -'@backstage/core': patch ---- - -More informative error message for missing ApiContext. diff --git a/.changeset/gentle-zoos-pump.md b/.changeset/gentle-zoos-pump.md deleted file mode 100644 index 77e996cbcc..0000000000 --- a/.changeset/gentle-zoos-pump.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Truncate and show ellipsis with tooltip if content of -`createMetadataDescriptionColumn` is too wide. diff --git a/.changeset/healthy-schools-kneel.md b/.changeset/healthy-schools-kneel.md deleted file mode 100644 index fa7b98a256..0000000000 --- a/.changeset/healthy-schools-kneel.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch -'@backstage/plugin-catalog': patch ---- - -Remove domain column from `HasSystemsCard` and system from `HasComponentsCard`, -`HasSubcomponentsCard`, and `HasApisCard`. diff --git a/.changeset/honest-hounds-exist.md b/.changeset/honest-hounds-exist.md deleted file mode 100644 index 8aadd84a95..0000000000 --- a/.changeset/honest-hounds-exist.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/catalog-model': patch -'@backstage/plugin-catalog': patch ---- - -Implement annotations for customising Entity URLs in the Catalog pages. diff --git a/.changeset/itchy-rivers-judge.md b/.changeset/itchy-rivers-judge.md deleted file mode 100644 index 711becd33b..0000000000 --- a/.changeset/itchy-rivers-judge.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': minor ---- - -Adding Search and Filter features to Scaffolder/Templates Grid diff --git a/.changeset/kind-eels-run.md b/.changeset/kind-eels-run.md deleted file mode 100644 index 0d890b0478..0000000000 --- a/.changeset/kind-eels-run.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/core': patch -'@backstage/plugin-catalog-react': patch ---- - -Forward link styling of `EntityRefLink` and `EnriryRefLinks` into the underling -`Link`. diff --git a/.changeset/mighty-masks-hear.md b/.changeset/mighty-masks-hear.md deleted file mode 100644 index bb19be1420..0000000000 --- a/.changeset/mighty-masks-hear.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Add the google analytics scripts in the `index.html` template for new applications. - -To apply this change to an existing application, change the following in `packages\app\public\index.html`: - -```diff - <%= app.title %> - -+ <% if (app.googleAnalyticsTrackingId && typeof app.googleAnalyticsTrackingId -+ === 'string') { %> -+ -+ -+ <% } %> - -``` diff --git a/.changeset/ninety-falcons-applaud.md b/.changeset/ninety-falcons-applaud.md deleted file mode 100644 index 7d6c941053..0000000000 --- a/.changeset/ninety-falcons-applaud.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Add missing `file-loader` dependency which could cause issues with loading images and other assets. diff --git a/.changeset/olive-moons-melt.md b/.changeset/olive-moons-melt.md deleted file mode 100644 index 415c4cb8cb..0000000000 --- a/.changeset/olive-moons-melt.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-catalog': patch -'@backstage/plugin-api-docs': patch ---- - -Make the description column in the catalog table and api-docs table use up as -much space as possible before hiding overflowing text. diff --git a/.changeset/popular-donuts-fetch.md b/.changeset/popular-donuts-fetch.md deleted file mode 100644 index 9681bdb875..0000000000 --- a/.changeset/popular-donuts-fetch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-tech-radar': patch ---- - -Added a dialog box that will show up when a you click on link on the radar and display the description if provided. diff --git a/.changeset/popular-nails-agree.md b/.changeset/popular-nails-agree.md deleted file mode 100644 index 361ff4745d..0000000000 --- a/.changeset/popular-nails-agree.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/core': patch ---- - -Deprecate `type` of `ItemCard` and introduce new `subtitle` which allows passing -react nodes. diff --git a/.changeset/selfish-onions-count.md b/.changeset/selfish-onions-count.md deleted file mode 100644 index ee4ea23ae8..0000000000 --- a/.changeset/selfish-onions-count.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Added the proper type parameters to entityRouteRef. diff --git a/.changeset/seven-kangaroos-work.md b/.changeset/seven-kangaroos-work.md deleted file mode 100644 index 6e4ac95c9d..0000000000 --- a/.changeset/seven-kangaroos-work.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-app-backend': patch ---- - -Clarify troubleshooting steps for schema serialization issues. diff --git a/.changeset/shy-vans-return.md b/.changeset/shy-vans-return.md deleted file mode 100644 index d0df497cd6..0000000000 --- a/.changeset/shy-vans-return.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-api': patch ---- - -Fix error when querying Backstage Identity with SAML authentication diff --git a/.changeset/silly-lemons-dream.md b/.changeset/silly-lemons-dream.md deleted file mode 100644 index a93be80597..0000000000 --- a/.changeset/silly-lemons-dream.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-import': patch ---- - -Allows the CodeOwnersProcessor to set the owner automatically within the catalog-import plugin. This adds an additional checkbox that overrides the group selector and will omit the owner option in the generated catalog file yaml. diff --git a/.changeset/silly-pandas-flash.md b/.changeset/silly-pandas-flash.md deleted file mode 100644 index 114b2386e0..0000000000 --- a/.changeset/silly-pandas-flash.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/core-api': patch -'@backstage/core': patch ---- - -Created separate `AppContext` type to be returned from `useApp` rather than the `BackstageApp` itself. The `AppContext` type includes but deprecates `getPlugins`, `getProvider`, `getRouter`, and `getRoutes`. In addition, the `AppContext` adds a new `getComponents` method which providers access to the app components. diff --git a/.changeset/sixty-chicken-ring.md b/.changeset/sixty-chicken-ring.md deleted file mode 100644 index 0fff26d6f9..0000000000 --- a/.changeset/sixty-chicken-ring.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/techdocs-common': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-techdocs': patch ---- - -Add support for assuming role in AWS integrations diff --git a/.changeset/small-bikes-enjoy.md b/.changeset/small-bikes-enjoy.md deleted file mode 100644 index 2b624ece2f..0000000000 --- a/.changeset/small-bikes-enjoy.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch -'@backstage/plugin-catalog': patch ---- - -Changes made in CatalogTable and ApiExplorerTable for using the OverflowTooltip component for truncating large description and showing tooltip on hover-over. diff --git a/.changeset/spotty-news-complain.md b/.changeset/spotty-news-complain.md deleted file mode 100644 index 8aac7ebdf6..0000000000 --- a/.changeset/spotty-news-complain.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@backstage/config': patch ---- - -Adds an optional type to `config.get` & `config.getOptional`. This avoids the need for casting. For example: - -```ts -const config = useApi(configApiRef); - -const myConfig = config.get('myPlugin.complexConfig'); -// vs -const myConfig config.get('myPlugin.complexConfig') as SomeTypeDefinition; -``` diff --git a/.changeset/strong-badgers-invent.md b/.changeset/strong-badgers-invent.md deleted file mode 100644 index 6369852675..0000000000 --- a/.changeset/strong-badgers-invent.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Fix Japanese Good Morning diff --git a/.changeset/techdocs-wild-zoos-do.md b/.changeset/techdocs-wild-zoos-do.md deleted file mode 100644 index 42a2a6f1c4..0000000000 --- a/.changeset/techdocs-wild-zoos-do.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/techdocs-common': patch ---- - -Fix AWS, GCS and Azure publisher to work on Windows. diff --git a/.changeset/tricky-birds-appear.md b/.changeset/tricky-birds-appear.md deleted file mode 100644 index ce1792c5da..0000000000 --- a/.changeset/tricky-birds-appear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core': patch ---- - -Introduced generic OverflowTooltip component for cases where longer text needs to be truncated with ellipsis and show hover tooltip with full text. This is particularly useful in the cases where longer description text is rendered in table. e.g. CatalogTable and ApiExplorerTable. diff --git a/.changeset/unlucky-ducks-report.md b/.changeset/unlucky-ducks-report.md deleted file mode 100644 index 4f361a6279..0000000000 --- a/.changeset/unlucky-ducks-report.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -The `yarn backstage-cli app:diff` has been broken since a couple of months. The command to perform updates `yarn backstage-cli versions:bump` prints change logs which seems to be a good replacement for this command. diff --git a/.changeset/weak-foxes-explain.md b/.changeset/weak-foxes-explain.md deleted file mode 100644 index 5536e1982c..0000000000 --- a/.changeset/weak-foxes-explain.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -**BREAKING CHANGE** - -The Scaffolder and Catalog plugins have been migrated to partially require use of the [new composability API](https://backstage.io/docs/plugins/composability). The Scaffolder used to register its pages using the deprecated route registration plugin API, but those registrations have been removed. This means you now need to add the Scaffolder plugin page to the app directly. - -The Catalog plugin has also been migrated to use an [external route reference](https://backstage.io/docs/plugins/composability#binding-external-routes-in-the-app) to dynamically link to the create component page. This means you need to migrate the catalog plugin to use the new extension components, as well as bind the external route. - -Apply the following changes to `packages/app/src/App.tsx`: - -```diff --import { Router as CatalogRouter } from '@backstage/plugin-catalog'; -+import { -+ catalogPlugin, -+ CatalogIndexPage, -+ CatalogEntityPage, -+} from '@backstage/plugin-catalog'; -+import { scaffolderPlugin, ScaffolderPage } from '@backstage/plugin-scaffolder'; - -# The following addition to the app config allows the catalog plugin to link to the -# component creation page, i.e. the scaffolder. You can chose a different target if you want to. - const app = createApp({ - apis, - plugins: Object.values(plugins), -+ bindRoutes({ bind }) { -+ bind(catalogPlugin.externalRoutes, { -+ createComponent: scaffolderPlugin.routes.root, -+ }); -+ } - }); - -# Apply these changes within FlatRoutes. It is important to have migrated to using FlatRoutes -# for this to work, if you haven't done that yet, see the previous entries in this changelog. -- } -- /> -+ } /> -+ } -+ > -+ -+ - } /> -+ } /> -``` - -The scaffolder has been redesigned to be horizontally scalable and to persistently store task state and execution logs in the database. Component registration has moved from the frontend into a separate registration step executed by the `TaskWorker`. This requires that a `CatalogClient` is passed to the scaffolder backend instead of the old `CatalogEntityClient`. - -The default catalog client comes from the `@backstage/catalog-client`, which you need to add as a dependency in `packages/backend/package.json`. - -Once the dependency has been added, apply the following changes to`packages/backend/src/plugins/scaffolder.ts`: - -```diff - import { - CookieCutter, - createRouter, - Preparers, - Publishers, - CreateReactAppTemplater, - Templaters, -- CatalogEntityClient, - } from '@backstage/plugin-scaffolder-backend'; -+import { CatalogClient } from '@backstage/catalog-client'; - - const discovery = SingleHostDiscovery.fromConfig(config); --const entityClient = new CatalogEntityClient({ discovery }); -+const catalogClient = new CatalogClient({ discoveryApi: discovery }) - - return await createRouter({ - preparers, - templaters, - publishers, - logger, - config, - dockerClient, -- entityClient, - database, -+ catalogClient, - }); -``` - -See the `@backstage/scaffolder-backend` changelog for more information about this change. diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index b85ffd3ae1..f9166939c6 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,64 @@ # example-app +## 0.2.17 + +### Patch Changes + +- Updated dependencies [3a58084b6] +- Updated dependencies [a5f42cf66] +- Updated dependencies [38205492a] +- Updated dependencies [e488f0502] +- Updated dependencies [e799e74d4] +- Updated dependencies [e3bc5aad7] +- Updated dependencies [a5f42cf66] +- Updated dependencies [a8953a9c9] +- Updated dependencies [f37992797] +- Updated dependencies [347137ccf] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [d6593abe6] +- Updated dependencies [bad21a085] +- Updated dependencies [e8e35fb5f] +- Updated dependencies [9615e68fb] +- Updated dependencies [e780e119c] +- Updated dependencies [437bac549] +- Updated dependencies [9f2b3a26e] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [968b588f7] +- Updated dependencies [3a58084b6] +- Updated dependencies [2499f6cde] +- Updated dependencies [5469a9761] +- Updated dependencies [60d1bc3e7] +- Updated dependencies [2c1f2a7c2] +- Updated dependencies [6266ddd11] + - @backstage/core@0.6.3 + - @backstage/plugin-scaffolder@0.6.0 + - @backstage/plugin-cost-insights@0.8.2 + - @backstage/plugin-org@0.3.8 + - @backstage/plugin-catalog@0.4.0 + - @backstage/plugin-catalog-import@0.4.2 + - @backstage/plugin-techdocs@0.5.8 + - @backstage/plugin-explore@0.2.7 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/plugin-api-docs@0.4.7 + - @backstage/catalog-model@0.7.2 + - @backstage/cli@0.6.2 + - @backstage/plugin-tech-radar@0.3.6 + - @backstage/plugin-circleci@0.2.10 + - @backstage/plugin-cloudbuild@0.2.11 + - @backstage/plugin-github-actions@0.3.4 + - @backstage/plugin-jenkins@0.3.11 + - @backstage/plugin-kafka@0.2.4 + - @backstage/plugin-kubernetes@0.3.11 + - @backstage/plugin-lighthouse@0.2.12 + - @backstage/plugin-pagerduty@0.3.1 + - @backstage/plugin-register-component@0.2.11 + - @backstage/plugin-rollbar@0.3.2 + - @backstage/plugin-search@0.3.2 + - @backstage/plugin-sentry@0.3.7 + ## 0.2.16 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index c7c517b9ff..76a087a4fd 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,38 +1,38 @@ { "name": "example-app", - "version": "0.2.16", + "version": "0.2.17", "private": true, "bundled": true, "dependencies": { - "@backstage/catalog-model": "^0.7.1", - "@backstage/cli": "^0.6.1", - "@backstage/core": "^0.6.2", - "@backstage/plugin-api-docs": "^0.4.6", - "@backstage/plugin-catalog": "^0.3.2", - "@backstage/plugin-catalog-react": "^0.0.4", - "@backstage/plugin-catalog-import": "^0.4.1", - "@backstage/plugin-circleci": "^0.2.9", - "@backstage/plugin-cloudbuild": "^0.2.10", - "@backstage/plugin-cost-insights": "^0.8.1", - "@backstage/plugin-explore": "^0.2.6", + "@backstage/catalog-model": "^0.7.2", + "@backstage/cli": "^0.6.2", + "@backstage/core": "^0.6.3", + "@backstage/plugin-api-docs": "^0.4.7", + "@backstage/plugin-catalog": "^0.4.0", + "@backstage/plugin-catalog-react": "^0.1.0", + "@backstage/plugin-catalog-import": "^0.4.2", + "@backstage/plugin-circleci": "^0.2.10", + "@backstage/plugin-cloudbuild": "^0.2.11", + "@backstage/plugin-cost-insights": "^0.8.2", + "@backstage/plugin-explore": "^0.2.7", "@backstage/plugin-gcp-projects": "^0.2.4", - "@backstage/plugin-github-actions": "^0.3.3", + "@backstage/plugin-github-actions": "^0.3.4", "@backstage/plugin-gitops-profiles": "^0.2.5", "@backstage/plugin-graphiql": "^0.2.7", - "@backstage/plugin-org": "^0.3.7", - "@backstage/plugin-jenkins": "^0.3.10", - "@backstage/plugin-kafka": "^0.2.3", - "@backstage/plugin-kubernetes": "^0.3.10", - "@backstage/plugin-lighthouse": "^0.2.11", + "@backstage/plugin-org": "^0.3.8", + "@backstage/plugin-jenkins": "^0.3.11", + "@backstage/plugin-kafka": "^0.2.4", + "@backstage/plugin-kubernetes": "^0.3.11", + "@backstage/plugin-lighthouse": "^0.2.12", "@backstage/plugin-newrelic": "^0.2.5", - "@backstage/plugin-pagerduty": "0.3.0", - "@backstage/plugin-register-component": "^0.2.10", - "@backstage/plugin-rollbar": "^0.3.1", - "@backstage/plugin-scaffolder": "^0.5.1", - "@backstage/plugin-sentry": "^0.3.6", - "@backstage/plugin-search": "^0.3.1", - "@backstage/plugin-tech-radar": "^0.3.5", - "@backstage/plugin-techdocs": "^0.5.7", + "@backstage/plugin-pagerduty": "0.3.1", + "@backstage/plugin-register-component": "^0.2.11", + "@backstage/plugin-rollbar": "^0.3.2", + "@backstage/plugin-scaffolder": "^0.6.0", + "@backstage/plugin-sentry": "^0.3.7", + "@backstage/plugin-search": "^0.3.2", + "@backstage/plugin-tech-radar": "^0.3.6", + "@backstage/plugin-techdocs": "^0.5.8", "@backstage/plugin-user-settings": "^0.2.6", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", @@ -53,7 +53,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/test-utils": "^0.1.7", + "@backstage/test-utils": "^0.1.8", "@testing-library/cypress": "^7.0.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 05549b2085..84e51d6bcf 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,27 @@ # example-backend +## 0.2.17 + +### Patch Changes + +- Updated dependencies [a70af22a2] +- Updated dependencies [ec504e7b4] +- Updated dependencies [a5f42cf66] +- Updated dependencies [f37992797] +- Updated dependencies [bad21a085] +- Updated dependencies [1c06cb312] +- Updated dependencies [2499f6cde] +- Updated dependencies [a1f5e6545] + - @backstage/plugin-kubernetes-backend@0.2.7 + - @backstage/plugin-auth-backend@0.3.2 + - @backstage/plugin-scaffolder-backend@0.8.0 + - @backstage/plugin-techdocs-backend@0.6.2 + - @backstage/catalog-model@0.7.2 + - @backstage/plugin-app-backend@0.3.8 + - @backstage/plugin-catalog-backend@0.6.3 + - @backstage/config@0.1.3 + - example-app@0.2.17 + ## 0.2.15 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 413d157c2d..9220b71866 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.15", + "version": "0.2.17", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,23 +29,23 @@ "dependencies": { "@backstage/backend-common": "^0.5.3", "@backstage/catalog-client": "^0.3.6", - "@backstage/catalog-model": "^0.7.1", - "@backstage/config": "^0.1.2", - "@backstage/plugin-app-backend": "^0.3.7", - "@backstage/plugin-auth-backend": "^0.3.0", - "@backstage/plugin-catalog-backend": "^0.6.1", + "@backstage/catalog-model": "^0.7.2", + "@backstage/config": "^0.1.3", + "@backstage/plugin-app-backend": "^0.3.8", + "@backstage/plugin-auth-backend": "^0.3.2", + "@backstage/plugin-catalog-backend": "^0.6.3", "@backstage/plugin-graphql-backend": "^0.1.5", - "@backstage/plugin-kubernetes-backend": "^0.2.6", + "@backstage/plugin-kubernetes-backend": "^0.2.7", "@backstage/plugin-kafka-backend": "^0.2.0", "@backstage/plugin-proxy-backend": "^0.2.4", "@backstage/plugin-rollbar-backend": "^0.1.7", - "@backstage/plugin-scaffolder-backend": "^0.7.0", - "@backstage/plugin-techdocs-backend": "^0.6.0", + "@backstage/plugin-scaffolder-backend": "^0.8.0", + "@backstage/plugin-techdocs-backend": "^0.6.2", "@gitbeaker/node": "^28.0.2", "@octokit/rest": "^18.0.12", "azure-devops-node-api": "^10.1.1", "dockerode": "^3.2.1", - "example-app": "^0.2.15", + "example-app": "^0.2.17", "express": "^4.17.1", "express-promise-router": "^3.0.3", "knex": "^0.21.6", @@ -55,7 +55,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.6.0", + "@backstage/cli": "^0.6.2", "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5" diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index 8d581dcb0c..3351ed317a 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/catalog-model +## 0.7.2 + +### Patch Changes + +- bad21a085: Implement annotations for customising Entity URLs in the Catalog pages. +- Updated dependencies [a1f5e6545] + - @backstage/config@0.1.3 + ## 0.7.1 ### Patch Changes diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index af74b78a08..554eea0937 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "0.7.1", + "version": "0.7.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.2", + "@backstage/config": "^0.1.3", "@types/json-schema": "^7.0.5", "@types/yup": "^0.29.8", "ajv": "^7.0.3", @@ -39,7 +39,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.6.0", + "@backstage/cli": "^0.6.2", "@types/express": "^4.17.6", "@types/jest": "^26.0.7", "@types/lodash": "^4.14.151", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index f8c09d45b9..148af6fa8a 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/cli +## 0.6.2 + +### Patch Changes + +- e780e119c: Add missing `file-loader` dependency which could cause issues with loading images and other assets. +- 6266ddd11: The `yarn backstage-cli app:diff` has been broken since a couple of months. The command to perform updates `yarn backstage-cli versions:bump` prints change logs which seems to be a good replacement for this command. +- Updated dependencies [a1f5e6545] + - @backstage/config@0.1.3 + ## 0.6.1 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index a5dddefcd6..4a95df0f38 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.6.1", + "version": "0.6.2", "private": false, "publishConfig": { "access": "public" @@ -31,7 +31,7 @@ "@babel/core": "^7.4.4", "@babel/plugin-transform-modules-commonjs": "^7.4.4", "@backstage/cli-common": "^0.1.1", - "@backstage/config": "^0.1.2", + "@backstage/config": "^0.1.3", "@backstage/config-loader": "^0.5.1", "@hot-loader/react-dom": "^16.13.0", "@lerna/package-graph": "^4.0.0", @@ -117,10 +117,10 @@ }, "devDependencies": { "@backstage/backend-common": "^0.5.4", - "@backstage/config": "^0.1.2", - "@backstage/core": "^0.6.2", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.6", + "@backstage/config": "^0.1.3", + "@backstage/core": "^0.6.3", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@backstage/theme": "^0.2.3", "@types/diff": "^4.0.2", "@types/express": "^4.17.6", diff --git a/packages/config/CHANGELOG.md b/packages/config/CHANGELOG.md index 06a9d8e4f1..cfc48fe21a 100644 --- a/packages/config/CHANGELOG.md +++ b/packages/config/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/config +## 0.1.3 + +### Patch Changes + +- a1f5e6545: Adds an optional type to `config.get` & `config.getOptional`. This avoids the need for casting. For example: + + ```ts + const config = useApi(configApiRef); + + const myConfig = config.get('myPlugin.complexConfig'); + // vs + const myConfig config.get('myPlugin.complexConfig') as SomeTypeDefinition; + ``` + ## 0.1.2 ### Patch Changes diff --git a/packages/config/package.json b/packages/config/package.json index 4e6c2f7157..1eadb5d4e5 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config", "description": "Config API used by Backstage core, backend, and CLI", - "version": "0.1.2", + "version": "0.1.3", "private": false, "publishConfig": { "access": "public", diff --git a/packages/core-api/CHANGELOG.md b/packages/core-api/CHANGELOG.md index 14a5aa899d..7fa7e71828 100644 --- a/packages/core-api/CHANGELOG.md +++ b/packages/core-api/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/core-api +## 0.2.11 + +### Patch Changes + +- 3a58084b6: The `FlatRoutes` components now renders the not found page of the app if no routes are matched. +- 1407b34c6: More informative error message for missing ApiContext. +- b6c4f485d: Fix error when querying Backstage Identity with SAML authentication +- 3a58084b6: Created separate `AppContext` type to be returned from `useApp` rather than the `BackstageApp` itself. The `AppContext` type includes but deprecates `getPlugins`, `getProvider`, `getRouter`, and `getRoutes`. In addition, the `AppContext` adds a new `getComponents` method which providers access to the app components. +- Updated dependencies [a1f5e6545] + - @backstage/config@0.1.3 + ## 0.2.10 ### Patch Changes diff --git a/packages/core-api/package.json b/packages/core-api/package.json index cfe90ebb34..d3eb0d7791 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-api", "description": "Internal Core API used by Backstage plugins and apps", - "version": "0.2.10", + "version": "0.2.11", "private": false, "publishConfig": { "access": "public", @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.2", + "@backstage/config": "^0.1.3", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -42,8 +42,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/test-utils": "^0.1.6", + "@backstage/cli": "^0.6.2", + "@backstage/test-utils": "^0.1.8", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index bd800e8d63..81fe728101 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/core +## 0.6.3 + +### Patch Changes + +- 3a58084b6: The `FlatRoutes` components now renders the not found page of the app if no routes are matched. +- e799e74d4: Fix `OverflowTooltip` cutting off the bottom of letters like "g" and "y". +- 1407b34c6: More informative error message for missing ApiContext. +- 9615e68fb: Forward link styling of `EntityRefLink` and `EnriryRefLinks` into the underling + `Link`. +- 49f9b7346: Deprecate `type` of `ItemCard` and introduce new `subtitle` which allows passing + react nodes. +- 3a58084b6: Created separate `AppContext` type to be returned from `useApp` rather than the `BackstageApp` itself. The `AppContext` type includes but deprecates `getPlugins`, `getProvider`, `getRouter`, and `getRoutes`. In addition, the `AppContext` adds a new `getComponents` method which providers access to the app components. +- 2c1f2a7c2: Introduced generic OverflowTooltip component for cases where longer text needs to be truncated with ellipsis and show hover tooltip with full text. This is particularly useful in the cases where longer description text is rendered in table. e.g. CatalogTable and ApiExplorerTable. +- Updated dependencies [3a58084b6] +- Updated dependencies [1407b34c6] +- Updated dependencies [b6c4f485d] +- Updated dependencies [3a58084b6] +- Updated dependencies [a1f5e6545] + - @backstage/core-api@0.2.11 + - @backstage/config@0.1.3 + ## 0.6.2 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index f6afa76b7f..c2fae32d6c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core", "description": "Core API used by Backstage plugins and apps", - "version": "0.6.2", + "version": "0.6.3", "private": false, "publishConfig": { "access": "public", @@ -29,8 +29,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.2", - "@backstage/core-api": "^0.2.10", + "@backstage/config": "^0.1.3", + "@backstage/core-api": "^0.2.11", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -68,8 +68,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 1735d0c1a0..de59a343ed 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,169 @@ # @backstage/create-app +## 0.3.11 + +### Patch Changes + +- 4594f7efc: Add the google analytics scripts in the `index.html` template for new applications. + + To apply this change to an existing application, change the following in `packages\app\public\index.html`: + + ```diff + <%= app.title %> + + + <% if (app.googleAnalyticsTrackingId && typeof app.googleAnalyticsTrackingId + + === 'string') { %> + + + + + + <% } %> + + ``` + +- 08fa2176a: **BREAKING CHANGE** + + The Scaffolder and Catalog plugins have been migrated to partially require use of the [new composability API](https://backstage.io/docs/plugins/composability). The Scaffolder used to register its pages using the deprecated route registration plugin API, but those registrations have been removed. This means you now need to add the Scaffolder plugin page to the app directly. + + The Catalog plugin has also been migrated to use an [external route reference](https://backstage.io/docs/plugins/composability#binding-external-routes-in-the-app) to dynamically link to the create component page. This means you need to migrate the catalog plugin to use the new extension components, as well as bind the external route. + + Apply the following changes to `packages/app/src/App.tsx`: + + ```diff + -import { Router as CatalogRouter } from '@backstage/plugin-catalog'; + +import { + + catalogPlugin, + + CatalogIndexPage, + + CatalogEntityPage, + +} from '@backstage/plugin-catalog'; + +import { scaffolderPlugin, ScaffolderPage } from '@backstage/plugin-scaffolder'; + + # The following addition to the app config allows the catalog plugin to link to the + # component creation page, i.e. the scaffolder. You can chose a different target if you want to. + const app = createApp({ + apis, + plugins: Object.values(plugins), + + bindRoutes({ bind }) { + + bind(catalogPlugin.externalRoutes, { + + createComponent: scaffolderPlugin.routes.root, + + }); + + } + }); + + # Apply these changes within FlatRoutes. It is important to have migrated to using FlatRoutes + # for this to work, if you haven't done that yet, see the previous entries in this changelog. + - } + - /> + + } /> + + } + + > + + + + + } /> + + } /> + ``` + + The scaffolder has been redesigned to be horizontally scalable and to persistently store task state and execution logs in the database. Component registration has moved from the frontend into a separate registration step executed by the `TaskWorker`. This requires that a `CatalogClient` is passed to the scaffolder backend instead of the old `CatalogEntityClient`. + + The default catalog client comes from the `@backstage/catalog-client`, which you need to add as a dependency in `packages/backend/package.json`. + + Once the dependency has been added, apply the following changes to`packages/backend/src/plugins/scaffolder.ts`: + + ```diff + import { + CookieCutter, + createRouter, + Preparers, + Publishers, + CreateReactAppTemplater, + Templaters, + - CatalogEntityClient, + } from '@backstage/plugin-scaffolder-backend'; + +import { CatalogClient } from '@backstage/catalog-client'; + + const discovery = SingleHostDiscovery.fromConfig(config); + -const entityClient = new CatalogEntityClient({ discovery }); + +const catalogClient = new CatalogClient({ discoveryApi: discovery }) + + return await createRouter({ + preparers, + templaters, + publishers, + logger, + config, + dockerClient, + - entityClient, + database, + + catalogClient, + }); + ``` + + See the `@backstage/scaffolder-backend` changelog for more information about this change. + +- Updated dependencies [ec504e7b4] +- Updated dependencies [3a58084b6] +- Updated dependencies [a5f42cf66] +- Updated dependencies [e488f0502] +- Updated dependencies [e799e74d4] +- Updated dependencies [dc12852c9] +- Updated dependencies [a5f42cf66] +- Updated dependencies [a8953a9c9] +- Updated dependencies [f37992797] +- Updated dependencies [347137ccf] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [d6593abe6] +- Updated dependencies [bad21a085] +- Updated dependencies [e8e35fb5f] +- Updated dependencies [9615e68fb] +- Updated dependencies [e780e119c] +- Updated dependencies [437bac549] +- Updated dependencies [9f2b3a26e] +- Updated dependencies [49f9b7346] +- Updated dependencies [1c06cb312] +- Updated dependencies [968b588f7] +- Updated dependencies [3a58084b6] +- Updated dependencies [2499f6cde] +- Updated dependencies [5469a9761] +- Updated dependencies [a1f5e6545] +- Updated dependencies [60d1bc3e7] +- Updated dependencies [2c1f2a7c2] +- Updated dependencies [6266ddd11] + - @backstage/plugin-auth-backend@0.3.2 + - @backstage/core@0.6.3 + - @backstage/plugin-scaffolder@0.6.0 + - @backstage/plugin-scaffolder-backend@0.8.0 + - @backstage/test-utils@0.1.8 + - @backstage/plugin-catalog@0.4.0 + - @backstage/plugin-catalog-import@0.4.2 + - @backstage/plugin-techdocs@0.5.8 + - @backstage/plugin-techdocs-backend@0.6.2 + - @backstage/plugin-explore@0.2.7 + - @backstage/plugin-api-docs@0.4.7 + - @backstage/catalog-model@0.7.2 + - @backstage/cli@0.6.2 + - @backstage/plugin-tech-radar@0.3.6 + - @backstage/plugin-app-backend@0.3.8 + - @backstage/plugin-catalog-backend@0.6.3 + - @backstage/config@0.1.3 + - @backstage/plugin-circleci@0.2.10 + - @backstage/plugin-github-actions@0.3.4 + - @backstage/plugin-lighthouse@0.2.12 + - @backstage/plugin-search@0.3.2 + ## 0.3.10 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index b427b1e2ed..110bd3634c 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "Create app package for Backstage", - "version": "0.3.10", + "version": "0.3.11", "private": false, "publishConfig": { "access": "public" @@ -46,30 +46,30 @@ "peerDependencies": { "@backstage/backend-common": "^0.5.4", "@backstage/catalog-client": "^0.3.6", - "@backstage/catalog-model": "^0.7.1", - "@backstage/cli": "^0.6.1", - "@backstage/config": "^0.1.2", - "@backstage/core": "^0.6.2", - "@backstage/plugin-api-docs": "^0.4.6", - "@backstage/plugin-app-backend": "^0.3.7", - "@backstage/plugin-auth-backend": "^0.3.1", - "@backstage/plugin-catalog": "^0.3.2", - "@backstage/plugin-catalog-backend": "^0.6.2", - "@backstage/plugin-catalog-import": "^0.4.1", - "@backstage/plugin-circleci": "^0.2.9", - "@backstage/plugin-explore": "^0.2.6", - "@backstage/plugin-github-actions": "^0.3.3", - "@backstage/plugin-lighthouse": "^0.2.11", + "@backstage/catalog-model": "^0.7.2", + "@backstage/cli": "^0.6.2", + "@backstage/config": "^0.1.3", + "@backstage/core": "^0.6.3", + "@backstage/plugin-api-docs": "^0.4.7", + "@backstage/plugin-app-backend": "^0.3.8", + "@backstage/plugin-auth-backend": "^0.3.2", + "@backstage/plugin-catalog": "^0.4.0", + "@backstage/plugin-catalog-backend": "^0.6.3", + "@backstage/plugin-catalog-import": "^0.4.2", + "@backstage/plugin-circleci": "^0.2.10", + "@backstage/plugin-explore": "^0.2.7", + "@backstage/plugin-github-actions": "^0.3.4", + "@backstage/plugin-lighthouse": "^0.2.12", "@backstage/plugin-proxy-backend": "^0.2.4", "@backstage/plugin-rollbar-backend": "^0.1.7", - "@backstage/plugin-scaffolder": "^0.5.1", - "@backstage/plugin-search": "^0.3.1", - "@backstage/plugin-scaffolder-backend": "^0.7.1", - "@backstage/plugin-tech-radar": "^0.3.5", - "@backstage/plugin-techdocs": "^0.5.7", - "@backstage/plugin-techdocs-backend": "^0.6.1", + "@backstage/plugin-scaffolder": "^0.6.0", + "@backstage/plugin-search": "^0.3.2", + "@backstage/plugin-scaffolder-backend": "^0.8.0", + "@backstage/plugin-tech-radar": "^0.3.6", + "@backstage/plugin-techdocs": "^0.5.8", + "@backstage/plugin-techdocs-backend": "^0.6.2", "@backstage/plugin-user-settings": "^0.2.6", - "@backstage/test-utils": "^0.1.7", + "@backstage/test-utils": "^0.1.8", "@backstage/theme": "^0.2.3" }, "nodemonConfig": { diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 52de79ca58..f02ce3ae23 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/dev-utils +## 0.1.12 + +### Patch Changes + +- 5aa4ceea6: Make sure to provide dummy routes for all external routes of plugins given to DevApp +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [dc12852c9] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/test-utils@0.1.8 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.1.11 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index abe21fe0f1..0d2af1bc69 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "0.1.11", + "version": "0.1.12", "private": false, "publishConfig": { "access": "public", @@ -29,10 +29,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.6.2", - "@backstage/catalog-model": "^0.7.1", - "@backstage/plugin-catalog-react": "^0.0.4", - "@backstage/test-utils": "^0.1.7", + "@backstage/core": "^0.6.3", + "@backstage/catalog-model": "^0.7.2", + "@backstage/plugin-catalog-react": "^0.1.0", + "@backstage/test-utils": "^0.1.8", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,7 +47,7 @@ "react-router-dom": "6.0.0-beta.0" }, "devDependencies": { - "@backstage/cli": "^0.6.1", + "@backstage/cli": "^0.6.2", "@types/jest": "^26.0.7", "@types/node": "^12.0.0" }, diff --git a/packages/techdocs-common/CHANGELOG.md b/packages/techdocs-common/CHANGELOG.md index caaa9f5e2f..60a10c924e 100644 --- a/packages/techdocs-common/CHANGELOG.md +++ b/packages/techdocs-common/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/techdocs-common +## 0.4.2 + +### Patch Changes + +- 2499f6cde: Add support for assuming role in AWS integrations +- 1e4ddd71d: Fix AWS, GCS and Azure publisher to work on Windows. +- Updated dependencies [bad21a085] +- Updated dependencies [a1f5e6545] + - @backstage/catalog-model@0.7.2 + - @backstage/config@0.1.3 + ## 0.4.1 ### Patch Changes diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index 9363334641..c8ae633c97 100644 --- a/packages/techdocs-common/package.json +++ b/packages/techdocs-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/techdocs-common", "description": "Common functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", - "version": "0.4.1", + "version": "0.4.2", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -39,8 +39,8 @@ "@azure/identity": "^1.2.2", "@azure/storage-blob": "^12.4.0", "@backstage/backend-common": "^0.5.4", - "@backstage/catalog-model": "^0.7.1", - "@backstage/config": "^0.1.2", + "@backstage/catalog-model": "^0.7.2", + "@backstage/config": "^0.1.3", "@backstage/integration": "^0.5.0", "@google-cloud/storage": "^5.6.0", "@types/dockerode": "^3.2.1", @@ -60,7 +60,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.6.1", + "@backstage/cli": "^0.6.2", "@types/fs-extra": "^9.0.5", "@types/git-url-parse": "^9.0.0", "@types/js-yaml": "^3.12.5", diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index a30b2b3f6a..50f656ec67 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/test-utils +## 0.1.8 + +### Patch Changes + +- dc12852c9: Allow `ExternalRouteRef` instances to be passed as a route ref to `mountedRoutes`. +- Updated dependencies [3a58084b6] +- Updated dependencies [1407b34c6] +- Updated dependencies [b6c4f485d] +- Updated dependencies [3a58084b6] + - @backstage/core-api@0.2.11 + ## 0.1.7 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 5a4ad2b4a4..fbbd4f9eca 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "0.1.7", + "version": "0.1.8", "private": false, "publishConfig": { "access": "public", @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-api": "^0.2.7", + "@backstage/core-api": "^0.2.11", "@backstage/test-utils-core": "^0.1.1", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", @@ -45,7 +45,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.0", + "@backstage/cli": "^0.6.2", "@types/jest": "^26.0.7", "@types/node": "^12.0.0" }, diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index a6aa6c1e2d..480beeff16 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-api-docs +## 0.4.7 + +### Patch Changes + +- d6593abe6: Remove domain column from `HasSystemsCard` and system from `HasComponentsCard`, + `HasSubcomponentsCard`, and `HasApisCard`. +- 437bac549: Make the description column in the catalog table and api-docs table use up as + much space as possible before hiding overflowing text. +- 5469a9761: Changes made in CatalogTable and ApiExplorerTable for using the OverflowTooltip component for truncating large description and showing tooltip on hover-over. +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.4.6 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 4182ccbe14..bdf6ccf47c 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.4.6", + "version": "0.4.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,9 +30,9 @@ }, "dependencies": { "@asyncapi/react-component": "^0.18.2", - "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.2", - "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/catalog-model": "^0.7.2", + "@backstage/core": "^0.6.3", + "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/theme": "^0.2.3", "@material-icons/font": "^1.0.2", "@material-ui/core": "^4.11.0", @@ -49,9 +49,9 @@ "swagger-ui-react": "^3.37.2" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index f39dad2eb9..06ec3e150f 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-app-backend +## 0.3.8 + +### Patch Changes + +- 1c06cb312: Clarify troubleshooting steps for schema serialization issues. +- Updated dependencies [a1f5e6545] + - @backstage/config@0.1.3 + ## 0.3.7 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 52ebab520d..af5a1e2f3d 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-backend", - "version": "0.3.7", + "version": "0.3.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,7 +31,7 @@ "dependencies": { "@backstage/backend-common": "^0.5.3", "@backstage/config-loader": "^0.5.1", - "@backstage/config": "^0.1.2", + "@backstage/config": "^0.1.3", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^3.0.3", @@ -40,7 +40,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.6.0", + "@backstage/cli": "^0.6.2", "@types/supertest": "^2.0.8", "msw": "^0.20.5", "supertest": "^4.0.2" diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 3d69afee37..14d70ba1f5 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-backend +## 0.3.2 + +### Patch Changes + +- ec504e7b4: Fix for refresh token being lost during Microsoft login. +- Updated dependencies [bad21a085] +- Updated dependencies [a1f5e6545] + - @backstage/catalog-model@0.7.2 + - @backstage/config@0.1.3 + ## 0.3.1 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index f206c0046d..f9dfdd5215 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.3.1", + "version": "0.3.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,8 +31,8 @@ "dependencies": { "@backstage/backend-common": "^0.5.4", "@backstage/catalog-client": "^0.3.6", - "@backstage/catalog-model": "^0.7.1", - "@backstage/config": "^0.1.2", + "@backstage/catalog-model": "^0.7.2", + "@backstage/config": "^0.1.3", "@types/express": "^4.17.6", "@types/passport": "^1.0.3", "compression": "^1.7.4", @@ -66,7 +66,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.6.1", + "@backstage/cli": "^0.6.2", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index dce76b57a3..42315b3802 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend +## 0.6.3 + +### Patch Changes + +- 2499f6cde: Add support for assuming role in AWS integrations +- Updated dependencies [bad21a085] +- Updated dependencies [a1f5e6545] + - @backstage/catalog-model@0.7.2 + - @backstage/config@0.1.3 + ## 0.6.2 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index bc087a3017..0925870506 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.6.2", + "version": "0.6.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,8 +31,8 @@ "dependencies": { "@azure/msal-node": "^1.0.0-beta.3", "@backstage/backend-common": "^0.5.4", - "@backstage/catalog-model": "^0.7.1", - "@backstage/config": "^0.1.2", + "@backstage/catalog-model": "^0.7.2", + "@backstage/config": "^0.1.3", "@backstage/integration": "^0.5.0", "@octokit/graphql": "^4.5.8", "@types/express": "^4.17.6", @@ -59,8 +59,8 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/test-utils": "^0.1.8", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", "@types/lodash": "^4.14.151", diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index e12718bc96..fce40aacfc 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog-import +## 0.4.2 + +### Patch Changes + +- a8953a9c9: This updates the `catalog-import` plugin to omit the default metadata namespace + field and also use the short form entity reference format for selected group owners. +- 968b588f7: Allows the CodeOwnersProcessor to set the owner automatically within the catalog-import plugin. This adds an additional checkbox that overrides the group selector and will omit the owner option in the generated catalog file yaml. +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.4.1 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index ad196e4f1f..a427a0e884 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.4.1", + "version": "0.4.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,11 +30,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.1", + "@backstage/catalog-model": "^0.7.2", "@backstage/catalog-client": "^0.3.6", - "@backstage/core": "^0.6.2", + "@backstage/core": "^0.6.3", "@backstage/integration": "^0.5.0", - "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -51,9 +51,9 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 07898af872..85035880eb 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-catalog-react +## 0.1.0 + +### Minor Changes + +- d0760ecdf: Moved common useStarredEntities hook to plugin-catalog-react + +### Patch Changes + +- 88f1f1b60: Truncate and show ellipsis with tooltip if content of + `createMetadataDescriptionColumn` is too wide. +- 9615e68fb: Forward link styling of `EntityRefLink` and `EnriryRefLinks` into the underling + `Link`. +- 5c2e2863f: Added the proper type parameters to entityRouteRef. +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [1407b34c6] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/catalog-model@0.7.2 + ## 0.0.4 ### Patch Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 432c59ef3b..7408e1d596 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-react", - "version": "0.0.4", + "version": "0.1.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,8 +29,8 @@ }, "dependencies": { "@backstage/catalog-client": "^0.3.6", - "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.2", + "@backstage/catalog-model": "^0.7.2", + "@backstage/core": "^0.6.3", "@material-ui/core": "^4.11.0", "@types/react": "^16.9", "react": "^16.13.1", @@ -39,9 +39,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 6dc2913b60..129bb98346 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,70 @@ # @backstage/plugin-catalog +## 0.4.0 + +### Minor Changes + +- a5f42cf66: The Scaffolder and Catalog plugins have been migrated to partially require use of the [new composability API](https://backstage.io/docs/plugins/composability). The Scaffolder used to register its pages using the deprecated route registration plugin API, but those registrations have been removed. This means you now need to add the Scaffolder plugin page to the app directly. + + The page is imported from the Scaffolder plugin and added to the `` component: + + ```tsx + } /> + ``` + + The Catalog plugin has also been migrated to use an [external route reference](https://backstage.io/docs/plugins/composability#binding-external-routes-in-the-app) to dynamically link to the create component page. This means you need to migrate the catalog plugin to use the new extension components, as well as bind the external route. + + To use the new extension components, replace existing usage of the `CatalogRouter` with the following: + + ```tsx + } /> + }> + + + ``` + + And to bind the external route from the catalog plugin to the scaffolder template index page, make sure you have the appropriate imports and add the following to the `createApp` call: + + ```ts + import { catalogPlugin } from '@backstage/plugin-catalog'; + import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; + + const app = createApp({ + // ... + bindRoutes({ bind }) { + bind(catalogPlugin.externalRoutes, { + createComponent: scaffolderPlugin.routes.root, + }); + }, + }); + ``` + +- d0760ecdf: Moved common useStarredEntities hook to plugin-catalog-react + +### Patch Changes + +- d6593abe6: Remove domain column from `HasSystemsCard` and system from `HasComponentsCard`, + `HasSubcomponentsCard`, and `HasApisCard`. +- bad21a085: Implement annotations for customising Entity URLs in the Catalog pages. +- 437bac549: Make the description column in the catalog table and api-docs table use up as + much space as possible before hiding overflowing text. +- 5469a9761: Changes made in CatalogTable and ApiExplorerTable for using the OverflowTooltip component for truncating large description and showing tooltip on hover-over. +- 60d1bc3e7: Fix Japanese Good Morning +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.3.2 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 6efb450597..d6dfc8da5a 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.3.2", + "version": "0.4.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,9 @@ }, "dependencies": { "@backstage/catalog-client": "^0.3.6", - "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.2", - "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/catalog-model": "^0.7.2", + "@backstage/core": "^0.6.3", + "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -50,9 +50,9 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@microsoft/microsoft-graph-types": "^1.25.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index 47e15ac88e..b849abe379 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-circleci +## 0.2.10 + +### Patch Changes + +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.2.9 ### Patch Changes diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 9c7794dd59..3c46020d3a 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-circleci", - "version": "0.2.9", + "version": "0.2.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,9 @@ "postpack": "backstage-cli postpack" }, "dependencies": { - "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.2", - "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/catalog-model": "^0.7.2", + "@backstage/core": "^0.6.3", + "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -50,9 +50,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md index 498f32489b..f79281be18 100644 --- a/plugins/cloudbuild/CHANGELOG.md +++ b/plugins/cloudbuild/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-cloudbuild +## 0.2.11 + +### Patch Changes + +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.2.10 ### Patch Changes diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 1638bd1446..07f7c03a0c 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-cloudbuild", - "version": "0.2.10", + "version": "0.2.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,9 +30,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.1", - "@backstage/plugin-catalog-react": "^0.0.4", - "@backstage/core": "^0.6.2", + "@backstage/catalog-model": "^0.7.2", + "@backstage/plugin-catalog-react": "^0.1.0", + "@backstage/core": "^0.6.3", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,9 +47,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index e672b33976..622454d2a9 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-cost-insights +## 0.8.2 + +### Patch Changes + +- 38205492a: Default alert properties can be overridden using accessors +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [1407b34c6] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [3a58084b6] +- Updated dependencies [a1f5e6545] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/config@0.1.3 + ## 0.8.1 ### Patch Changes diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index d4b8ee1ad0..e306a7fa6e 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-cost-insights", - "version": "0.8.1", + "version": "0.8.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,8 +30,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.2", - "@backstage/core": "^0.6.2", + "@backstage/config": "^0.1.3", + "@backstage/core": "^0.6.3", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -55,9 +55,9 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/explore/CHANGELOG.md b/plugins/explore/CHANGELOG.md index 698cf08c34..be284dfa64 100644 --- a/plugins/explore/CHANGELOG.md +++ b/plugins/explore/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-explore +## 0.2.7 + +### Patch Changes + +- 347137ccf: Display the owner of a domain on the domain card. +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.2.6 ### Patch Changes diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 697a8c67fe..5c370ecfac 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-explore", - "version": "0.2.6", + "version": "0.2.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,9 +30,9 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.2", - "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/catalog-model": "^0.7.2", + "@backstage/core": "^0.6.3", + "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/plugin-explore-react": "^0.0.2", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", @@ -45,9 +45,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/fossa/CHANGELOG.md b/plugins/fossa/CHANGELOG.md index ebbfef0ff3..23129f83f9 100644 --- a/plugins/fossa/CHANGELOG.md +++ b/plugins/fossa/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-fossa +## 0.2.3 + +### Patch Changes + +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.2.2 ### Patch Changes diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index 6cc2c30b29..803b7ddf30 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-fossa", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.2", - "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/catalog-model": "^0.7.2", + "@backstage/core": "^0.6.3", + "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -44,9 +44,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index d5b19fa239..b1f14753f2 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.6.2", + "@backstage/core": "^0.6.3", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,9 +41,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index 4dff868c42..ac717c1059 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-github-actions +## 0.3.4 + +### Patch Changes + +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.3.3 ### Patch Changes diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 44599c8c36..eb203aaac8 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-github-actions", - "version": "0.3.3", + "version": "0.3.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,9 +32,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.1", - "@backstage/plugin-catalog-react": "^0.0.4", - "@backstage/core": "^0.6.2", + "@backstage/catalog-model": "^0.7.2", + "@backstage/plugin-catalog-react": "^0.1.0", + "@backstage/core": "^0.6.3", "@backstage/integration": "^0.5.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", @@ -50,9 +50,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 415c43c598..1902bebc70 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.6.2", + "@backstage/core": "^0.6.3", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -42,9 +42,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index 48e87877d4..fbd56d7255 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.6.2", + "@backstage/core": "^0.6.3", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,9 +43,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md index 872b2c3146..8ba44c30f5 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-jenkins +## 0.3.11 + +### Patch Changes + +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.3.10 ### Patch Changes diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index bad68dadb5..817e6ae1a3 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-jenkins", - "version": "0.3.10", + "version": "0.3.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.2", - "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/catalog-model": "^0.7.2", + "@backstage/core": "^0.6.3", + "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,9 +47,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/kafka/CHANGELOG.md b/plugins/kafka/CHANGELOG.md index 7972091b84..a8153002c9 100644 --- a/plugins/kafka/CHANGELOG.md +++ b/plugins/kafka/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-kafka +## 0.2.4 + +### Patch Changes + +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.2.3 ### Patch Changes diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index df8581751e..5c8dc9e934 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kafka", - "version": "0.2.3", + "version": "0.2.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.2", - "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/catalog-model": "^0.7.2", + "@backstage/core": "^0.6.3", + "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -33,9 +33,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/react-hooks": "^3.4.2", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 08a3a66657..e02c3e14a0 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-kubernetes-backend +## 0.2.7 + +### Patch Changes + +- a70af22a2: update kubernetes plugin backend function to use classes +- Updated dependencies [bad21a085] +- Updated dependencies [a1f5e6545] + - @backstage/catalog-model@0.7.2 + - @backstage/config@0.1.3 + ## 0.2.6 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 3823ee1899..9d5a0c25e0 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-backend", - "version": "0.2.6", + "version": "0.2.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,8 +33,8 @@ "dependencies": { "@aws-sdk/credential-provider-node": "^3.3.0", "@backstage/backend-common": "^0.5.2", - "@backstage/catalog-model": "^0.7.1", - "@backstage/config": "^0.1.2", + "@backstage/catalog-model": "^0.7.2", + "@backstage/config": "^0.1.3", "@kubernetes/client-node": "^0.13.2", "@types/express": "^4.17.6", "aws4": "^1.11.0", @@ -51,7 +51,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.6.0", + "@backstage/cli": "^0.6.2", "@types/aws4": "^1.5.1", "supertest": "^4.0.2" }, diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index 4be7017d35..75df5943dd 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-kubernetes +## 0.3.11 + +### Patch Changes + +- Updated dependencies [a70af22a2] +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [a1f5e6545] +- Updated dependencies [2c1f2a7c2] + - @backstage/plugin-kubernetes-backend@0.2.7 + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + - @backstage/config@0.1.3 + ## 0.3.10 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index e06d53989c..d38b56653f 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.3.10", + "version": "0.3.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,11 +31,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.1", - "@backstage/plugin-catalog-react": "^0.0.4", - "@backstage/config": "^0.1.2", - "@backstage/core": "^0.6.2", - "@backstage/plugin-kubernetes-backend": "^0.2.6", + "@backstage/catalog-model": "^0.7.2", + "@backstage/plugin-catalog-react": "^0.1.0", + "@backstage/config": "^0.1.3", + "@backstage/core": "^0.6.3", + "@backstage/plugin-kubernetes-backend": "^0.2.7", "@backstage/theme": "^0.2.3", "@kubernetes/client-node": "^0.13.2", "@material-ui/core": "^4.11.0", @@ -48,9 +48,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index e4697a66d7..20b1a5c1e1 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-lighthouse +## 0.2.12 + +### Patch Changes + +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [a1f5e6545] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + - @backstage/config@0.1.3 + ## 0.2.11 ### Patch Changes diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 899c173616..a8f231cc1b 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-lighthouse", - "version": "0.2.11", + "version": "0.2.12", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/catalog-model": "^0.7.1", - "@backstage/config": "^0.1.2", - "@backstage/core": "^0.6.2", - "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/catalog-model": "^0.7.2", + "@backstage/config": "^0.1.3", + "@backstage/core": "^0.6.3", + "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -46,9 +46,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index daf48cb1bb..d5aeaa0e5e 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.6.2", + "@backstage/core": "^0.6.3", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,9 +41,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 9347ca7ca1..474d44a735 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-org +## 0.3.8 + +### Patch Changes + +- e3bc5aad7: Use the `pageTheme` to colour the OwnershipCard boxes with their respective theme colours. +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [b6c4f485d] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core-api@0.2.11 + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.3.7 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index 2d114a9184..a9b5661c2e 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.3.7", + "version": "0.3.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,10 +20,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.2", - "@backstage/core-api": "^0.2.10", - "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/catalog-model": "^0.7.2", + "@backstage/core": "^0.6.3", + "@backstage/core-api": "^0.2.11", + "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -35,9 +35,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/pagerduty/CHANGELOG.md b/plugins/pagerduty/CHANGELOG.md index ec4bda11ea..85547d08c7 100644 --- a/plugins/pagerduty/CHANGELOG.md +++ b/plugins/pagerduty/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-pagerduty +## 0.3.1 + +### Patch Changes + +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.3.0 ### Minor Changes diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index a03462df7d..3e10be41ea 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-pagerduty", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,9 +30,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.2", - "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/catalog-model": "^0.7.2", + "@backstage/core": "^0.6.3", + "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -46,9 +46,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/register-component/CHANGELOG.md b/plugins/register-component/CHANGELOG.md index e1786c95c7..c37820a2c7 100644 --- a/plugins/register-component/CHANGELOG.md +++ b/plugins/register-component/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-register-component +## 0.2.11 + +### Patch Changes + +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.2.10 ### Patch Changes diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 78a8323fdc..92ea4f0f6d 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-register-component", - "version": "0.2.10", + "version": "0.2.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,9 +30,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.2", - "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/catalog-model": "^0.7.2", + "@backstage/core": "^0.6.3", + "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -45,9 +45,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index a9a28b4e95..32995efab8 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-rollbar +## 0.3.2 + +### Patch Changes + +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.3.1 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index ba8947850e..1ce8007683 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar", - "version": "0.3.1", + "version": "0.3.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.2", - "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/catalog-model": "^0.7.2", + "@backstage/core": "^0.6.3", + "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,9 +47,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 2e56afc8c8..e7e3b0d718 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,65 @@ # @backstage/plugin-scaffolder-backend +## 0.8.0 + +### Minor Changes + +- a5f42cf66: # Stateless scaffolding + + The scaffolder has been redesigned to be horizontally scalable and to persistently store task state and execution logs in the database. + + Each scaffolder task is given a unique task ID which is persisted in the database. + Tasks are then picked up by a `TaskWorker` which performs the scaffolding steps. + Execution logs are also persisted in the database meaning you can now refresh the scaffolder task status page without losing information. + + The task status page is now dynamically created based on the step information stored in the database. + This allows for custom steps to be displayed once the next version of the scaffolder template schema is available. + + The task page is updated to display links to both the git repository and to the newly created catalog entity. + + Component registration has moved from the frontend into a separate registration step executed by the `TaskWorker`. This requires that a `CatalogClient` is passed to the scaffolder backend instead of the old `CatalogEntityClient`. + + Make sure to update `plugins/scaffolder.ts` + + ```diff + import { + CookieCutter, + createRouter, + Preparers, + Publishers, + CreateReactAppTemplater, + Templaters, + - CatalogEntityClient, + } from '@backstage/plugin-scaffolder-backend'; + + +import { CatalogClient } from '@backstage/catalog-client'; + + const discovery = SingleHostDiscovery.fromConfig(config); + -const entityClient = new CatalogEntityClient({ discovery }); + +const catalogClient = new CatalogClient({ discoveryApi: discovery }) + + return await createRouter({ + preparers, + templaters, + publishers, + logger, + config, + dockerClient, + - entityClient, + database, + + catalogClient, + }); + ``` + + As well as adding the `@backstage/catalog-client` packages as a dependency of your backend package. + +### Patch Changes + +- Updated dependencies [bad21a085] +- Updated dependencies [a1f5e6545] + - @backstage/catalog-model@0.7.2 + - @backstage/config@0.1.3 + ## 0.7.1 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 24636ce20e..4d9d5f5404 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "0.7.1", + "version": "0.8.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,8 +31,8 @@ "dependencies": { "@backstage/backend-common": "^0.5.4", "@backstage/catalog-client": "^0.3.6", - "@backstage/catalog-model": "^0.7.1", - "@backstage/config": "^0.1.2", + "@backstage/catalog-model": "^0.7.2", + "@backstage/config": "^0.1.3", "@backstage/integration": "^0.5.0", "@gitbeaker/core": "^28.0.2", "@gitbeaker/node": "^28.0.2", @@ -63,8 +63,8 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/test-utils": "^0.1.8", "@types/fs-extra": "^9.0.1", "@types/mock-fs": "^4.13.0", "@types/supertest": "^2.0.8", diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 34b042b3a9..5d5523f821 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,117 @@ # @backstage/plugin-scaffolder +## 0.6.0 + +### Minor Changes + +- a5f42cf66: The Scaffolder and Catalog plugins have been migrated to partially require use of the [new composability API](https://backstage.io/docs/plugins/composability). The Scaffolder used to register its pages using the deprecated route registration plugin API, but those registrations have been removed. This means you now need to add the Scaffolder plugin page to the app directly. + + The page is imported from the Scaffolder plugin and added to the `` component: + + ```tsx + } /> + ``` + + The Catalog plugin has also been migrated to use an [external route reference](https://backstage.io/docs/plugins/composability#binding-external-routes-in-the-app) to dynamically link to the create component page. This means you need to migrate the catalog plugin to use the new extension components, as well as bind the external route. + + To use the new extension components, replace existing usage of the `CatalogRouter` with the following: + + ```tsx + } /> + }> + + + ``` + + And to bind the external route from the catalog plugin to the scaffolder template index page, make sure you have the appropriate imports and add the following to the `createApp` call: + + ```ts + import { catalogPlugin } from '@backstage/plugin-catalog'; + import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; + + const app = createApp({ + // ... + bindRoutes({ bind }) { + bind(catalogPlugin.externalRoutes, { + createComponent: scaffolderPlugin.routes.root, + }); + }, + }); + ``` + +- d0760ecdf: Moved common useStarredEntities hook to plugin-catalog-react +- e8e35fb5f: Adding Search and Filter features to Scaffolder/Templates Grid + +### Patch Changes + +- a5f42cf66: # Stateless scaffolding + + The scaffolder has been redesigned to be horizontally scalable and to persistently store task state and execution logs in the database. + + Each scaffolder task is given a unique task ID which is persisted in the database. + Tasks are then picked up by a `TaskWorker` which performs the scaffolding steps. + Execution logs are also persisted in the database meaning you can now refresh the scaffolder task status page without losing information. + + The task status page is now dynamically created based on the step information stored in the database. + This allows for custom steps to be displayed once the next version of the scaffolder template schema is available. + + The task page is updated to display links to both the git repository and to the newly created catalog entity. + + Component registration has moved from the frontend into a separate registration step executed by the `TaskWorker`. This requires that a `CatalogClient` is passed to the scaffolder backend instead of the old `CatalogEntityClient`. + + Make sure to update `plugins/scaffolder.ts` + + ```diff + import { + CookieCutter, + createRouter, + Preparers, + Publishers, + CreateReactAppTemplater, + Templaters, + - CatalogEntityClient, + } from '@backstage/plugin-scaffolder-backend'; + + +import { CatalogClient } from '@backstage/catalog-client'; + + const discovery = SingleHostDiscovery.fromConfig(config); + -const entityClient = new CatalogEntityClient({ discovery }); + +const catalogClient = new CatalogClient({ discoveryApi: discovery }) + + return await createRouter({ + preparers, + templaters, + publishers, + logger, + config, + dockerClient, + - entityClient, + database, + + catalogClient, + }); + ``` + + As well as adding the `@backstage/catalog-client` packages as a dependency of your backend package. + +- e488f0502: Update messages that process during loading, error, and no templates found. + Remove unused dependencies. +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [a1f5e6545] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + - @backstage/config@0.1.3 + ## 0.5.1 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 2018887013..7b288a25f1 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "0.5.1", + "version": "0.6.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ }, "dependencies": { "@backstage/catalog-client": "^0.3.6", - "@backstage/catalog-model": "^0.7.1", - "@backstage/config": "^0.1.2", - "@backstage/core": "^0.6.2", - "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/catalog-model": "^0.7.2", + "@backstage/config": "^0.1.3", + "@backstage/core": "^0.6.3", + "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -56,9 +56,9 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 887d922613..a4715a9ce1 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-search +## 0.3.2 + +### Patch Changes + +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.3.1 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index e1770abfa2..0f407bc22e 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "0.3.1", + "version": "0.3.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,9 +29,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.6.2", - "@backstage/catalog-model": "^0.7.1", - "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/core": "^0.6.3", + "@backstage/catalog-model": "^0.7.2", + "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,9 +43,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index d8446686b3..2902faab77 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-sentry +## 0.3.7 + +### Patch Changes + +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.3.6 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index a3a88e1bee..f010bd3e47 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sentry", - "version": "0.3.6", + "version": "0.3.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.2", - "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/catalog-model": "^0.7.2", + "@backstage/core": "^0.6.3", + "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -46,9 +46,9 @@ "timeago.js": "^4.0.2" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/sonarqube/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index 85d861e5d3..677cdcbb32 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-sonarqube +## 0.1.13 + +### Patch Changes + +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.1.12 ### Patch Changes diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index 0219a9d8fa..ba6c173a59 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sonarqube", - "version": "0.1.12", + "version": "0.1.13", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,9 +32,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.1", - "@backstage/plugin-catalog-react": "^0.0.4", - "@backstage/core": "^0.6.2", + "@backstage/catalog-model": "^0.7.2", + "@backstage/plugin-catalog-react": "^0.1.0", + "@backstage/core": "^0.6.3", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,9 +47,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/splunk-on-call/CHANGELOG.md b/plugins/splunk-on-call/CHANGELOG.md index ad98bf1feb..bff3bcf8bf 100644 --- a/plugins/splunk-on-call/CHANGELOG.md +++ b/plugins/splunk-on-call/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-splunk-on-call +## 0.1.3 + +### Patch Changes + +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + ## 0.1.2 ### Patch Changes diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index 3573c1bc5e..8600afb105 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-splunk-on-call", - "version": "0.1.2", + "version": "0.1.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,9 +30,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.2", - "@backstage/plugin-catalog-react": "^0.0.4", + "@backstage/catalog-model": "^0.7.2", + "@backstage/core": "^0.6.3", + "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -45,9 +45,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/tech-radar/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md index 3c4e3a0706..697469bf7a 100644 --- a/plugins/tech-radar/CHANGELOG.md +++ b/plugins/tech-radar/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-tech-radar +## 0.3.6 + +### Patch Changes + +- 9f2b3a26e: Added a dialog box that will show up when a you click on link on the radar and display the description if provided. +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [1407b34c6] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [3a58084b6] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + ## 0.3.5 ### Patch Changes diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 5b21613053..223d5c1762 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-radar", - "version": "0.3.5", + "version": "0.3.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,7 +30,7 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.6.2", + "@backstage/core": "^0.6.3", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,9 +43,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index cae37c076e..49e3fdbc44 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-techdocs-backend +## 0.6.2 + +### Patch Changes + +- f37992797: Got rid of some `attr` and cleaned up a bit in the TechDocs config schema. +- Updated dependencies [bad21a085] +- Updated dependencies [2499f6cde] +- Updated dependencies [a1f5e6545] +- Updated dependencies [1e4ddd71d] + - @backstage/catalog-model@0.7.2 + - @backstage/techdocs-common@0.4.2 + - @backstage/config@0.1.3 + ## 0.6.1 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 994e05fefa..03bea5233d 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "0.6.1", + "version": "0.6.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,9 @@ }, "dependencies": { "@backstage/backend-common": "^0.5.4", - "@backstage/catalog-model": "^0.7.1", - "@backstage/config": "^0.1.2", - "@backstage/techdocs-common": "^0.4.1", + "@backstage/catalog-model": "^0.7.2", + "@backstage/config": "^0.1.3", + "@backstage/techdocs-common": "^0.4.2", "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", "cross-fetch": "^3.0.6", @@ -45,7 +45,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.6.1", + "@backstage/cli": "^0.6.2", "supertest": "^4.0.2" }, "files": [ diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 1772ea62fe..a1e7e456d4 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-techdocs +## 0.5.8 + +### Patch Changes + +- f37992797: Got rid of some `attr` and cleaned up a bit in the TechDocs config schema. +- 2499f6cde: Add support for assuming role in AWS integrations +- Updated dependencies [3a58084b6] +- Updated dependencies [e799e74d4] +- Updated dependencies [dc12852c9] +- Updated dependencies [d0760ecdf] +- Updated dependencies [1407b34c6] +- Updated dependencies [88f1f1b60] +- Updated dependencies [bad21a085] +- Updated dependencies [9615e68fb] +- Updated dependencies [49f9b7346] +- Updated dependencies [5c2e2863f] +- Updated dependencies [3a58084b6] +- Updated dependencies [2499f6cde] +- Updated dependencies [a1f5e6545] +- Updated dependencies [1e4ddd71d] +- Updated dependencies [2c1f2a7c2] + - @backstage/core@0.6.3 + - @backstage/test-utils@0.1.8 + - @backstage/plugin-catalog-react@0.1.0 + - @backstage/catalog-model@0.7.2 + - @backstage/techdocs-common@0.4.2 + - @backstage/config@0.1.3 + ## 0.5.7 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index fdedc7d4dc..360441945d 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "0.5.7", + "version": "0.5.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,13 +31,13 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.2", - "@backstage/catalog-model": "^0.7.1", - "@backstage/core": "^0.6.2", - "@backstage/plugin-catalog-react": "^0.0.4", - "@backstage/test-utils": "^0.1.7", + "@backstage/config": "^0.1.3", + "@backstage/catalog-model": "^0.7.2", + "@backstage/core": "^0.6.3", + "@backstage/plugin-catalog-react": "^0.1.0", + "@backstage/test-utils": "^0.1.8", "@backstage/theme": "^0.2.3", - "@backstage/techdocs-common": "^0.4.1", + "@backstage/techdocs-common": "^0.4.2", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -50,9 +50,9 @@ "sanitize-html": "^2.3.2" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index f6cd6231c1..0e5d9a2859 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.6.2", + "@backstage/core": "^0.6.3", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,9 +41,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index 81ba864a24..cb8452e8f8 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -30,7 +30,7 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.6.2", + "@backstage/core": "^0.6.3", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,9 +41,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.1", - "@backstage/dev-utils": "^0.1.11", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.2", + "@backstage/dev-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.8", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", From 305f7239fc2b54073cba5d36f751c406c51d1d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 23 Feb 2021 11:41:42 +0100 Subject: [PATCH 254/270] it's that time of year again! --- yarn.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/yarn.lock b/yarn.lock index 576c7a0820..815916312c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1816,9 +1816,9 @@ to-fast-properties "^2.0.0" "@backstage/catalog-model@^0.2.0": - version "0.7.1" + version "0.7.2" dependencies: - "@backstage/config" "^0.1.2" + "@backstage/config" "^0.1.3" "@types/json-schema" "^7.0.5" "@types/yup" "^0.29.8" ajv "^7.0.3" @@ -1828,9 +1828,9 @@ yup "^0.29.3" "@backstage/catalog-model@^0.3.0": - version "0.7.1" + version "0.7.2" dependencies: - "@backstage/config" "^0.1.2" + "@backstage/config" "^0.1.3" "@types/json-schema" "^7.0.5" "@types/yup" "^0.29.8" ajv "^7.0.3" @@ -1840,10 +1840,10 @@ yup "^0.29.3" "@backstage/core@^0.3.0": - version "0.6.2" + version "0.6.3" dependencies: - "@backstage/config" "^0.1.2" - "@backstage/core-api" "^0.2.10" + "@backstage/config" "^0.1.3" + "@backstage/core-api" "^0.2.11" "@backstage/theme" "^0.2.3" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" @@ -1880,13 +1880,13 @@ remark-gfm "^1.0.0" zen-observable "^0.8.15" -"@backstage/plugin-catalog@^0.2.1": - version "0.3.2" +"@backstage/plugin-catalog@^0.2.1", "@backstage/plugin-catalog@^0.3.1": + version "0.4.0" dependencies: "@backstage/catalog-client" "^0.3.6" - "@backstage/catalog-model" "^0.7.1" - "@backstage/core" "^0.6.2" - "@backstage/plugin-catalog-react" "^0.0.4" + "@backstage/catalog-model" "^0.7.2" + "@backstage/core" "^0.6.3" + "@backstage/plugin-catalog-react" "^0.1.0" "@backstage/theme" "^0.2.3" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" From 2a17def998fcd275d7f9fb6fdb51e17a41279903 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 23 Feb 2021 11:06:33 +0100 Subject: [PATCH 255/270] create-app: switch peer deps to use * to work around major bump issue --- packages/create-app/package.json | 54 ++++++++++++++++---------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 110bd3634c..05615cc78d 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -44,33 +44,33 @@ "ts-node": "^8.6.2" }, "peerDependencies": { - "@backstage/backend-common": "^0.5.4", - "@backstage/catalog-client": "^0.3.6", - "@backstage/catalog-model": "^0.7.2", - "@backstage/cli": "^0.6.2", - "@backstage/config": "^0.1.3", - "@backstage/core": "^0.6.3", - "@backstage/plugin-api-docs": "^0.4.7", - "@backstage/plugin-app-backend": "^0.3.8", - "@backstage/plugin-auth-backend": "^0.3.2", - "@backstage/plugin-catalog": "^0.4.0", - "@backstage/plugin-catalog-backend": "^0.6.3", - "@backstage/plugin-catalog-import": "^0.4.2", - "@backstage/plugin-circleci": "^0.2.10", - "@backstage/plugin-explore": "^0.2.7", - "@backstage/plugin-github-actions": "^0.3.4", - "@backstage/plugin-lighthouse": "^0.2.12", - "@backstage/plugin-proxy-backend": "^0.2.4", - "@backstage/plugin-rollbar-backend": "^0.1.7", - "@backstage/plugin-scaffolder": "^0.6.0", - "@backstage/plugin-search": "^0.3.2", - "@backstage/plugin-scaffolder-backend": "^0.8.0", - "@backstage/plugin-tech-radar": "^0.3.6", - "@backstage/plugin-techdocs": "^0.5.8", - "@backstage/plugin-techdocs-backend": "^0.6.2", - "@backstage/plugin-user-settings": "^0.2.6", - "@backstage/test-utils": "^0.1.8", - "@backstage/theme": "^0.2.3" + "@backstage/backend-common": "*", + "@backstage/catalog-client": "*", + "@backstage/catalog-model": "*", + "@backstage/cli": "*", + "@backstage/config": "*", + "@backstage/core": "*", + "@backstage/plugin-api-docs": "*", + "@backstage/plugin-app-backend": "*", + "@backstage/plugin-auth-backend": "*", + "@backstage/plugin-catalog": "*", + "@backstage/plugin-catalog-backend": "*", + "@backstage/plugin-catalog-import": "*", + "@backstage/plugin-circleci": "*", + "@backstage/plugin-explore": "*", + "@backstage/plugin-github-actions": "*", + "@backstage/plugin-lighthouse": "*", + "@backstage/plugin-proxy-backend": "*", + "@backstage/plugin-rollbar-backend": "*", + "@backstage/plugin-scaffolder": "*", + "@backstage/plugin-search": "*", + "@backstage/plugin-scaffolder-backend": "*", + "@backstage/plugin-tech-radar": "*", + "@backstage/plugin-techdocs": "*", + "@backstage/plugin-techdocs-backend": "*", + "@backstage/plugin-user-settings": "*", + "@backstage/test-utils": "*", + "@backstage/theme": "*" }, "nodemonConfig": { "watch": "./src", From f301dd573f80d3a96a1a027b484527b7842d6db0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 23 Feb 2021 13:35:25 +0100 Subject: [PATCH 256/270] package.json: allow release flow to change the lockfile --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6c39cf2953..527304b4f3 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "docker-build": "yarn tsc && yarn workspace example-backend build --build-dependencies && yarn workspace example-backend build-image", "create-plugin": "backstage-cli create-plugin --scope backstage --no-private", "remove-plugin": "backstage-cli remove-plugin", - "release": "changeset version && yarn diff --yes && yarn prettier --write '{packages,plugins}/*/{package.json,CHANGELOG.md}' && yarn install --frozen-lockfile", + "release": "changeset version && yarn diff --yes && yarn prettier --write '{packages,plugins}/*/{package.json,CHANGELOG.md}' && yarn install", "prettier:check": "prettier --check .", "lerna": "lerna", "storybook": "yarn workspace storybook start", From 02816ecd723e5ffb0db728e2d007dff0f92a856a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Tue, 23 Feb 2021 13:33:47 +0100 Subject: [PATCH 257/270] fix: Loading should be true when *not* having an entity --- .changeset/twenty-pandas-crash.md | 5 +++++ .../src/components/EntityProvider/EntityProvider.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/twenty-pandas-crash.md diff --git a/.changeset/twenty-pandas-crash.md b/.changeset/twenty-pandas-crash.md new file mode 100644 index 0000000000..95ff389406 --- /dev/null +++ b/.changeset/twenty-pandas-crash.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Fixed EntityProvider setting 'loading' bool erroneously to true diff --git a/plugins/catalog-react/src/components/EntityProvider/EntityProvider.tsx b/plugins/catalog-react/src/components/EntityProvider/EntityProvider.tsx index 45cdd56d04..861fe3d9e7 100644 --- a/plugins/catalog-react/src/components/EntityProvider/EntityProvider.tsx +++ b/plugins/catalog-react/src/components/EntityProvider/EntityProvider.tsx @@ -26,7 +26,7 @@ export const EntityProvider = ({ entity, children }: EntityProviderProps) => ( From 7bd6bde38839adecc59b37efbbe9d74127805c0d Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Tue, 23 Feb 2021 08:46:46 -0500 Subject: [PATCH 258/270] Move line break to a Box --- .../components/BuildWithStepsPage/BuildWithStepsPage.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx b/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx index 23618dec9e..65a213a1e6 100644 --- a/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx +++ b/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx @@ -17,6 +17,7 @@ import React from 'react'; import { useParams } from 'react-router-dom'; import { Breadcrumbs, Content, Link } from '@backstage/core'; import { + Box, Typography, Paper, TableContainer, @@ -33,9 +34,9 @@ import { JenkinsRunStatus } from '../BuildsPage/lib/Status'; import ExternalLinkIcon from '@material-ui/icons/Launch'; const useStyles = makeStyles(theme => ({ - root: { - maxWidth: 720, - }, + // root: { + // maxWidth: 720, + // }, table: { padding: theme.spacing(1), }, @@ -58,7 +59,7 @@ const BuildWithStepsView = () => { Projects Run -
+
From 560b4ad72a1cf3ea3d6eab989a6f2466967e3ec0 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Tue, 23 Feb 2021 09:02:45 -0500 Subject: [PATCH 259/270] Revert maxWidth removal testing --- .../components/BuildWithStepsPage/BuildWithStepsPage.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx b/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx index 65a213a1e6..5d9d81c985 100644 --- a/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx +++ b/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx @@ -34,9 +34,9 @@ import { JenkinsRunStatus } from '../BuildsPage/lib/Status'; import ExternalLinkIcon from '@material-ui/icons/Launch'; const useStyles = makeStyles(theme => ({ - // root: { - // maxWidth: 720, - // }, + root: { + maxWidth: 720, + }, table: { padding: theme.spacing(1), }, From 7ad5ad88421ebc844e88070ed9722cee13b3101c Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Tue, 23 Feb 2021 09:12:50 -0600 Subject: [PATCH 260/270] add link to k8s docs on Missing Annotation page --- plugins/kubernetes/src/Router.tsx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/plugins/kubernetes/src/Router.tsx b/plugins/kubernetes/src/Router.tsx index 5197e4811d..838bfef60a 100644 --- a/plugins/kubernetes/src/Router.tsx +++ b/plugins/kubernetes/src/Router.tsx @@ -21,6 +21,7 @@ import { Route, Routes } from 'react-router-dom'; import { rootCatalogKubernetesRouteRef } from './plugin'; import { KubernetesContent } from './components/KubernetesContent'; import { MissingAnnotationEmptyState } from '@backstage/core'; +import { Button } from '@material-ui/core'; const KUBERNETES_ANNOTATION = 'backstage.io/kubernetes-id'; const KUBERNETES_LABEL_SELECTOR_QUERY_ANNOTATION = @@ -54,5 +55,20 @@ export const Router = (_props: Props) => { ); } - return ; + return ( + <> + +

+ Or use a label selector query, which takes precedence over the previous + annotation. +

+ + + ); }; From e58eb67b5540db2eea85fe4a3cc2e7c47e7bee91 Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Tue, 23 Feb 2021 10:29:53 -0600 Subject: [PATCH 261/270] revert yarn.lock chage --- yarn.lock | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/yarn.lock b/yarn.lock index c13dad3caa..815916312c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1880,29 +1880,7 @@ remark-gfm "^1.0.0" zen-observable "^0.8.15" -"@backstage/plugin-catalog@^0.2.1": - version "0.4.0" - dependencies: - "@backstage/catalog-client" "^0.3.6" - "@backstage/catalog-model" "^0.7.2" - "@backstage/core" "^0.6.3" - "@backstage/plugin-catalog-react" "^0.1.0" - "@backstage/theme" "^0.2.3" - "@material-ui/core" "^4.11.0" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - "@types/react" "^16.9" - classnames "^2.2.6" - git-url-parse "^11.4.4" - react "^16.13.1" - react-dom "^16.13.1" - react-helmet "6.1.0" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" - swr "^0.3.0" - -"@backstage/plugin-catalog@^0.3.1": +"@backstage/plugin-catalog@^0.2.1", "@backstage/plugin-catalog@^0.3.1": version "0.4.0" dependencies: "@backstage/catalog-client" "^0.3.6" From f95d5da8507d2e50f5d89ac0c97a34f4f7268204 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Tue, 23 Feb 2021 15:49:33 -0500 Subject: [PATCH 262/270] Improve splunk image --- microsite/data/plugins/splunk-on-call.yaml | 2 +- microsite/static/img/splunk-black-white-bg.png | Bin 0 -> 4456 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 microsite/static/img/splunk-black-white-bg.png diff --git a/microsite/data/plugins/splunk-on-call.yaml b/microsite/data/plugins/splunk-on-call.yaml index 2d6deb0180..15ceb86a1b 100644 --- a/microsite/data/plugins/splunk-on-call.yaml +++ b/microsite/data/plugins/splunk-on-call.yaml @@ -5,7 +5,7 @@ authorUrl: https://github.com/spotify category: Monitoring description: Splunk On-Call offers a simple way to identify incidents and escalation policies. documentation: https://github.com/backstage/backstage/tree/master/plugins/splunk-on-call -iconUrl: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDMyIDMyIiBjbGFzcz0idm8tbGVhZi1ibGFjayI+PHBhdGggZD0iTTE1Ljk5OTcgMzJDMjQuODM2MSAzMiAzMiAyNC44MzY4IDMyIDE2LjAwMDJDMzIgNy4xNjM1NiAyNC44MzYxIDAgMTUuOTk5NyAwQzcuMTYzNCAwIDAgNy4xNjM1NiAwIDE2LjAwMDJDMCAyNC44MzY4IDcuMTYzNCAzMiAxNS45OTk3IDMyWk0yMi41Njc1IDE0LjY5MDRMMjMuNjI1OSAxNi41MTU4TDE4Ljg4NTEgMTguOTE0NUwxOC44MSAxOS43MTQxTDIyLjQwMzMgMTguNTc2MUwyMC41OTcyIDIwLjg0MzJMMjAuNDQ2MiAyMS41NjI4TDE4LjI0MzkgMjIuNTUyNUwxOC4wMTc2IDIzLjIzMjJMMTkuMDA0NSAyMi44MTU3TDE2LjM0MjIgMjUuOTEwNFYyOS42MDA0SDE1LjY1NzdWMjUuOTEwNEwxMi45OTU5IDIyLjgxNTdMMTMuOTgyOCAyMy4yMzIyTDEzLjc1NjYgMjIuNTUyNUwxMS41NTQ1IDIxLjU2MjhMMTEuNDAzNiAyMC44NDMyTDkuNTk2OTIgMTguNTc2MUwxMy4xOTA2IDE5LjcxNDFMMTMuMTE0OSAxOC45MTQ1TDguMzc0NDkgMTYuNTE1OEw5LjQzMzM5IDE0LjY5MDRMNy4yMDAyIDExLjUxODlMMTMuMzcxOCAxNC41MDA3TDEzLjQ5NyAxMy42MDM3TDExLjY5MzYgMTAuNjYyTDExLjk1MiAxMC4xNzc0TDExLjExOTUgNi43MTg2N0wxNC4zNTUzIDEwLjEyNjdMMTQuNTQ5MyA4LjI0NDU0TDEzLjU4MTggNS40MjI1M0wxNC44NzMgNS44NTYyNEwxNiAyLjQwMDM5TDE3LjEyNzMgNS44NTYyNEwxOC40MTg2IDUuNDIyNTNMMTcuNDUxMiA4LjI0NDU0TDE3LjY0NTQgMTAuMTI2N0wyMC44ODA3IDYuNzE4NjdMMjAuMDQ4MiAxMC4xNzc0TDIwLjMwNjggMTAuNjYyTDE4LjUwMzQgMTMuNjAzN0wxOC42Mjg4IDE0LjUwMDdMMjQuODAwMiAxMS41MTg5TDIyLjU2NzUgMTQuNjkwNFoiIGZpbGw9JyMyQzJDMkMnLz48L3N2Zz4K +iconUrl: img/splunk-black-white-bg.png npmPackageName: '@backstage/plugin-splunk-on-call' tags: - monitoring diff --git a/microsite/static/img/splunk-black-white-bg.png b/microsite/static/img/splunk-black-white-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..76eb7ef0f8be5bf168cbd6fd5de91191ad1fa761 GIT binary patch literal 4456 zcmeHLYg7{076wNf&9q6=Cd0Qi*Dy^jANj&Zpkr!9S)^i(_)3igA<00p%uEw}i;+%` zv1^)+IQdA_%1nG9Ss59qnLNrDB`F~&m;T(J_s6|I@2tJPb=Em&?S0Pq_TFo+eR2GR12hbSKB*^~@xCFx06-H(S9wZH zJ=ZxKh`<5>25H{`oQN|||E__?gdPEMZl9|J0Dk%$6ySR#9XUTvp&1IFKA#dMK|CN} z$WArXa!`%qwd5GBgntX2_*B$pzT*a=nx=zT?rKc3KRJeK?jKT$I3E2sxm$(-Cts+d z)^q^=nwsQmWWd2qKLK`XXh8Nu0RPn11{eW>zgt-Vj{Lp(Hwph`B-DKqmPZdS@Y|ge zSH8Xqb3pgnzFjhzztJ}SAA;&}hLGa3_BJx7#0ODM21v5I*H)6Shi1gpnWW`cVLmBq zRkqRbrWzW|Ed~V+-Aas9oMTQ(U+&rHLnnS6f!)Y#)&~O5FhLE!+8IzK-71pwp+ncV z4|6^$IY*=-Ddt<*+~Z|rfa`>dMTAZtH#)OZp3$|i`^`R0&9*NX*34;}F2zCzn?MbW zTvXYGLCD)A#bMrKpqhcN@1I;^->RS2S$bxz;w)-vraPc0)>ACYy19!ejy3$sS4M$H zqeJAHsc~`kax=%fL&;rY^d?yPm|9m$Q?J!g=CYGF#>?wzxUnCA6JYnqhn?##<8)XD z#xQH58)z5jSzh&eW{M?l zw5#+OvIAc1MNzA+E=I@Sm~@mTinz+?jvq@2-qKmR;$%K~18ZSYXU#)Z}y0p>?0{E!Tl*ubGLAfKgO!Ffyj}88^Wi&a-KKNK7 zs>uhaM$!|y?>jSO#vVh|z6!6(hbUdgoc(jTb`Ie7y-&MK<-gVHl|KKt?zlcImXoOC z$1nrZEWqCidwg@B%H{GgshHymUt9Y)a;I5MBa+nTQ)ZG~G~&wbwf6DgMtG8x=~6Ra zc?LMc%J`X3fQxW8i=e?KdT-SypUqDudl6UCzVtk@v;nty(uU2pZgq0HW^3)+Ae|d) zW^2VAuhro@8AK3jP z=gO{OgwFQ!?W>y!AVdXzoP&FXYuZdDG)65b9?7l2T_Z+uHP6JdLgQizU~1KK<*{@CW|OiY$XWp$~=c~SJDXf zoOzrSc7-Y0Yer;a{8o?Wpq(gTrXgiL_pJvx_U>rRrU|yv=jp8nRb(8l1@)Ts-IAhx4H^~0=c#m#*ZoAf?@ z#yAubShn5b$9yyWfuJ6{A<1ub7i#lNVG0E!d0%*U6(tX!ki&E8;;RI|g*R zHx#CzfP?D^?9#cHKtg#=K)MPw(XAtW&8PBRG@LB_^?6s5Hzq#VyNLQk<_jMD7Rm|f zKqiC+o-qV&rIwz?_rNB-Kio%mui{Ii#{c+11Ad_MokxT?2`OzVtFxpv^Sc{0?^3la z@3@{oR4_bL4G#M*w?l7JbvK}A_TO`Pfj6WcUbaFq(;YoE&n9`iJ{Hi{@1&80BKL>f z5I4&T`{>1j)-RwwOAROgWh=)1(S>(Atb&OqQ!jOeA>)Sy-!P<@nS%nl?$CPq>y(~C zX&D?!8xPlTf)D4ZFMhpC5s#{i%QegoZu|_pgxaFQ4?69}t#Pirs?{glG%VnyGom2I zMOs$Q(bz+8ZJ9dLJm@7Gi`Izc=P>)qsM9@(w%DG+;<fsue4kRRcdPK?c6S|>{lvNeH?`NnZ=omwoy9lAjpg-oBA#;z&MU#@BW*de5^5 zb1&STI}%&~<-^?GsEwb%_C5;Mzr-Fd8uW7#-aOhGnzDyY_rDmdRpI)BKU9CZ0e9G| zvG!~|3=*C;M;4rY%x|Tbw-fb89&`%b_C2b*4jCr49ZW{#%2}uQ+r#e1d$X9ub(7gK zUK&om;YIBJ(Zb?O?7cP}o`l7_NbP1VH^!er0zv`mO@LtZW4_8bNM5Bl^-LWTe<#BT z8+Jchua5@o;~i*+!sUju%Y+G_@}9n>ZBiyQ6iqit-Ha%49EmMcWVZQx=ZXX6XeBl6UZT zv7d|*vb-4>bWQz9XgEg0scYrusD{N#$-7sRv6DnGj;-ujr2jBB{c~S{b7+?-ex-#@ zVKwpjVv|KPSi36brYt=I`o?Z4avsxEgjiZ5#H1ylhvS8jN3rVY`Ly57rE&ISf_Kd( zsu#xVS#NmM>cvAjC=v2}HuHL*qJ3TwnVg6=R-QAWJfI(_mIhHfP_cLt$=(cBL`V>{ z4>LW_`$f4Gz#t8Uoz(mw&iXV|-KTvzFPq~zF3vI&uz(*QzKpOPCJt>h^L%S}73&Zh zdAJ3tHWWOUD3lE*)W_(-MAwtGBW1Q%2vLk%pJztv*i1V2!T_U4VN@{vsCfE#d8U{= zRI#<_?FPkA*^#)n)_4GN@8vcZMGhvskv#RggYU7DT@pVua+w&dEvQ|l-XO!q=d6v^;^-8op z&=w`)WEE$-ylR+k9{X(|tI}5Y*XCA7SBr>a=8^_2Z{6e?J_#jE-DxmT*VxBvY3vPI zU7a|_lONi1#vbT?Dp*VNq~7qw|FuE%erZg;=Jj`v`M>h`|Inhlc_ Date: Tue, 23 Feb 2021 16:57:41 -0500 Subject: [PATCH 263/270] fix(app): incorrect package ordering --- packages/app/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 76a087a4fd..4882662016 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -9,8 +9,8 @@ "@backstage/core": "^0.6.3", "@backstage/plugin-api-docs": "^0.4.7", "@backstage/plugin-catalog": "^0.4.0", - "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/plugin-catalog-import": "^0.4.2", + "@backstage/plugin-catalog-react": "^0.1.0", "@backstage/plugin-circleci": "^0.2.10", "@backstage/plugin-cloudbuild": "^0.2.11", "@backstage/plugin-cost-insights": "^0.8.2", @@ -19,12 +19,12 @@ "@backstage/plugin-github-actions": "^0.3.4", "@backstage/plugin-gitops-profiles": "^0.2.5", "@backstage/plugin-graphiql": "^0.2.7", - "@backstage/plugin-org": "^0.3.8", "@backstage/plugin-jenkins": "^0.3.11", "@backstage/plugin-kafka": "^0.2.4", "@backstage/plugin-kubernetes": "^0.3.11", "@backstage/plugin-lighthouse": "^0.2.12", "@backstage/plugin-newrelic": "^0.2.5", + "@backstage/plugin-org": "^0.3.8", "@backstage/plugin-pagerduty": "0.3.1", "@backstage/plugin-register-component": "^0.2.11", "@backstage/plugin-rollbar": "^0.3.2", From 2a271d89e8701d91c733404a8894078ec5cf5e42 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 23 Feb 2021 00:51:31 +0100 Subject: [PATCH 264/270] core-api: switch component data to use a global store --- .changeset/rich-socks-move.md | 6 ++ .../src/extensions/componentData.test.tsx | 59 +++++++++++++++++++ .../core-api/src/extensions/componentData.tsx | 44 +++++++++++--- packages/core-api/src/lib/globalObject.ts | 29 +++++++++ 4 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 .changeset/rich-socks-move.md create mode 100644 packages/core-api/src/lib/globalObject.ts diff --git a/.changeset/rich-socks-move.md b/.changeset/rich-socks-move.md new file mode 100644 index 0000000000..5aeb3121f1 --- /dev/null +++ b/.changeset/rich-socks-move.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-api': patch +'@backstage/core': patch +--- + +Internal refactor of how component data is access to avoid polluting components and make it possible to bridge across versions. diff --git a/packages/core-api/src/extensions/componentData.test.tsx b/packages/core-api/src/extensions/componentData.test.tsx index 6c4abda40f..cb0918b290 100644 --- a/packages/core-api/src/extensions/componentData.test.tsx +++ b/packages/core-api/src/extensions/componentData.test.tsx @@ -59,4 +59,63 @@ describe('elementData', () => { 'Attempted to attach duplicate data "my-data" to component "MyComponent"', ); }); + + describe('works across versions', () => { + function getDataSymbol() { + const Component = () => null; + attachComponentData(Component, 'my-data', {}); + const [symbol] = Object.getOwnPropertySymbols(Component); + return symbol; + } + + it('should should be able to get data from older versions', () => { + const symbol = getDataSymbol(); + + const data = { foo: 'bar' }; + const Component = () => null; + attachComponentData(Component, 'my-data', data); + + const element = ; + expect((element as any).type[symbol].map.get('my-data')).toBe(data); + }); + + it('should should be able to attach data for older versions', () => { + const symbol = getDataSymbol(); + + const data = { foo: 'bar' }; + const Component = () => null; + (Component as any)[symbol] = { + map: new Map([['my-data', data]]), + }; + + const element = ; + expect(getComponentData(element, 'my-data')).toBe(data); + }); + + it('should be able to get data from newer versions', () => { + const data = { foo: 'bar' }; + const Component = () => null; + attachComponentData(Component, 'my-data', data); + + const element = ; + const container = (global as any)[ + '__@backstage/component-data-store__' + ].store.get(element.type); + expect(container.map.get('my-data')).toBe(data); + }); + + it('should should be able to attach data for newer versions', () => { + const data = { foo: 'bar' }; + const Component = () => null; + (global as any)['__@backstage/component-data-store__'].store.set( + Component, + { + map: new Map([['my-data', data]]), + }, + ); + + const element = ; + expect(getComponentData(element, 'my-data')).toBe(data); + }); + }); }); diff --git a/packages/core-api/src/extensions/componentData.tsx b/packages/core-api/src/extensions/componentData.tsx index 1f88356a48..3d2558276d 100644 --- a/packages/core-api/src/extensions/componentData.tsx +++ b/packages/core-api/src/extensions/componentData.tsx @@ -15,21 +15,40 @@ */ import { ComponentType, ReactNode } from 'react'; +import { globalObject } from '../lib/globalObject'; +// TODO(Rugvip): Access via symbol is deprecated, remove once on 0.3.x const DATA_KEY = Symbol('backstage-component-data'); -type DataContainer = { - map: Map; -}; - type ComponentWithData

= ComponentType

& { [DATA_KEY]?: DataContainer; }; -type ReactNodeWithData = ReactNode & { - type?: { [DATA_KEY]?: DataContainer }; +type DataContainer = { + map: Map; }; +type MaybeComponentNode = ReactNode & { + type?: ComponentType & { [DATA_KEY]?: DataContainer }; +}; + +const GLOBAL_KEY = '__@backstage/component-data-store__'; + +// The store is bridged across versions using the global object +function getStore() { + let storeObj = globalObject[GLOBAL_KEY] as + | { store: WeakMap, DataContainer> } + | undefined; + if (!storeObj) { + const store = new WeakMap, DataContainer>(); + storeObj = { store }; + globalObject[GLOBAL_KEY] = storeObj; + } + return storeObj.store; +} + +const store = getStore(); + export function attachComponentData

( component: ComponentType

, type: string, @@ -37,9 +56,11 @@ export function attachComponentData

( ) { const dataComponent = component as ComponentWithData

; - let container = dataComponent[DATA_KEY]; + let container = store.get(component) || dataComponent[DATA_KEY]; if (!container) { - container = dataComponent[DATA_KEY] = { map: new Map() }; + container = { map: new Map() }; + store.set(component, container); + dataComponent[DATA_KEY] = container; } if (container.map.has(type)) { @@ -60,7 +81,12 @@ export function getComponentData( return undefined; } - const container = (node as ReactNodeWithData).type?.[DATA_KEY]; + const component = (node as MaybeComponentNode).type; + if (!component) { + return undefined; + } + + const container = store.get(component) || component[DATA_KEY]; if (!container) { return undefined; } diff --git a/packages/core-api/src/lib/globalObject.ts b/packages/core-api/src/lib/globalObject.ts new file mode 100644 index 0000000000..85f16212fe --- /dev/null +++ b/packages/core-api/src/lib/globalObject.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2021 Spotify AB + * + * 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. + */ + +// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 +function getGlobal() { + if (typeof window !== 'undefined' && window.Math === Math) { + return window; + } + if (typeof self !== 'undefined' && self.Math === Math) { + return self; + } + // eslint-disable-next-line no-new-func + return Function('return this')(); +} + +export const globalObject = getGlobal(); From 8a1566719692d0d216807386e12bc89746b8cbe5 Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Tue, 23 Feb 2021 15:47:55 -0500 Subject: [PATCH 265/270] feat(core): add support config & button/error components This adds a new app.support top level config that can be used to configure various support links and other information. This can later be extended for plugins to append their own specific support items. --- .changeset/silver-balloons-play.md | 5 + app-config.yaml | 13 ++ packages/core-api/src/app/App.tsx | 4 +- packages/core-api/src/app/types.ts | 4 +- packages/core-api/src/icons/icons.tsx | 27 +++- packages/core-api/src/icons/types.ts | 11 +- packages/core/config.d.ts | 35 +++++ .../SupportButton/SupportButton.tsx | 132 +++++++----------- packages/core/src/hooks/index.ts | 6 + packages/core/src/hooks/useSupportConfig.ts | 56 ++++++++ .../core/src/layout/ErrorPage/ErrorPage.tsx | 14 +- .../EntityLinksCard/EntityLinksCard.tsx | 5 +- .../DomainExplorerContent.test.tsx | 36 ++--- .../ToolExplorerContent.test.tsx | 32 +++-- .../ProfileCatalog/ProfileCatalog.test.tsx | 22 +-- .../RegisterComponentPage.test.tsx | 42 +++--- .../src/components/RadarPage.test.tsx | 29 ++-- .../WelcomePage/WelcomePage.test.tsx | 19 ++- 18 files changed, 301 insertions(+), 191 deletions(-) create mode 100644 .changeset/silver-balloons-play.md create mode 100644 packages/core/src/hooks/useSupportConfig.ts diff --git a/.changeset/silver-balloons-play.md b/.changeset/silver-balloons-play.md new file mode 100644 index 0000000000..24a5c8ac46 --- /dev/null +++ b/.changeset/silver-balloons-play.md @@ -0,0 +1,5 @@ +--- +'@backstage/core': patch +--- + +Added a new useSupportConfig hook that reads a new `app.support` config key. Also updated the SupportButton and ErrorPage components to use the new config. diff --git a/app-config.yaml b/app-config.yaml index 672ba26cd2..f68f5a30a2 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -2,6 +2,19 @@ app: title: Backstage Example App baseUrl: http://localhost:3000 googleAnalyticsTrackingId: # UA-000000-0 + support: + url: https://github.com/backstage/backstage/issues # Used by common ErrorPage + items: # Used by common SupportButton component + - title: Issues + icon: github + links: + - url: https://github.com/backstage/backstage/issues + title: GitHub Issues + - title: Discord Chatroom + icon: chat + links: + - url: https://discord.gg/MUpMjP2 + title: '#backstage' backend: baseUrl: http://localhost:7000 diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index 3cf5abdb44..1cf2d892d0 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -149,7 +149,7 @@ class AppContextImpl implements AppContext { return this.app.getPlugins(); } - getSystemIcon(key: string): IconComponent { + getSystemIcon(key: IconKey): IconComponent | undefined { return this.app.getSystemIcon(key); } @@ -206,7 +206,7 @@ export class PrivateAppImpl implements BackstageApp { return this.plugins; } - getSystemIcon(key: IconKey): IconComponent { + getSystemIcon(key: IconKey): IconComponent | undefined { return this.icons[key]; } diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts index 69eb7f7189..ec7b70689d 100644 --- a/packages/core-api/src/app/types.ts +++ b/packages/core-api/src/app/types.ts @@ -171,7 +171,7 @@ export type BackstageApp = { /** * Get a common or custom icon for this app. */ - getSystemIcon(key: IconKey): IconComponent; + getSystemIcon(key: IconKey): IconComponent | undefined; /** * Provider component that should wrap the Router created with getRouter() @@ -202,7 +202,7 @@ export type AppContext = { /** * Get a common or custom icon for this app. */ - getSystemIcon(key: IconKey): IconComponent; + getSystemIcon(key: IconKey): IconComponent | undefined; /** * Get the components registered for various purposes in the app. diff --git a/packages/core-api/src/icons/icons.tsx b/packages/core-api/src/icons/icons.tsx index 0c2b580ea7..49db90efcc 100644 --- a/packages/core-api/src/icons/icons.tsx +++ b/packages/core-api/src/icons/icons.tsx @@ -15,31 +15,46 @@ */ import { SvgIconProps } from '@material-ui/core'; +import MuiBrokenImageIcon from '@material-ui/icons/BrokenImage'; +import MuiChatIcon from '@material-ui/icons/Chat'; import MuiDashboardIcon from '@material-ui/icons/Dashboard'; +import MuiEmailIcon from '@material-ui/icons/Email'; +import MuiGitHubIcon from '@material-ui/icons/GitHub'; import MuiHelpIcon from '@material-ui/icons/Help'; -import PeopleIcon from '@material-ui/icons/People'; -import PersonIcon from '@material-ui/icons/Person'; +import MuiPeopleIcon from '@material-ui/icons/People'; +import MuiPersonIcon from '@material-ui/icons/Person'; +import MuiWarningIcon from '@material-ui/icons/Warning'; import React from 'react'; import { useApp } from '../app/AppContext'; -import { IconComponent, SystemIconKey, IconComponentMap } from './types'; +import { IconComponent, IconComponentMap, SystemIconKey } from './types'; export const defaultSystemIcons: IconComponentMap = { - user: PersonIcon, - group: PeopleIcon, + brokenImage: MuiBrokenImageIcon, + chat: MuiChatIcon, dashboard: MuiDashboardIcon, + email: MuiEmailIcon, + github: MuiGitHubIcon, + group: MuiPeopleIcon, help: MuiHelpIcon, + user: MuiPersonIcon, + warning: MuiWarningIcon, }; const overridableSystemIcon = (key: SystemIconKey): IconComponent => { const Component = (props: SvgIconProps) => { const app = useApp(); const Icon = app.getSystemIcon(key); - return ; + return Icon ? : ; }; return Component; }; +export const BrokenImageIcon = overridableSystemIcon('brokenImage'); +export const ChatIcon = overridableSystemIcon('chat'); export const DashboardIcon = overridableSystemIcon('dashboard'); +export const EmailIcon = overridableSystemIcon('email'); +export const GitHubIcon = overridableSystemIcon('github'); export const GroupIcon = overridableSystemIcon('group'); export const HelpIcon = overridableSystemIcon('help'); export const UserIcon = overridableSystemIcon('user'); +export const WarningIcon = overridableSystemIcon('warning'); diff --git a/packages/core-api/src/icons/types.ts b/packages/core-api/src/icons/types.ts index be24b223ae..c4634bd00a 100644 --- a/packages/core-api/src/icons/types.ts +++ b/packages/core-api/src/icons/types.ts @@ -17,7 +17,16 @@ import { ComponentType } from 'react'; import { SvgIconProps } from '@material-ui/core'; -export type SystemIconKey = 'user' | 'group' | 'dashboard' | 'help'; +export type SystemIconKey = + | 'brokenImage' + | 'chat' + | 'dashboard' + | 'email' + | 'github' + | 'group' + | 'help' + | 'user' + | 'warning'; export type IconComponent = ComponentType; export type IconKey = SystemIconKey | string; diff --git a/packages/core/config.d.ts b/packages/core/config.d.ts index a6f95ae71c..bc6c462014 100644 --- a/packages/core/config.d.ts +++ b/packages/core/config.d.ts @@ -30,6 +30,41 @@ export interface Config { * @visibility frontend */ title?: string; + + /** + * Information about support of this Backstage instance and how to contact the integrator team. + */ + support?: { + /** + * The primary support url. + * @visibility frontend + */ + url: string; + /** + * A list of categorized support item groupings. + */ + items: { + /** + * The title of the support item grouping. + * @visibility frontend + */ + title: string; + /** + * An optional icon for the support item grouping. + * @visibility frontend + */ + icon?: string; + /** + * A list of support links for the Backstage instance. + */ + links?: { + /** @visibility frontend */ + url: string; + /** @visibility frontend */ + title?: string; + }[]; + }[]; + }; }; /** diff --git a/packages/core/src/components/SupportButton/SupportButton.tsx b/packages/core/src/components/SupportButton/SupportButton.tsx index 2a1b810582..7c532c0f8f 100644 --- a/packages/core/src/components/SupportButton/SupportButton.tsx +++ b/packages/core/src/components/SupportButton/SupportButton.tsx @@ -14,35 +14,26 @@ * limitations under the License. */ -import React, { - Fragment, - useState, - MouseEventHandler, - PropsWithChildren, -} from 'react'; +import { HelpIcon, useApp } from '@backstage/core-api'; import { Button, - Link, List, ListItem, ListItemIcon, - Popover, - Typography, - makeStyles, ListItemText, + makeStyles, + Popover, } from '@material-ui/core'; -import GroupIcon from '@material-ui/icons/Group'; -import HelpIcon from '@material-ui/icons/Help'; +import React, { + Fragment, + MouseEventHandler, + PropsWithChildren, + useState, +} from 'react'; +import { SupportItem, SupportItemLink, useSupportConfig } from '../../hooks'; +import { Link } from '../Link'; -// import { EmailIcon, SlackIcon, SupportIcon } from 'shared/icons'; -// import { Button, Link } from 'shared/components'; -// import { StackOverflow, StackOverflowTag } from 'shared/components/layout'; - -type Props = { - slackChannel?: string | string[]; - email?: string | string[]; - plugin?: any; -}; +type Props = {}; const useStyles = makeStyles(theme => ({ leftIcon: { @@ -50,17 +41,45 @@ const useStyles = makeStyles(theme => ({ }, popoverList: { minWidth: 260, - maxWidth: 320, + maxWidth: 400, }, })); -export const SupportButton = ({ - slackChannel = '#backstage', - email = [], - children, -}: // plugin, -PropsWithChildren) => { - // TODO: get plugin manifest with hook +const SupportIcon = ({ icon }: { icon: string | undefined }) => { + const app = useApp(); + const Icon = icon ? app.getSystemIcon(icon) ?? HelpIcon : HelpIcon; + return ; +}; + +const SupportLink = ({ link }: { link: SupportItemLink }) => ( + + {link.title ?? link.url} + +); + +const SupportListItem = ({ item }: { item: SupportItem }) => { + return ( + + + + + + {item.links && + item.links.map(link => ( + + ))} + + } + /> + + ); +}; + +export const SupportButton = ({ children }: PropsWithChildren) => { + const { items } = useSupportConfig(); const [popoverOpen, setPopoverOpen] = useState(false); const [anchorEl, setAnchorEl] = useState(null); @@ -75,12 +94,6 @@ PropsWithChildren) => { setPopoverOpen(false); }; - // const tags = plugin ? plugin.stackoverflowTags : undefined; - const slackChannels = Array.isArray(slackChannel) - ? slackChannel - : [slackChannel]; - const contactEmails = Array.isArray(email) ? email : [email]; - return (