chore: working added createdBy column

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2022-05-02 11:16:13 +02:00
parent 183948fa85
commit d48f69fa60
6 changed files with 176 additions and 5 deletions
@@ -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');
});
};
@@ -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) {
@@ -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,
@@ -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;
};
@@ -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,
};
}
@@ -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<any, any>[];
}
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 (
<>
<MuiStepper activeStep={activeStep} alternativeLabel variant="elevation">
{steps.map((step, index) => (
<MuiStep key={index}>
<MuiStepLabel>{step.title}</MuiStepLabel>
</MuiStep>
))}
</MuiStepper>
<div className={styles.formWrapper}>
<Form
schema={steps[activeStep].schema}
uiSchema={steps[activeStep].uiSchema}
onSubmit={handleNext}
fields={extensions}
showErrorList={false}
>
<div className={styles.footer}>
<Button
onClick={handleBack}
className={styles.backButton}
disabled={activeStep < 1}
>
Back
</Button>
<Button variant="contained" color="primary" type="submit">
{activeStep === steps.length - 1 ? 'Review' : 'Next'}
</Button>
</div>
</Form>
</div>
</>
);
};