From d48f69fa60aba165eeaf7249489ec0f44e60890c Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 2 May 2022 11:16:13 +0200 Subject: [PATCH] chore: working added createdBy column Signed-off-by: blam --- .../migrations/20220129100000_created_by.js | 38 +++++++ .../src/scaffolder/tasks/DatabaseTaskStore.ts | 4 + .../src/scaffolder/tasks/StorageTaskBroker.ts | 5 + .../src/scaffolder/tasks/types.ts | 3 + .../scaffolder-backend/src/service/router.ts | 29 ++++- .../Stepper/Stepper copy.tsx | 102 ++++++++++++++++++ 6 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 plugins/scaffolder-backend/migrations/20220129100000_created_by.js create mode 100644 plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper copy.tsx diff --git a/plugins/scaffolder-backend/migrations/20220129100000_created_by.js b/plugins/scaffolder-backend/migrations/20220129100000_created_by.js new file mode 100644 index 0000000000..3391a883c2 --- /dev/null +++ b/plugins/scaffolder-backend/migrations/20220129100000_created_by.js @@ -0,0 +1,38 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + await knex.schema.alterTable('tasks', table => { + table + .text('created_by') + .nullable() + .comment('An entityRef of the user who created the task'); + }); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + await knex.schema.alterTable('tasks', table => { + table.dropColumn('created_by'); + }); +}; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index 9fef68529f..d0a0fec0a6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -43,6 +43,7 @@ export type RawDbTaskRow = { status: TaskStatus; last_heartbeat_at?: string; created_at: string; + created_by: string | null; secrets?: string | null; }; @@ -100,6 +101,7 @@ export class DatabaseTaskStore implements TaskStore { status: result.status, lastHeartbeatAt: result.last_heartbeat_at, createdAt: result.created_at, + createdBy: result.created_by ?? undefined, secrets, }; } catch (error) { @@ -115,6 +117,7 @@ export class DatabaseTaskStore implements TaskStore { id: taskId, spec: JSON.stringify(options.spec), secrets: options.secrets ? JSON.stringify(options.secrets) : undefined, + created_by: options.createdBy ?? null, status: 'open', }); return { taskId }; @@ -155,6 +158,7 @@ export class DatabaseTaskStore implements TaskStore { status: 'processing', lastHeartbeatAt: task.last_heartbeat_at, createdAt: task.created_at, + createdBy: task.created_by ?? undefined, secrets, }; } catch (error) { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index a92d6abdb8..0aa0b6b830 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -127,6 +127,10 @@ export interface CurrentClaimedTask { * The secrets that are stored with the task. */ secrets?: TaskSecrets; + /** + * The creator of the task. + */ + createdBy?: string; } function defer() { @@ -156,6 +160,7 @@ export class StorageTaskBroker implements TaskBroker { taskId: pendingTask.id, spec: pendingTask.spec, secrets: pendingTask.secrets, + createdBy: pendingTask.createdBy, }, this.storage, this.logger, diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 2af98b111b..bbd0da2565 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -47,6 +47,7 @@ export type SerializedTask = { status: TaskStatus; createdAt: string; lastHeartbeatAt?: string; + createdBy?: string; secrets?: TaskSecrets; }; @@ -97,6 +98,7 @@ export type TaskBrokerDispatchResult = { export type TaskBrokerDispatchOptions = { spec: TaskSpec; secrets?: TaskSecrets; + createdBy?: string; }; /** @@ -157,6 +159,7 @@ export type TaskStoreListEventsOptions = { */ export type TaskStoreCreateTaskOptions = { spec: TaskSpec; + createdBy?: string; secrets?: TaskSecrets; }; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index e7413b3a87..94c2694f9a 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -130,10 +130,11 @@ export async function createRouter( '/v2/templates/:namespace/:kind/:name/parameter-schema', async (req, res) => { const { namespace, kind, name } = req.params; + const { token } = parseBearerToken(req.headers.authorization); const template = await findTemplate({ catalogApi: catalogClient, entityRef: { kind, namespace, name }, - token: getBearerToken(req.headers.authorization), + token, }); if (isSupportedTemplate(template)) { const parameters = [template.spec.parameters ?? []].flat(); @@ -168,12 +169,16 @@ export async function createRouter( const { kind, namespace, name } = parseEntityRef(templateRef, { defaultKind: 'template', }); + const { token, entityRef: userEntityRef } = parseBearerToken( + req.headers.authorization, + ); + const values = req.body.values; - const token = getBearerToken(req.headers.authorization); + const template = await findTemplate({ catalogApi: catalogClient, entityRef: { kind, namespace, name }, - token: getBearerToken(req.headers.authorization), + token, }); if (!isSupportedTemplate(template)) { @@ -215,6 +220,7 @@ export async function createRouter( const result = await taskBroker.dispatch({ spec: taskSpec, + createdBy: userEntityRef, secrets: { ...req.body.secrets, backstageToken: token, @@ -315,6 +321,19 @@ export async function createRouter( return app; } -function getBearerToken(header?: string): string | undefined { - return header?.match(/Bearer\s+(\S+)/i)?.[1]; +function parseBearerToken(header?: string): { + token?: string; + entityRef?: string; +} { + const token = header?.match(/Bearer\s+(\S+)/i)?.[1]; + + if (!token) return {}; + + const [_header, rawPayload, _signature] = token.split('.'); + const payload: { sub: string } = JSON.parse(atob(rawPayload)); + + return { + entityRef: payload.sub, + token, + }; } diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper copy.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper copy.tsx new file mode 100644 index 0000000000..f864e9ee09 --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper copy.tsx @@ -0,0 +1,102 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + Stepper as MuiStepper, + Step as MuiStep, + StepLabel as MuiStepLabel, + Button, + makeStyles, +} from '@material-ui/core'; +import { withTheme } from '@rjsf/core'; +import { Theme as MuiTheme } from '@rjsf/material-ui'; +import React, { useMemo, useState } from 'react'; +import { FieldExtensionOptions } from '../../../extensions'; +import { TemplateParameterSchema } from '../../../types'; +import { useTemplateSchema } from './useTemplateSchema'; + +const useStyles = makeStyles(theme => ({ + backButton: { + marginRight: theme.spacing(1), + }, + footer: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'right', + }, + formWrapper: { + padding: theme.spacing(2), + }, +})); + +export interface StepperProps { + manifest: TemplateParameterSchema; + extensions: FieldExtensionOptions[]; +} + +const Form = withTheme(MuiTheme); + +export const Stepper = (props: StepperProps) => { + const { steps } = useTemplateSchema(props.manifest); + const [activeStep, setActiveStep] = useState(0); + const styles = useStyles(); + + const extensions = useMemo(() => { + return Object.fromEntries( + props.extensions.map(({ name, component }) => [name, component]), + ); + }, [props.extensions]); + + const handleBack = () => { + setActiveStep(prevActiveStep => prevActiveStep - 1); + }; + const handleNext = () => { + setActiveStep(prevActiveStep => prevActiveStep + 1); + }; + + return ( + <> + + {steps.map((step, index) => ( + + {step.title} + + ))} + +
+
+
+ + +
+
+
+ + ); +};