feat(gitlab): Allow setting optional description on group creation

Signed-off-by: Jellyfrog <Jellyfrog@users.noreply.github.com>
This commit is contained in:
Jellyfrog
2026-01-21 17:06:00 +01:00
parent 508b51c3e2
commit 6b5e7d97f0
6 changed files with 83 additions and 17 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend-module-gitlab': patch
---
Allow setting optional description on group creation
@@ -22,6 +22,7 @@ export const createGitlabGroupEnsureExistsAction: (options: {
}
)[];
token?: string | undefined;
description?: string | undefined;
},
{
groupId?: number | undefined;
@@ -75,7 +75,7 @@ describe('gitlab:group:ensureExists', () => {
expect(mockGitlabClient.Groups.create).toHaveBeenCalledWith(
'group1',
'group1',
{},
{ description: 'This is a top-level group' },
);
expect(mockContext.output).toHaveBeenCalledWith('groupId', 3);
@@ -18,7 +18,7 @@ import yaml from 'yaml';
export const examples: TemplateExample[] = [
{
description: 'Creating a group at the top level',
description: 'Creating a group at the top level, with a description',
example: yaml.stringify({
steps: [
{
@@ -28,6 +28,7 @@ export const examples: TemplateExample[] = [
input: {
repoUrl: 'gitlab.com',
path: ['group1'],
description: 'This is a top-level group',
},
},
],
@@ -86,16 +86,73 @@ describe('gitlab:group:ensureExists', () => {
input: {
repoUrl: 'gitlab.com?repo=repo&owner=owner',
path: ['foo', 'bar'],
description: 'my cool group',
},
});
expect(mockGitlabClient.Groups.create).toHaveBeenCalledWith('bar', 'bar', {
description: 'my cool group',
parentId: 2,
});
expect(mockContext.output).toHaveBeenCalledWith('groupId', 3);
});
it('should only apply description to the last created group', async () => {
mockGitlabClient.Groups.search.mockResolvedValue([]);
mockGitlabClient.Groups.create
.mockResolvedValueOnce({
id: 1,
full_path: 'foo',
})
.mockResolvedValueOnce({
id: 2,
full_path: 'foo/bar',
})
.mockResolvedValueOnce({
id: 3,
full_path: 'foo/bar/baz',
});
await action.handler({
...mockContext,
input: {
repoUrl: 'gitlab.com?repo=repo&owner=owner',
path: ['foo', 'bar', 'baz'],
description: 'only for innermost group',
},
});
expect(mockGitlabClient.Groups.create).toHaveBeenCalledTimes(3);
// First group: no description
expect(mockGitlabClient.Groups.create).toHaveBeenNthCalledWith(
1,
'foo',
'foo',
{},
);
// Second group: no description
expect(mockGitlabClient.Groups.create).toHaveBeenNthCalledWith(
2,
'bar',
'bar',
{ parentId: 1 },
);
// Third (last) group: has description
expect(mockGitlabClient.Groups.create).toHaveBeenNthCalledWith(
3,
'baz',
'baz',
{ parentId: 2, description: 'only for innermost group' },
);
expect(mockContext.output).toHaveBeenCalledWith('groupId', 3);
});
it('should create a new group from pathstring if it does not exists', async () => {
mockGitlabClient.Groups.search.mockResolvedValue([
{
@@ -16,7 +16,7 @@
import { ScmIntegrationRegistry } from '@backstage/integration';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import { GroupSchema } from '@gitbeaker/rest';
import { GroupSchema, CreateGroupOptions } from '@gitbeaker/rest';
import { getClient, parseRepoUrl } from '../util';
import { examples } from './gitlabGroupEnsureExists.examples';
@@ -47,6 +47,12 @@ export const createGitlabGroupEnsureExistsAction = (options: {
description: 'The token to use for authorization to GitLab',
})
.optional(),
description: z =>
z
.string({
description: 'The groups description',
})
.optional(),
path: z =>
z
.array(
@@ -77,8 +83,7 @@ export const createGitlabGroupEnsureExistsAction = (options: {
ctx.output('groupId', 42);
return;
}
const { token, repoUrl, path } = ctx.input;
const { token, repoUrl, path, description } = ctx.input;
const { host } = parseRepoUrl(repoUrl, integrations);
@@ -86,7 +91,10 @@ export const createGitlabGroupEnsureExistsAction = (options: {
let currentPath: string | null = null;
let parentId: number | null = null;
for (const { name, slug } of pathIterator(path)) {
const pathParts = [...pathIterator(path)];
const lastIndex = pathParts.length - 1;
for (let i = 0; i < pathParts.length; i++) {
const { name, slug } = pathParts[i];
const fullPath: string = currentPath ? `${currentPath}/${slug}` : slug;
const result = (await api.Groups.search(
fullPath,
@@ -101,17 +109,11 @@ export const createGitlabGroupEnsureExistsAction = (options: {
key: `ensure.${name}.${slug}.${parentId}`,
// eslint-disable-next-line no-loop-func
fn: async () => {
return (
await api.Groups.create(
name,
slug,
parentId
? {
parentId: parentId,
}
: {},
)
)?.id;
const groupOptions: CreateGroupOptions = {
...(parentId ? { parentId } : {}),
...(description && i === lastIndex ? { description } : {}),
};
return (await api.Groups.create(name, slug, groupOptions))?.id;
},
});
} else {