From b8600fe1650507c7b0e443ba58d8183e4a7413a8 Mon Sep 17 00:00:00 2001 From: Adam Letizia Date: Fri, 19 Jul 2024 09:48:20 -0500 Subject: [PATCH] fix(scaffolder): refetch user credentials when selected host is changed in RepoUrlPicker Signed-off-by: Adam Letizia --- .changeset/tough-lies-repair.md | 5 ++ .../RepoUrlPicker/RepoUrlPicker.test.tsx | 56 +++++++++++++++---- .../fields/RepoUrlPicker/RepoUrlPicker.tsx | 11 +++- 3 files changed, 58 insertions(+), 14 deletions(-) create mode 100644 .changeset/tough-lies-repair.md diff --git a/.changeset/tough-lies-repair.md b/.changeset/tough-lies-repair.md new file mode 100644 index 0000000000..25b1884bbc --- /dev/null +++ b/.changeset/tough-lies-repair.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +RepoUrlPicker: refetch user credentials when a new host is selected diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx index 002652cfab..fce9d9517c 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx @@ -33,6 +33,7 @@ import { ScaffolderRJSFField, } from '@backstage/plugin-scaffolder-react'; import { act, fireEvent } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; describe('RepoUrlPicker', () => { const mockScaffolderApi: Partial = { @@ -323,14 +324,29 @@ describe('RepoUrlPicker', () => { }); }); - it('should not call the scmAuthApi if secret is available in the state', async () => { + it('should call the scmAuthApi with the new host when the host is changed', async () => { const SecretsComponent = () => { const { secrets } = useTemplateSecrets(); return (
{JSON.stringify({ secrets })}
); }; - const { getByTestId } = await renderInTestApp( + const allowedHosts = ['github.com', 'gitlab.example.com']; + + (mockScmAuthApi.getCredentials as jest.Mock).mockImplementation( + ({ url }) => { + let token = ''; + if (url === `https://${allowedHosts[0]}`) { + token = 'abc123'; + } else if (url === `https://${allowedHosts[1]}`) { + token = 'def456'; + } + return Promise.resolve({ token }); + }, + ); + const secondHost = allowedHosts[1]; + + const { getAllByRole, getByTestId } = await renderInTestApp( { [scaffolderApiRef, mockScaffolderApi], ]} > - +
{ ); await act(async () => { + // need to wait for the debounce to finish to fetch credentials for the first selected host + await new Promise(resolve => setTimeout(resolve, 600)); + }); + const firstHostSecrets = JSON.parse( + getByTestId('current-secrets').textContent!, + ); + expect(firstHostSecrets).toEqual({ + secrets: { testKey: 'abc123' }, + }); + + await act(async () => { + // Select the second host + const hostInput = getAllByRole('combobox')[0]; + await userEvent.selectOptions(hostInput, secondHost); + // 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( + const secondHostSecrets = JSON.parse( getByTestId('current-secrets').textContent!, ); - - expect(currentSecrets).toEqual({ - secrets: { testKey: 'abc123' }, + expect(secondHostSecrets).toEqual({ + secrets: { testKey: 'def456' }, + }); + expect(mockScmAuthApi.getCredentials).toHaveBeenCalledWith({ + url: `https://${secondHost}`, + additionalScope: { + repoWrite: true, + }, }); }); }); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx index e0d714ffda..e653be9178 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx @@ -49,6 +49,9 @@ export const RepoUrlPicker = (props: RepoUrlPickerProps) => { const [state, setState] = useState( parseRepoPickerUrl(formData), ); + const [credentialsHost, setCredentialsHost] = useState( + undefined, + ); const integrationApi = useApi(scmIntegrationsApiRef); const scmAuthApi = useApi(scmAuthApiRef); const { secrets, setSecrets } = useTemplateSecrets(); @@ -128,8 +131,11 @@ export const RepoUrlPicker = (props: RepoUrlPickerProps) => { return; } - // don't show login prompt if secret value is already in state - if (secrets[requestUserCredentials.secretsKey]) { + // don't show login prompt if secret value is already in state for selected host + if ( + secrets[requestUserCredentials.secretsKey] && + credentialsHost === state.host + ) { return; } @@ -147,6 +153,7 @@ export const RepoUrlPicker = (props: RepoUrlPickerProps) => { // set the secret using the key provided in the ui:options for use // in the templating the manifest with ${{ secrets[secretsKey] }} setSecrets({ [requestUserCredentials.secretsKey]: token }); + setCredentialsHost(state.host); }, 500, [state, uiSchema],