fix: don't show login prompt if token is defined in state

Signed-off-by: Ali Dalal <ali.dalal@bonial.com>
This commit is contained in:
Ali Dalal
2024-03-27 16:58:47 +01:00
parent 8cc47df993
commit 419e948d42
6 changed files with 103 additions and 7 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder': patch
---
Don't show login prompt if token is set in the state
+3 -1
View File
@@ -455,7 +455,9 @@ export interface ScaffolderUseTemplateSecrets {
// @public
export const SecretsContextProvider: (
props: PropsWithChildren<{}>,
props: PropsWithChildren<{
initialSecrets?: Record<string, string>;
}>,
) => React_2.JSX.Element;
// @public
@@ -35,4 +35,20 @@ describe('SecretsContext', () => {
expect(result.current.hook?.secrets.foo).toEqual('bar');
});
it('should create SecretsContextProvider with initial secrets', async () => {
const { result } = renderHook(
() => ({
hook: useTemplateSecrets(),
}),
{
wrapper: ({ children }: React.PropsWithChildren<{}>) => (
<SecretsContextProvider initialSecrets={{ foo: 'bar' }}>
{children}
</SecretsContextProvider>
),
},
);
expect(result.current.hook?.secrets.foo).toEqual('bar');
});
});
@@ -43,8 +43,13 @@ const SecretsContext = createVersionedContext<{
* The Context Provider that holds the state for the secrets.
* @public
*/
export const SecretsContextProvider = (props: PropsWithChildren<{}>) => {
const [secrets, setSecrets] = useState<Record<string, string>>({});
export const SecretsContextProvider = (
props: PropsWithChildren<{ initialSecrets?: Record<string, string> }>,
) => {
const { initialSecrets = {} } = props;
const [secrets, setSecrets] = useState<Record<string, string>>({
...initialSecrets,
});
return (
<SecretsContext.Provider
@@ -58,9 +58,13 @@ describe('RepoUrlPicker', () => {
byHost: () => ({ type: 'github' }),
};
const mockScmAuthApi: Partial<ScmAuthApi> = {
getCredentials: jest.fn().mockResolvedValue({ token: 'abc123' }),
};
let mockScmAuthApi: Partial<ScmAuthApi>;
beforeEach(() => {
mockScmAuthApi = {
getCredentials: jest.fn().mockResolvedValue({ token: 'abc123' }),
};
});
describe('happy path rendering', () => {
it('should render the repo url picker with minimal props', async () => {
@@ -350,5 +354,64 @@ describe('RepoUrlPicker', () => {
secrets: { testKey: 'abc123' },
});
});
it('should not call the scmAuthApi if secret is available in the state', async () => {
const SecretsComponent = () => {
const { secrets } = useTemplateSecrets();
return (
<div data-testid="current-secrets">{JSON.stringify({ secrets })}</div>
);
};
const { getAllByRole, getByTestId } = await renderInTestApp(
<TestApiProvider
apis={[
[scmIntegrationsApiRef, mockIntegrationsApi],
[scmAuthApiRef, mockScmAuthApi],
[scaffolderApiRef, mockScaffolderApi],
]}
>
<SecretsContextProvider initialSecrets={{ testKey: 'abc123' }}>
<Form
validator={validator}
schema={{ type: 'string' }}
uiSchema={{
'ui:field': 'RepoUrlPicker',
'ui:options': {
requestUserCredentials: {
secretsKey: 'testKey',
additionalScopes: { github: ['workflow'] },
},
},
}}
fields={{
RepoUrlPicker: RepoUrlPicker as ScaffolderRJSFField<string>,
}}
/>
<SecretsComponent />
</SecretsContextProvider>
</TestApiProvider>,
);
const [ownerInput, repoInput] = getAllByRole('textbox');
await act(async () => {
fireEvent.change(ownerInput, { target: { value: 'backstage' } });
fireEvent.change(repoInput, { target: { value: 'repo123' } });
// need to wait for the debounce to finish
await new Promise(resolve => setTimeout(resolve, 600));
});
// as we already have a secret in the state, getCredentials should not be called again.
expect(mockScmAuthApi.getCredentials).toHaveBeenCalledTimes(0);
const currentSecrets = JSON.parse(
getByTestId('current-secrets').textContent!,
);
expect(currentSecrets).toEqual({
secrets: { testKey: 'abc123' },
});
});
});
});
@@ -51,7 +51,7 @@ export const RepoUrlPicker = (props: RepoUrlPickerProps) => {
);
const integrationApi = useApi(scmIntegrationsApiRef);
const scmAuthApi = useApi(scmAuthApiRef);
const { setSecrets } = useTemplateSecrets();
const { secrets, setSecrets } = useTemplateSecrets();
const allowedHosts = useMemo(
() => uiSchema?.['ui:options']?.allowedHosts ?? [],
[uiSchema],
@@ -132,6 +132,11 @@ export const RepoUrlPicker = (props: RepoUrlPickerProps) => {
return;
}
// don't show login prompt if secret value is already in state
if (secrets[requestUserCredentials.secretsKey]) {
return;
}
// previously, we were encodeURI for state.host, workspace and state.repoName separately.
// That created an issue where GitLab workspace can be nested like groupA/subgroupB
// when we encodeURi separately and then join, the URL will be malformed and