chore: use the existing secrets provider otherwise existing fields wont work

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2022-04-01 11:32:49 +02:00
parent 2e8cf988e3
commit 43342dcf0a
6 changed files with 28 additions and 142 deletions
@@ -16,7 +16,6 @@
import React, { PropsWithChildren } from 'react';
import { Routes, Route, useOutlet } from 'react-router';
import { TemplateListPage } from '../TemplateListPage';
import { SecretsContextProvider } from '../TemplateWizardPage/SecretsContext';
import { TemplateWizardPage } from '../TemplateWizardPage';
import {
FieldExtensionOptions,
@@ -29,6 +28,7 @@ import { useElementFilter } from '@backstage/core-plugin-api';
import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
import { TemplateGroupFilter } from '../TemplateListPage/TemplateGroups';
import { nextSelectedTemplateRouteRef } from '../../routes';
import { SecretsContextProvider } from '../../components/secrets/SecretsContext';
/**
* The Props for the Scaffolder Router
@@ -1,43 +0,0 @@
/*
* 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 React, { useContext } from 'react';
import {
useTemplateSecrets,
SecretsContextProvider,
SecretsContext,
} from './SecretsContext';
import { renderHook, act } from '@testing-library/react-hooks';
describe('SecretsContext', () => {
it('should allow the setting of secrets in the context', async () => {
const { result } = renderHook(
() => ({
hook: useTemplateSecrets(),
context: useContext(SecretsContext),
}),
{
wrapper: ({ children }) => (
<SecretsContextProvider>{children}</SecretsContextProvider>
),
},
);
expect(result.current.context?.secrets.foo).toEqual(undefined);
act(() => result.current.hook.setSecret({ foo: 'bar' }));
expect(result.current.context?.secrets.foo).toEqual('bar');
});
});
@@ -1,73 +0,0 @@
/*
* 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 React, {
useState,
useCallback,
useContext,
createContext,
PropsWithChildren,
} from 'react';
type SecretsContextContents = {
secrets: Record<string, string>;
setSecrets: React.Dispatch<React.SetStateAction<Record<string, string>>>;
};
/**
* The actual context object.
*/
export const SecretsContext = createContext<SecretsContextContents | undefined>(
undefined,
);
/**
* The Context Provider that holds the state for the secrets.
*
* @alpha
*/
export const SecretsContextProvider = ({ children }: PropsWithChildren<{}>) => {
const [secrets, setSecrets] = useState<Record<string, string>>({});
return (
<SecretsContext.Provider value={{ secrets, setSecrets }}>
{children}
</SecretsContext.Provider>
);
};
/**
* Hook to access the secrets context.
* @alpha
*/
export const useTemplateSecrets = () => {
const value = useContext(SecretsContext);
if (!value) {
throw new Error(
'useTemplateSecrets must be used within a SecretsContextProvider',
);
}
const { setSecrets } = value;
const setSecret = useCallback(
(input: Record<string, string>) => {
setSecrets(currentSecrets => ({ ...currentSecrets, ...input }));
},
[setSecrets],
);
return { setSecret };
};
@@ -1,20 +0,0 @@
/*
* 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.
*/
export {
useTemplateSecrets,
SecretsContext,
SecretsContextProvider,
} from './SecretsContext';
@@ -22,7 +22,8 @@ import {
} from '@material-ui/core';
import { withTheme } from '@rjsf/core';
import { Theme as MuiTheme } from '@rjsf/material-ui';
import React, { useState } from 'react';
import React, { useMemo, useState } from 'react';
import { FieldExtensionOptions } from '../../../extensions';
import { TemplateParameterSchema } from '../../../types';
import { useTemplateSchema } from './useTemplateSchema';
@@ -42,6 +43,7 @@ const useStyles = makeStyles(theme => ({
export interface StepperProps {
manifest: TemplateParameterSchema;
extensions: FieldExtensionOptions<any, any>[];
}
const Form = withTheme(MuiTheme);
@@ -50,6 +52,13 @@ 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);
};
@@ -69,7 +78,9 @@ export const Stepper = (props: StepperProps) => {
<div className={styles.formWrapper}>
<Form
schema={steps[activeStep].schema}
uiSchema={steps[activeStep].uiSchema}
onSubmit={handleNext}
fields={extensions}
showErrorList={false}
>
<div className={styles.footer}>
@@ -23,14 +23,15 @@ import {
MarkdownContent,
} from '@backstage/core-components';
import { FieldExtensionOptions } from '../../extensions';
import { useParams } from 'react-router';
import { Navigate, useParams } from 'react-router';
import { stringifyEntityRef } from '@backstage/catalog-model';
import { useApi } from '@backstage/core-plugin-api';
import { errorApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api';
import { scaffolderApiRef } from '../../api';
import useAsync from 'react-use/lib/useAsync';
import { makeStyles } from '@material-ui/core';
import { Stepper } from './Stepper';
import { BackstageTheme } from '@backstage/theme';
import { nextRouteRef } from '../../routes';
export interface TemplateWizardPageProps {
customFieldExtensions: FieldExtensionOptions<any, any>[];
@@ -58,9 +59,11 @@ const useTemplateParameterSchema = (templateRef: string) => {
return { manifest: value, loading, error };
};
export const TemplateWizardPage = (_props: TemplateWizardPageProps) => {
export const TemplateWizardPage = (props: TemplateWizardPageProps) => {
const styles = useStyles();
const rootRef = useRouteRef(nextRouteRef);
const { templateName, namespace } = useParams();
const errorApi = useApi(errorApiRef);
const { loading, manifest, error } = useTemplateParameterSchema(
stringifyEntityRef({
kind: 'Template',
@@ -69,6 +72,11 @@ export const TemplateWizardPage = (_props: TemplateWizardPageProps) => {
}),
);
if (error) {
errorApi.post(new Error(`Failed to load template, ${error}`));
return <Navigate to={rootRef()} />;
}
return (
<Page themeId="website">
<Header
@@ -90,7 +98,10 @@ export const TemplateWizardPage = (_props: TemplateWizardPageProps) => {
noPadding
titleTypographyProps={{ component: 'h2' }}
>
<Stepper manifest={manifest} fields={{}} widgets={{}} />
<Stepper
manifest={manifest}
extensions={props.customFieldExtensions}
/>
</InfoCard>
)}
</Content>