From a6603e4f34e1b6c2338ebfa3d8034e6744efcb21 Mon Sep 17 00:00:00 2001 From: Yghore Date: Mon, 8 Jul 2024 21:32:28 +0200 Subject: [PATCH 1/8] feat: add automatic commited action for merge-request gitlab Signed-off-by: Yghore --- .changeset/wild-eggs-exist.md | 10 ++++ .../src/actions/gitlabMergeRequest.ts | 53 ++++++++++++++----- 2 files changed, 50 insertions(+), 13 deletions(-) create mode 100644 .changeset/wild-eggs-exist.md diff --git a/.changeset/wild-eggs-exist.md b/.changeset/wild-eggs-exist.md new file mode 100644 index 0000000000..68c49e343b --- /dev/null +++ b/.changeset/wild-eggs-exist.md @@ -0,0 +1,10 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Add custom action for merge request: **auto** + +The **Auto** action selects the committed action between _create_ and _update_. + +The **Auto** action fetches files using the **/projects/repository/tree endpoint**. +After fetching, it checks if the file exists locally and in the repository. If it does, it chooses **update**; otherwise, it chooses **create**. diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts index a549585426..6032b4ac28 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts @@ -17,6 +17,7 @@ import { createTemplateAction, parseRepoUrl, + SerializedFile, serializeDirectoryContents, } from '@backstage/plugin-scaffolder-node'; import { Types } from '@gitbeaker/core'; @@ -27,6 +28,20 @@ import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { createGitlabApi } from './helpers'; import { examples } from './gitlabMergeRequest.examples'; +function getFileAction( + file: SerializedFile, + remoteFiles: Types.RepositoryTreeSchema[], + defaultCommitAction: 'create' | 'delete' | 'update' | 'auto' | undefined, +): 'create' | 'delete' | 'update' { + if (defaultCommitAction === 'auto') { + return remoteFiles && + remoteFiles.some(remoteFile => remoteFile.path === file.path) + ? 'update' + : 'create'; + } + return defaultCommitAction ?? 'create'; +} + /** * Create a new action that creates a gitlab merge request. * @@ -46,7 +61,7 @@ export const createPublishGitlabMergeRequestAction = (options: { sourcePath?: string; targetPath?: string; token?: string; - commitAction?: 'create' | 'delete' | 'update'; + commitAction?: 'create' | 'delete' | 'update' | 'auto'; /** @deprecated projectID passed as query parameters in the repoUrl */ projectid?: string; removeSourceBranch?: boolean; @@ -109,9 +124,9 @@ export const createPublishGitlabMergeRequestAction = (options: { commitAction: { title: 'Commit action', type: 'string', - enum: ['create', 'update', 'delete'], + enum: ['create', 'update', 'delete', 'auto'], description: - 'The action to be used for git commit. Defaults to create.', + 'The action to be used for git commit. Defaults to create. "auto" is custom action provide by backstage, (automatic assign create or update action) /!\\ Use more api calls /!\\ *', }, removeSourceBranch: { title: 'Delete source branch', @@ -199,16 +214,7 @@ export const createPublishGitlabMergeRequestAction = (options: { gitignore: true, }); - const actions: Types.CommitAction[] = fileContents.map(file => ({ - action: ctx.input.commitAction ?? 'create', - filePath: targetPath - ? path.posix.join(targetPath, file.path) - : file.path, - encoding: 'base64', - content: file.content.toString('base64'), - execute_filemode: file.executable, - })); - + let remoteFiles: Types.RepositoryTreeSchema[]; let targetBranch = targetBranchName; if (!targetBranch) { const projects = await api.Projects.show(repoID); @@ -216,6 +222,27 @@ export const createPublishGitlabMergeRequestAction = (options: { const { default_branch: defaultBranch } = projects; targetBranch = defaultBranch!; } + try { + remoteFiles = await api.Repositories.tree(repoID, { + ref: targetBranch, + recursive: true, + path: targetPath ?? undefined, + }); + } catch (e) { + ctx.logger.warn( + `Could not retrieve the list of files for ${repoID} (branch: ${targetBranch}) : ${e}`, + ); + } + + const actions: Types.CommitAction[] = fileContents.map(file => ({ + action: getFileAction(file, remoteFiles, ctx.input.commitAction), + filePath: targetPath + ? path.posix.join(targetPath, file.path) + : file.path, + encoding: 'base64', + content: file.content.toString('base64'), + execute_filemode: file.executable, + })); try { await api.Branches.create(repoID, branchName, String(targetBranch)); From 224958e95239ddf67176fbf4695635dfad70d80d Mon Sep 17 00:00:00 2001 From: Yghore Date: Mon, 8 Jul 2024 21:55:46 +0200 Subject: [PATCH 2/8] doc: add api report Signed-off-by: Yghore --- .../scaffolder-backend-module-gitlab/api-report.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/api-report.md b/plugins/scaffolder-backend-module-gitlab/api-report.md index b1f240029e..4b5dc491ca 100644 --- a/plugins/scaffolder-backend-module-gitlab/api-report.md +++ b/plugins/scaffolder-backend-module-gitlab/api-report.md @@ -33,11 +33,11 @@ export const createGitlabIssueAction: (options: { projectId: number; labels?: string | undefined; description?: string | undefined; - weight?: number | undefined; token?: string | undefined; + weight?: number | undefined; assignees?: number[] | undefined; - createdAt?: string | undefined; confidential?: boolean | undefined; + createdAt?: string | undefined; milestoneId?: number | undefined; epicId?: number | undefined; dueDate?: string | undefined; @@ -61,8 +61,8 @@ export const createGitlabProjectAccessTokenAction: (options: { projectId: string | number; name?: string | undefined; token?: string | undefined; - scopes?: string[] | undefined; expiresAt?: string | undefined; + scopes?: string[] | undefined; accessLevel?: number | undefined; }, { @@ -118,7 +118,7 @@ export const createGitlabRepoPushAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'delete' | 'create' | undefined; + commitAction?: 'update' | 'create' | 'delete' | undefined; }, JsonObject >; @@ -193,7 +193,7 @@ export const createPublishGitlabMergeRequestAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'delete' | 'create' | undefined; + commitAction?: 'auto' | 'update' | 'create' | 'delete' | undefined; projectid?: string | undefined; removeSourceBranch?: boolean | undefined; assignee?: string | undefined; @@ -206,8 +206,8 @@ export const createTriggerGitlabPipelineAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { - branch: string; repoUrl: string; + branch: string; projectId: number; tokenDescription: string; token?: string | undefined; @@ -229,8 +229,8 @@ export const editGitlabIssueAction: (options: { title?: string | undefined; labels?: string | undefined; description?: string | undefined; - weight?: number | undefined; token?: string | undefined; + weight?: number | undefined; assignees?: number[] | undefined; addLabels?: string | undefined; confidential?: boolean | undefined; From ed0bea5d93d4b2e1f52d04469eb6b288fc13abde Mon Sep 17 00:00:00 2001 From: Yghore Date: Sun, 14 Jul 2024 22:56:19 +0200 Subject: [PATCH 3/8] fix(default action) create -> auto Signed-off-by: Yghore --- .../scaffolder-backend-module-gitlab/api-report.md | 14 +++++++------- .../src/actions/gitlabMergeRequest.ts | 6 +++--- plugins/scaffolder-backend/api-report.md | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/api-report.md b/plugins/scaffolder-backend-module-gitlab/api-report.md index 4b5dc491ca..ab89669ede 100644 --- a/plugins/scaffolder-backend-module-gitlab/api-report.md +++ b/plugins/scaffolder-backend-module-gitlab/api-report.md @@ -33,11 +33,11 @@ export const createGitlabIssueAction: (options: { projectId: number; labels?: string | undefined; description?: string | undefined; - token?: string | undefined; weight?: number | undefined; + token?: string | undefined; assignees?: number[] | undefined; - confidential?: boolean | undefined; createdAt?: string | undefined; + confidential?: boolean | undefined; milestoneId?: number | undefined; epicId?: number | undefined; dueDate?: string | undefined; @@ -61,8 +61,8 @@ export const createGitlabProjectAccessTokenAction: (options: { projectId: string | number; name?: string | undefined; token?: string | undefined; - expiresAt?: string | undefined; scopes?: string[] | undefined; + expiresAt?: string | undefined; accessLevel?: number | undefined; }, { @@ -118,7 +118,7 @@ export const createGitlabRepoPushAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'create' | 'delete' | undefined; + commitAction?: 'update' | 'delete' | 'create' | undefined; }, JsonObject >; @@ -193,7 +193,7 @@ export const createPublishGitlabMergeRequestAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'auto' | 'update' | 'create' | 'delete' | undefined; + commitAction?: 'auto' | 'update' | 'delete' | 'create' | undefined; projectid?: string | undefined; removeSourceBranch?: boolean | undefined; assignee?: string | undefined; @@ -206,8 +206,8 @@ export const createTriggerGitlabPipelineAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { - repoUrl: string; branch: string; + repoUrl: string; projectId: number; tokenDescription: string; token?: string | undefined; @@ -229,8 +229,8 @@ export const editGitlabIssueAction: (options: { title?: string | undefined; labels?: string | undefined; description?: string | undefined; - token?: string | undefined; weight?: number | undefined; + token?: string | undefined; assignees?: number[] | undefined; addLabels?: string | undefined; confidential?: boolean | undefined; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts index 6032b4ac28..d256a2166b 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts @@ -33,13 +33,13 @@ function getFileAction( remoteFiles: Types.RepositoryTreeSchema[], defaultCommitAction: 'create' | 'delete' | 'update' | 'auto' | undefined, ): 'create' | 'delete' | 'update' { - if (defaultCommitAction === 'auto') { + if (!defaultCommitAction || defaultCommitAction === 'auto') { return remoteFiles && remoteFiles.some(remoteFile => remoteFile.path === file.path) ? 'update' : 'create'; } - return defaultCommitAction ?? 'create'; + return defaultCommitAction; } /** @@ -126,7 +126,7 @@ export const createPublishGitlabMergeRequestAction = (options: { type: 'string', enum: ['create', 'update', 'delete', 'auto'], description: - 'The action to be used for git commit. Defaults to create. "auto" is custom action provide by backstage, (automatic assign create or update action) /!\\ Use more api calls /!\\ *', + 'The action to be used for git commit. Defaults to auto. "auto" is custom action provide by backstage, (automatic assign create or update action) /!\\ Use more api calls /!\\ *', }, removeSourceBranch: { title: 'Delete source branch', diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index a3beee2b53..907a30b541 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -312,7 +312,7 @@ export const createPublishGitlabMergeRequestAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'delete' | 'create' | undefined; + commitAction?: 'auto' | 'update' | 'delete' | 'create' | undefined; projectid?: string | undefined; removeSourceBranch?: boolean | undefined; assignee?: string | undefined; From 3afa2966b2e2b2d90196e604fffa939450e145e8 Mon Sep 17 00:00:00 2001 From: Yghore Date: Tue, 9 Jul 2024 13:12:25 +0000 Subject: [PATCH 4/8] fix: remote file was always fetched even without auto commit action Signed-off-by: Yghore --- .../src/actions/gitlabMergeRequest.ts | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts index d256a2166b..0c6a6fad48 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts @@ -214,7 +214,6 @@ export const createPublishGitlabMergeRequestAction = (options: { gitignore: true, }); - let remoteFiles: Types.RepositoryTreeSchema[]; let targetBranch = targetBranchName; if (!targetBranch) { const projects = await api.Projects.show(repoID); @@ -222,16 +221,20 @@ export const createPublishGitlabMergeRequestAction = (options: { const { default_branch: defaultBranch } = projects; targetBranch = defaultBranch!; } - try { - remoteFiles = await api.Repositories.tree(repoID, { - ref: targetBranch, - recursive: true, - path: targetPath ?? undefined, - }); - } catch (e) { - ctx.logger.warn( - `Could not retrieve the list of files for ${repoID} (branch: ${targetBranch}) : ${e}`, - ); + + let remoteFiles: Types.RepositoryTreeSchema[] = []; + if (ctx.input.commitAction && ctx.input.commitAction === 'auto') { + try { + remoteFiles = await api.Repositories.tree(repoID, { + ref: targetBranch, + recursive: true, + path: targetPath ?? undefined, + }); + } catch (e) { + ctx.logger.warn( + `Could not retrieve the list of files for ${repoID} (branch: ${targetBranch}) : ${e}`, + ); + } } const actions: Types.CommitAction[] = fileContents.map(file => ({ From 9281a5e9bd60da001cc413dd58a7043a94232e72 Mon Sep 17 00:00:00 2001 From: Yghore Date: Mon, 15 Jul 2024 12:29:41 +0000 Subject: [PATCH 5/8] fix(filepath if targetPath is set), fix(default commitAction not retrieve repo files) Signed-off-by: Yghore --- .../src/actions/gitlabMergeRequest.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts index 0c6a6fad48..733365c6df 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts @@ -29,13 +29,14 @@ import { createGitlabApi } from './helpers'; import { examples } from './gitlabMergeRequest.examples'; function getFileAction( - file: SerializedFile, + fileInfo: { file: SerializedFile; targetPath: string | undefined }, remoteFiles: Types.RepositoryTreeSchema[], defaultCommitAction: 'create' | 'delete' | 'update' | 'auto' | undefined, ): 'create' | 'delete' | 'update' { if (!defaultCommitAction || defaultCommitAction === 'auto') { + const filePath = path.join(fileInfo.targetPath ?? '', fileInfo.file.path); return remoteFiles && - remoteFiles.some(remoteFile => remoteFile.path === file.path) + remoteFiles.some(remoteFile => remoteFile.path === filePath) ? 'update' : 'create'; } @@ -223,7 +224,7 @@ export const createPublishGitlabMergeRequestAction = (options: { } let remoteFiles: Types.RepositoryTreeSchema[] = []; - if (ctx.input.commitAction && ctx.input.commitAction === 'auto') { + if (!ctx.input.commitAction || ctx.input.commitAction === 'auto') { try { remoteFiles = await api.Repositories.tree(repoID, { ref: targetBranch, @@ -238,7 +239,11 @@ export const createPublishGitlabMergeRequestAction = (options: { } const actions: Types.CommitAction[] = fileContents.map(file => ({ - action: getFileAction(file, remoteFiles, ctx.input.commitAction), + action: getFileAction( + { file, targetPath }, + remoteFiles, + ctx.input.commitAction, + ), filePath: targetPath ? path.posix.join(targetPath, file.path) : file.path, From 422f883e4e8a7b38452ba6f58e0af910d8894ae3 Mon Sep 17 00:00:00 2001 From: Yghore Date: Mon, 15 Jul 2024 12:33:00 +0000 Subject: [PATCH 6/8] add: test for auto commited action Signed-off-by: Yghore --- .../src/actions/gitlabMergeRequest.test.ts | 115 +++++++++++++++++- 1 file changed, 113 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts index befc2c01c9..3eda50a924 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts @@ -61,6 +61,28 @@ const mockGitlabClient = { ]; }), }, + Repositories: { + tree: jest.fn( + async ( + repoID: string | number, + options: { ref: string; recursive: boolean; path: string | undefined }, + ) => { + if (repoID !== 'owner/repo') throw new Error('repo does not exist'); + if (options.recursive === false) throw new Error('malformed options'); + else { + return [ + { + id: 'a1e8f8d745cc87e3a9248358d9352bb7f9a0aeba', + name: 'auto.txt', + type: 'blob', + path: 'source/auto.txt', + mode: '040000', + }, + ]; + } + }, + ), + }, }; jest.mock('@gitbeaker/node', () => ({ @@ -407,7 +429,7 @@ describe('createGitLabMergeRequest', () => { }); describe('createGitLabMergeRequestWithoutCommitAction', () => { - it('default commitAction is create', async () => { + it('default commitAction is auto', async () => { const input = { repoUrl: 'gitlab.com?repo=repo&owner=owner', title: 'Create my new MR', @@ -417,7 +439,7 @@ describe('createGitLabMergeRequest', () => { }; mockDir.setContent({ [workspacePath]: { - source: { 'foo.txt': 'Hello there!' }, + source: { 'foo.txt': 'Hello there!', 'auto.txt': 'File exist' }, irrelevant: { 'bar.txt': 'Nothing to see here' }, }, }); @@ -429,6 +451,13 @@ describe('createGitLabMergeRequest', () => { 'new-mr', 'Create my new MR', [ + { + action: 'update', + filePath: 'source/auto.txt', + content: 'RmlsZSBleGlzdA==', + encoding: 'base64', + execute_filemode: false, + }, { action: 'create', filePath: 'source/foo.txt', @@ -512,6 +541,88 @@ describe('createGitLabMergeRequest', () => { ); }); + it('commitAction is auto when auto is passed in options', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + branchName: 'new-mr', + description: 'MR description', + commitAction: 'auto', + }; + mockDir.setContent({ + [workspacePath]: { + source: { 'foo.txt': 'Hello there!', 'auto.txt': 'File exist' }, + }, + }); + + const ctx = createMockActionContext({ input, workspacePath }); + await instance.handler(ctx); + + expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'Create my new MR', + [ + { + action: 'update', + filePath: 'source/auto.txt', + content: 'RmlsZSBleGlzdA==', + encoding: 'base64', + execute_filemode: false, + }, + { + action: 'create', + filePath: 'source/foo.txt', + content: 'SGVsbG8gdGhlcmUh', + encoding: 'base64', + execute_filemode: false, + }, + ], + ); + }); + + it('commitAction is auto when auto is passed in options with targetPath', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + branchName: 'new-mr', + description: 'MR description', + commitAction: 'auto', + targetPath: 'source', + }; + mockDir.setContent({ + [workspacePath]: { + source: { 'foo.txt': 'Hello there!', 'auto.txt': 'File exist' }, + irrevelant: {}, + }, + }); + + const ctx = createMockActionContext({ input, workspacePath }); + await instance.handler(ctx); + + expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'Create my new MR', + [ + { + action: 'update', + filePath: 'source/auto.txt', + content: 'RmlsZSBleGlzdA==', + encoding: 'base64', + execute_filemode: false, + }, + { + action: 'create', + filePath: 'source/foo.txt', + content: 'SGVsbG8gdGhlcmUh', + encoding: 'base64', + execute_filemode: false, + }, + ], + ); + }); + it('commitAction is delete when delete is passed in options', async () => { const input = { repoUrl: 'gitlab.com?repo=repo&owner=owner', From 9999ce0937e33086ca2c8a601400f2967b4ebfb1 Mon Sep 17 00:00:00 2001 From: Yghore Date: Mon, 15 Jul 2024 12:47:17 +0000 Subject: [PATCH 7/8] fix: add api-report... Signed-off-by: Yghore --- plugins/scaffolder-backend/api-report.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 907a30b541..ca008c4280 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -312,7 +312,10 @@ export const createPublishGitlabMergeRequestAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'auto' | 'update' | 'delete' | 'create' | undefined; + commitAction?: 'auto' | 'update' | 'delete' | 'create' | undefined + /** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-github instead + */; projectid?: string | undefined; removeSourceBranch?: boolean | undefined; assignee?: string | undefined; From 6bec6090a8bbfb11691118bf87c416afdd77eacc Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 23 Jul 2024 10:21:26 +0200 Subject: [PATCH 8/8] chore: fix api-reports Signed-off-by: blam --- plugins/scaffolder-backend/api-report.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index ca008c4280..907a30b541 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -312,10 +312,7 @@ export const createPublishGitlabMergeRequestAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'auto' | 'update' | 'delete' | 'create' | undefined - /** - * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-github instead - */; + commitAction?: 'auto' | 'update' | 'delete' | 'create' | undefined; projectid?: string | undefined; removeSourceBranch?: boolean | undefined; assignee?: string | undefined;