diff --git a/.changeset/beige-tigers-matter.md b/.changeset/beige-tigers-matter.md
new file mode 100644
index 0000000000..248ce1fbf9
--- /dev/null
+++ b/.changeset/beige-tigers-matter.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder': patch
+---
+
+Fix case GitLab workspace is a nested subgroup
diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx
index c36374f939..4b44a4fa3e 100644
--- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx
+++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx
@@ -43,6 +43,11 @@ describe('RepoUrlPicker', () => {
type: 'bitbucketServer',
title: 'server.bitbucket.org',
},
+ {
+ host: 'gitlab.example.com',
+ type: 'gitlab',
+ title: 'gitlab.example.com',
+ },
],
}),
};
@@ -184,6 +189,59 @@ describe('RepoUrlPicker', () => {
secrets: { testKey: 'abc123' },
});
});
+ it('should call the scmAuthApi with the correct params if workspace is nested', async () => {
+ const SecretsComponent = () => {
+ const { secrets } = useTemplateSecrets();
+ return (
+
{JSON.stringify({ secrets })}
+ );
+ };
+ const { getAllByRole } = await renderInTestApp(
+
+
+
+
+
+ ,
+ );
+
+ const [projectInput, repoInput] = getAllByRole('textbox');
+
+ await act(async () => {
+ fireEvent.change(projectInput, {
+ target: { value: 'backstage/mysubgroup' },
+ });
+ fireEvent.change(repoInput, { target: { value: 'repo123' } });
+
+ // need to wait for the debounce to finish
+ await new Promise(resolve => setTimeout(resolve, 600));
+ });
+
+ expect(mockScmAuthApi.getCredentials).toHaveBeenCalledWith({
+ url: 'https://gitlab.example.com/backstage/mysubgroup/repo123',
+ additionalScope: {
+ repoWrite: true,
+ },
+ });
+ });
it('should call the scmAuthApi with the correct params if only a project is set', async () => {
const SecretsComponent = () => {
const { secrets } = useTemplateSecrets();
diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx
index 9bd904de13..57cebb3d96 100644
--- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx
+++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx
@@ -128,17 +128,19 @@ export const RepoUrlPicker = (props: RepoUrlPickerProps) => {
return;
}
- const [encodedHost, encodedWorkspace, encodedRepoName] = [
- state.host,
- workspace,
- state.repoName,
- ].map(encodeURIComponent);
+ // 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
+ // resulting in 400 request error from GitLab API
+ const [encodedHost, encodedRepoName] = [state.host, state.repoName].map(
+ encodeURIComponent,
+ );
// user has requested that we use the users credentials
// so lets grab them using the scmAuthApi and pass through
// any additional scopes from the ui:options
const { token } = await scmAuthApi.getCredentials({
- url: `https://${encodedHost}/${encodedWorkspace}/${encodedRepoName}`,
+ url: `https://${encodedHost}/${workspace}/${encodedRepoName}`,
additionalScope: {
repoWrite: true,
customScopes: requestUserCredentials.additionalScopes,