From cee3b6182eb68a1355e009f23bff81041273def1 Mon Sep 17 00:00:00 2001 From: mathijs-bb Date: Thu, 2 Jul 2026 15:13:56 +0200 Subject: [PATCH 1/6] Test change readme --- Apps.GitLab/Apps.GitLab.csproj | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Apps.GitLab/Apps.GitLab.csproj b/Apps.GitLab/Apps.GitLab.csproj index 274984d..89e76dd 100644 --- a/Apps.GitLab/Apps.GitLab.csproj +++ b/Apps.GitLab/Apps.GitLab.csproj @@ -6,7 +6,7 @@ enable GitLab GitLab is a developer platform that allows developers to create, store, and manage their code. This app focuses on connecting repository events and file actions into the Blackbird ecosystem. - 1.0.14 + 1.0.15 Apps.GitLab @@ -25,6 +25,12 @@ - + + + + + README.md + Always + From 358fa088dadf8a560f4b2ab9438da06412b80076 Mon Sep 17 00:00:00 2001 From: Fesenko-A <111450737+Fesenko-A@users.noreply.github.com> Date: Thu, 2 Jul 2026 18:45:35 +0300 Subject: [PATCH 2/6] update GetFile to inject Blacklake metadata, rename action name & update docs --- Apps.GitLab/Actions/RepositoryActions.cs | 45 +++++++-------------- Apps.GitLab/Apps.GitLab.csproj | 3 +- Apps.GitLab/Utils/File/FileContentResult.cs | 3 ++ Apps.GitLab/Utils/File/FileHelper.cs | 43 ++++++++++++++++++++ Apps.GitLab/Utils/File/FileToProcess.cs | 3 ++ README.md | 2 +- 6 files changed, 67 insertions(+), 32 deletions(-) create mode 100644 Apps.GitLab/Utils/File/FileContentResult.cs create mode 100644 Apps.GitLab/Utils/File/FileHelper.cs create mode 100644 Apps.GitLab/Utils/File/FileToProcess.cs diff --git a/Apps.GitLab/Actions/RepositoryActions.cs b/Apps.GitLab/Actions/RepositoryActions.cs index 74510be..12c94ae 100644 --- a/Apps.GitLab/Actions/RepositoryActions.cs +++ b/Apps.GitLab/Actions/RepositoryActions.cs @@ -11,7 +11,6 @@ using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Actions; using Blackbird.Applications.Sdk.Common.Exceptions; -using Blackbird.Applications.Sdk.Common.Files; using Blackbird.Applications.Sdk.Common.Invocation; using Blackbird.Applications.Sdk.Utils.Extensions.Files; using Blackbird.Applications.Sdk.Utils.Extensions.Http; @@ -21,20 +20,14 @@ using GitLabApiClient.Models.Trees.Responses; using RestSharp; using System.Net.Mime; +using Apps.GitLab.Utils.File; namespace Apps.Gitlab.Actions; [ActionList("Repository")] -public class RepositoryActions : GitLabActions +public class RepositoryActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) + : GitLabActions(invocationContext) { - private readonly IFileManagementClient _fileManagementClient; - - public RepositoryActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) - : base(invocationContext) - { - _fileManagementClient = fileManagementClient; - } - [Action("Create new repository", Description = "Create repository with selected settings")] public async Task CreateRepository([ActionParameter] CreateRepositoryInput input) { @@ -50,7 +43,7 @@ public async Task CreateRepository([ActionParameter] CreateR return RepositoryResponse.FromProject(project); } - [Action("Get repository file", Description = "Get file from a repository by file path")] + [Action("Download file", Description = "Download a file from a repository by file path")] public async Task GetFile( [ActionParameter] GetRepositoryRequest repositoryRequest, [ActionParameter] GetOptionalBranchRequest branchRequest, @@ -60,29 +53,19 @@ public async Task GetFile( var repository = await GetProject(projectId); var branch = branchRequest.Name ?? repository.DefaultBranch; - var request = RestClient.CreateRequest( - $"/projects/{projectId}/repository/files/{Uri.EscapeDataString(getFileRequest.FilePath)}", - Method.Get); - request.AddQueryParameter("ref", branch); + string endpoint = $"/projects/{projectId}/repository/files/{Uri.EscapeDataString(getFileRequest.FilePath)}"; + var request = RestClient.CreateRequest(endpoint, Method.Get).AddQueryParameter("ref", branch); - var fileData = await RestClient.ExecuteWithErrorHandling(request); - if (fileData == null) - throw new PluginMisconfigurationException($"File does not exist ({getFileRequest.FilePath})"); - - var filename = Path.GetFileName(getFileRequest.FilePath); - if (!MimeTypes.TryGetMimeType(filename, out var mimeType)) - mimeType = MediaTypeNames.Application.Octet; - - FileReference file; - using (var stream = new MemoryStream(Convert.FromBase64String(fileData.Content))) - { - file = await _fileManagementClient.UploadAsync(stream, mimeType, filename); - } + var repoFileResponse = await RestClient.ExecuteWithErrorHandling(request) ?? + throw new PluginMisconfigurationException($"File does not exist ({getFileRequest.FilePath})"); + var fileToProcess = new FileToProcess(repoFileResponse.Content, getFileRequest.FilePath, repository.WebUrl, branch); + var fileData = FileHelper.ProcessDownloadedFile(fileToProcess); + var fileReference = await fileManagementClient.UploadAsync(fileData.FileStream, fileData.MimeType, fileData.FileName); return new GetFileResponse { FilePath = getFileRequest.FilePath, - File = file, + File = fileReference, FileExtension = Path.GetExtension(getFileRequest.FilePath) }; } @@ -132,7 +115,7 @@ public async Task GetAllFilesInFolder( if (!MimeTypes.TryGetMimeType(filename, out var mimeType)) mimeType = MediaTypeNames.Application.Octet; - var uploadedFile = await _fileManagementClient.UploadAsync(file.FileStream, mimeType, filename); + var uploadedFile = await fileManagementClient.UploadAsync(file.FileStream, mimeType, filename); resultFiles.Add(new GitLabFile { File = uploadedFile, @@ -255,4 +238,6 @@ private Task GetProject(int projectId) var request = RestClient.CreateRequest($"/projects/{projectId}", Method.Get); return RestClient.ExecuteWithErrorHandling(request); } + + } diff --git a/Apps.GitLab/Apps.GitLab.csproj b/Apps.GitLab/Apps.GitLab.csproj index 89e76dd..24cdecf 100644 --- a/Apps.GitLab/Apps.GitLab.csproj +++ b/Apps.GitLab/Apps.GitLab.csproj @@ -6,7 +6,7 @@ enable GitLab GitLab is a developer platform that allows developers to create, store, and manage their code. This app focuses on connecting repository events and file actions into the Blackbird ecosystem. - 1.0.15 + 1.0.16 Apps.GitLab @@ -14,6 +14,7 @@ + diff --git a/Apps.GitLab/Utils/File/FileContentResult.cs b/Apps.GitLab/Utils/File/FileContentResult.cs new file mode 100644 index 0000000..29f1522 --- /dev/null +++ b/Apps.GitLab/Utils/File/FileContentResult.cs @@ -0,0 +1,3 @@ +namespace Apps.GitLab.Utils.File; + +public record FileContentResult(Stream FileStream, string MimeType, string FileName); \ No newline at end of file diff --git a/Apps.GitLab/Utils/File/FileHelper.cs b/Apps.GitLab/Utils/File/FileHelper.cs new file mode 100644 index 0000000..0bf1080 --- /dev/null +++ b/Apps.GitLab/Utils/File/FileHelper.cs @@ -0,0 +1,43 @@ +using System.Net.Mime; +using Blackbird.Applications.Sdk.Common.Exceptions; +using Blackbird.Filters.Transformations; + +namespace Apps.GitLab.Utils.File; + +public static class FileHelper +{ + public static FileContentResult ProcessDownloadedFile(FileToProcess fileToProcess) + { + var filename = Path.GetFileName(fileToProcess.Path); + if (!MimeTypes.TryGetMimeType(filename, out var mimeType)) + mimeType = MediaTypeNames.Application.Octet; + + var stream = new MemoryStream(Convert.FromBase64String(fileToProcess.Content)); + var transformationResult = Transformation.Load(stream, filename, mimeType); + + if (!transformationResult.Success) + { + stream.Position = 0; + return new(stream, mimeType, filename); + } + + string encodedPath = string.Join("/", fileToProcess.Path.Split('/').Select(Uri.EscapeDataString)); + string blobUrl = $"{fileToProcess.RepoWebUrl}/-/blob/{fileToProcess.BranchName}/{encodedPath}"; + string editUrl = $"{fileToProcess.RepoWebUrl}/-/edit/{fileToProcess.BranchName}/{encodedPath}"; + + var transformation = transformationResult.Value!; + + transformation.SourceLanguage = filename.Split('.')[0]; + transformation.SourceSystemReference.ContentId = blobUrl; + transformation.SourceSystemReference.AdminUrl = editUrl; + transformation.SourceSystemReference.ContentName = filename; + transformation.SourceSystemReference.SystemName = "GitLab"; + transformation.SourceSystemReference.SystemRef = "https://gitlab.com/"; + + var sourceLoadResult = transformation.Source(); + if (!sourceLoadResult.Success) + throw new PluginMisconfigurationException(sourceLoadResult.Error); + + return new(sourceLoadResult.Value.ToStream(), mimeType, filename); + } +} \ No newline at end of file diff --git a/Apps.GitLab/Utils/File/FileToProcess.cs b/Apps.GitLab/Utils/File/FileToProcess.cs new file mode 100644 index 0000000..dd5fba5 --- /dev/null +++ b/Apps.GitLab/Utils/File/FileToProcess.cs @@ -0,0 +1,3 @@ +namespace Apps.GitLab.Utils.File; + +public record FileToProcess(string Content, string Path, string RepoWebUrl, string BranchName); \ No newline at end of file diff --git a/README.md b/README.md index 55255f9..864d57f 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ If your GitLab instance is hosted on a custom domain, use the **OAuth Self-manag - **Enable printing merge request link**: Option to print merge request links. - **CI config path**: CI config file path. - **Initialize with README**: Option to create an initial README file. -- **Get repository file** Get file from a repository by file path. +- **Download file** Download a file from a repository by file path. Advanced settings: - **Branch name**: Branch to use instead of the default branch. - **Get all files in folder** Get files from a repository folder. From 10df843757c469771540f90521ec668783de4f4f Mon Sep 17 00:00:00 2001 From: Fesenko-A <111450737+Fesenko-A@users.noreply.github.com> Date: Fri, 3 Jul 2026 13:34:36 +0300 Subject: [PATCH 3/6] refactor PushFile & UpdateFile actions and add Blacklake compatibility --- Apps.GitLab/Actions/CommitActions.cs | 133 ++++++++++-------- Apps.GitLab/Actions/RepositoryActions.cs | 22 +-- Apps.GitLab/BlackbirdGitLabClient.cs | 33 ++++- .../Extensions/TransformationExtensions.cs | 46 ++++++ .../Commit/Responses/UploadFileResponse.cs | 6 + .../Responses/RepositoryFileResponse.cs | 13 +- Apps.GitLab/Utils/File/DownloadedFile.cs | 3 + Apps.GitLab/Utils/File/FileContentResult.cs | 3 - Apps.GitLab/Utils/File/FileHelper.cs | 54 +++++-- Apps.GitLab/Utils/File/FileToProcess.cs | 3 - .../Utils/File/ProcessedDownloadedFile.cs | 3 + .../Utils/File/ProcessedUploadedFile.cs | 5 + Apps.GitLab/Utils/File/UploadedFile.cs | 3 + Tests.GitLab/RepositoryActionTests.cs | 18 +++ 14 files changed, 244 insertions(+), 101 deletions(-) create mode 100644 Apps.GitLab/Extensions/TransformationExtensions.cs create mode 100644 Apps.GitLab/Models/Commit/Responses/UploadFileResponse.cs create mode 100644 Apps.GitLab/Utils/File/DownloadedFile.cs delete mode 100644 Apps.GitLab/Utils/File/FileContentResult.cs delete mode 100644 Apps.GitLab/Utils/File/FileToProcess.cs create mode 100644 Apps.GitLab/Utils/File/ProcessedDownloadedFile.cs create mode 100644 Apps.GitLab/Utils/File/ProcessedUploadedFile.cs create mode 100644 Apps.GitLab/Utils/File/UploadedFile.cs diff --git a/Apps.GitLab/Actions/CommitActions.cs b/Apps.GitLab/Actions/CommitActions.cs index 837a8d4..8974c6b 100644 --- a/Apps.GitLab/Actions/CommitActions.cs +++ b/Apps.GitLab/Actions/CommitActions.cs @@ -1,3 +1,4 @@ +using System.Net; using Apps.Gitlab.Actions.Base; using Apps.Gitlab.Models.Branch.Requests; using Apps.Gitlab.Models.Commit.Requests; @@ -5,31 +6,27 @@ using Apps.Gitlab.Webhooks; using Apps.GitLab.Constants; using Apps.GitLab.Dtos; +using Apps.Gitlab.Extensions; using Apps.GitLab.Models.Commit.Requests; using Apps.GitLab.Models.Commit.Responses; -using Apps.GitLab.Models.Respository.Requests; +using Apps.GitLab.Utils.File; using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Actions; using Blackbird.Applications.Sdk.Common.Exceptions; using Blackbird.Applications.Sdk.Common.Invocation; -using Blackbird.Applications.Sdk.Utils.Extensions.Files; using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces; +using Blackbird.Filters.Constants; using GitLabApiClient.Models.Commits.Responses; +using GitLabApiClient.Models.Projects.Responses; using RestSharp; namespace Apps.Gitlab.Actions; [ActionList("Commit")] -public class CommitActions : GitLabActions +public class CommitActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) + : GitLabActions(invocationContext) { private const int CommitsPageSize = 100; - private readonly IFileManagementClient _fileManagementClient; - - public CommitActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) - : base(invocationContext) - { - _fileManagementClient = fileManagementClient; - } [Action("Search commits", Description = "Search commits in a repository")] public async Task ListRepositoryCommits( @@ -202,67 +199,87 @@ public async Task ListAddedOrModifiedInHours }; } - [Action("Create or update file", Description = "Create file or update existing file in a repository")] - public async Task PushFile( + [Action("Upload file", Description = "Create file or update existing file in a repository")] + public async Task PushFile( [ActionParameter] GetRepositoryRequest repositoryRequest, [ActionParameter] GetOptionalBranchRequest branchRequest, [ActionParameter] PushFileRequest input) { var projectId = ParseProjectId(repositoryRequest.RepositoryId); - - var repContent = - await new RepositoryActions(InvocationContext, _fileManagementClient).ListRepositoryContent( - repositoryRequest, branchRequest, new FolderContentWithTypeRequest { IncludeSubfolders = true }); - - if (repContent.Content.Where(x => x.Type == GitLabItemTypes.Blob).Any(p => p.Path == input.DestinationFilePath)) - { - return await UpdateFile( - repositoryRequest, - branchRequest, - new PushFileRequest - { - DestinationFilePath = input.DestinationFilePath, - File = input.File, - CommitMessage = input.CommitMessage - }); - } - - if (repContent.Content.Where(x => x.Type == GitLabItemTypes.Tree).Select(x => x.Path) - .Contains(input.DestinationFilePath.Trim('/'))) - { - throw new PluginMisconfigurationException("Destination file path is invalid!"); - } - - var file = await _fileManagementClient.DownloadAsync(input.File); - var fileBytes = await file.GetByteData(); - var pushFileResult = await RestClient.PushChanges(projectId, branchRequest.Name, input.CommitMessage, - input.DestinationFilePath, fileBytes, GitLabCommitActions.Create); - - return new(pushFileResult); + var repository = await RestClient.GetProject(projectId); + var branch = branchRequest.Name ?? repository.DefaultBranch; + + var action = await CheckFileExists(projectId, input.DestinationFilePath, branch) + ? GitLabCommitActions.Update + : GitLabCommitActions.Create; + + return await CommitFile(repository, branch, input, action); } [Action("Update file", Description = "Update existing file in a repository")] - public async Task UpdateFile( + public async Task UpdateFile( [ActionParameter] GetRepositoryRequest repositoryRequest, [ActionParameter] GetOptionalBranchRequest branchRequest, [ActionParameter] PushFileRequest input) { var projectId = ParseProjectId(repositoryRequest.RepositoryId); - - var repContent = - await new RepositoryActions(InvocationContext, _fileManagementClient).ListRepositoryContent( - repositoryRequest, branchRequest, - new FolderContentWithTypeRequest { IncludeSubfolders = true, ContentType = GitLabItemTypes.Blob }); - - if (!repContent.Content.Select(x => x.Path).Contains(input.DestinationFilePath.Trim('/'))) - throw new PluginApplicationException("File does not exist by specified file path!"); - - var file = await _fileManagementClient.DownloadAsync(input.File); - var fileBytes = await file.GetByteData(); - var fileUpload = await RestClient.PushChanges(projectId, branchRequest.Name, input.CommitMessage, - input.DestinationFilePath, fileBytes, GitLabCommitActions.Update); - - return new(fileUpload); + var repository = await RestClient.GetProject(projectId); + var branch = branchRequest.Name ?? repository.DefaultBranch; + + var fileExists = await CheckFileExists(projectId, input.DestinationFilePath, branch); + if (!fileExists) + throw new PluginMisconfigurationException("File does not exist"); + + return await CommitFile(repository, branch, input, GitLabCommitActions.Update); + } + + private async Task CommitFile( + Project repository, + string branch, + PushFileRequest input, + string action) + { + int projectId = repository.Id; + var fileStream = await fileManagementClient.DownloadAsync(input.File); + var uploadedFile = new UploadedFile(fileStream, input.File.Name, input.File.ContentType); + var processedFile = await FileHelper.ProcessUploadFile(uploadedFile); + + var pushResult = await RestClient.PushChanges( + projectId, + branch, + input.CommitMessage, + input.DestinationFilePath, + processedFile.FileBytes, + action); + + var commitDto = new CommitDto(pushResult); + + if (processedFile.Transformation is null) + return new(commitDto, input.File); + + var uploadedFileInfo = await RestClient.GetFileInfo(projectId, input.DestinationFilePath, branch); + + var transformation = processedFile.Transformation.AddTargetMetadata( + uploadedFileInfo.FilePath, + uploadedFile.Name, + branch, + repository.WebUrl); + + var transformedFile = await fileManagementClient.UploadAsync( + transformation.ToStream(), + MediaTypes.Xliff2, + transformation.BilingualFileName); + + return new(commitDto, transformedFile); + } + + public async Task CheckFileExists(int projectId, string filePath, string branch) + { + var endpoint = $"/projects/{projectId}/repository/files/{Uri.EscapeDataString(filePath.Trim('/'))}"; + var request = RestClient.CreateRequest(endpoint, Method.Head).AddQueryParameter("ref", branch); + + var response = await RestClient.ExecuteWithErrorHandling(request, HttpStatusCode.NotFound); + return response.IsSuccessStatusCode; } [Action("Delete file", Description = "Delete file from a repository")] diff --git a/Apps.GitLab/Actions/RepositoryActions.cs b/Apps.GitLab/Actions/RepositoryActions.cs index 12c94ae..3a7ec0b 100644 --- a/Apps.GitLab/Actions/RepositoryActions.cs +++ b/Apps.GitLab/Actions/RepositoryActions.cs @@ -50,17 +50,13 @@ public async Task GetFile( [ActionParameter] GetFileRequest getFileRequest) { var projectId = ParseProjectId(repositoryRequest.RepositoryId); - var repository = await GetProject(projectId); + var repository = await RestClient.GetProject(projectId); var branch = branchRequest.Name ?? repository.DefaultBranch; - string endpoint = $"/projects/{projectId}/repository/files/{Uri.EscapeDataString(getFileRequest.FilePath)}"; - var request = RestClient.CreateRequest(endpoint, Method.Get).AddQueryParameter("ref", branch); - - var repoFileResponse = await RestClient.ExecuteWithErrorHandling(request) ?? - throw new PluginMisconfigurationException($"File does not exist ({getFileRequest.FilePath})"); - - var fileToProcess = new FileToProcess(repoFileResponse.Content, getFileRequest.FilePath, repository.WebUrl, branch); + var fileInfo = await RestClient.GetFileInfo(projectId, getFileRequest.FilePath, branch); + var fileToProcess = new DownloadedFile(fileInfo.Content, getFileRequest.FilePath, repository.WebUrl, branch); var fileData = FileHelper.ProcessDownloadedFile(fileToProcess); + var fileReference = await fileManagementClient.UploadAsync(fileData.FileStream, fileData.MimeType, fileData.FileName); return new GetFileResponse { @@ -129,7 +125,7 @@ public async Task GetAllFilesInFolder( [Action("Get repository", Description = "Get repository details")] public async Task GetRepositoryById([ActionParameter] GetRepositoryRequest input) { - var project = await GetProject(ParseProjectId(input.RepositoryId)); + var project = await RestClient.GetProject(ParseProjectId(input.RepositoryId)); return RepositoryResponse.FromProject(project); } @@ -232,12 +228,4 @@ public async Task BranchExists( var branches = await RestClient.ExecuteWithErrorHandling>(request); return branches.Any(x => x.Name == branchNameRequest); } - - private Task GetProject(int projectId) - { - var request = RestClient.CreateRequest($"/projects/{projectId}", Method.Get); - return RestClient.ExecuteWithErrorHandling(request); - } - - } diff --git a/Apps.GitLab/BlackbirdGitLabClient.cs b/Apps.GitLab/BlackbirdGitLabClient.cs index 72a3264..3c80220 100644 --- a/Apps.GitLab/BlackbirdGitLabClient.cs +++ b/Apps.GitLab/BlackbirdGitLabClient.cs @@ -1,14 +1,17 @@ +using System.Net; using Apps.GitLab.Constants; using Apps.GitLab.Dtos; using Apps.Gitlab.Constants; +using Apps.GitLab.Models.Respository.Responses; using Blackbird.Applications.Sdk.Common.Authentication; using Blackbird.Applications.Sdk.Common.Exceptions; using Blackbird.Applications.Sdk.Utils.Extensions.Sdk; using Blackbird.Applications.Sdk.Utils.Extensions.String; using Blackbird.Applications.Sdk.Utils.RestSharp; -using GitLabApiClient.Models.Commits.Responses; +using GitLabApiClient.Models.Projects.Responses; using Newtonsoft.Json; using RestSharp; +using Commit = GitLabApiClient.Models.Commits.Responses.Commit; namespace Apps.Gitlab; @@ -30,6 +33,15 @@ public BlackbirdGitlabClient(IEnumerable auth _authenticationCredentials = authenticationCredentialsProviders; BaseUrl = GetBaseUrl(authenticationCredentialsProviders); } + + public async Task ExecuteWithErrorHandling(RestRequest request, params HttpStatusCode[] allowedStatuses) + { + var response = await ExecuteAsync(request); + if (response.IsSuccessStatusCode || allowedStatuses.Contains(response.StatusCode)) + return response; + + throw ConfigureErrorException(response); + } public static string GetBaseUrl(IEnumerable creds) { @@ -53,6 +65,20 @@ public async Task ExecuteForBytesWithErrorHandling(RestRequest request) return response.RawBytes ?? []; } + + public async Task GetProject(int projectId) + { + var request = CreateRequest($"/projects/{projectId}", Method.Get); + return await ExecuteWithErrorHandling(request); + } + + public async Task GetFileInfo(int projectId, string filePath, string branch) + { + string endpoint = $"/projects/{projectId}/repository/files/{Uri.EscapeDataString(filePath)}"; + var request = CreateRequest(endpoint, Method.Get).AddQueryParameter("ref", branch); + + return await ExecuteWithErrorHandling(request); + } public async Task GetArchive(int projectId, string? branchName) { @@ -65,13 +91,12 @@ public async Task GetArchive(int projectId, string? branchName) public async Task PushChanges(int projectId, string? branchName, string commitMessage, string filePath, byte[]? file, string action) { - var repository = await ExecuteWithErrorHandling( - CreateRequest($"/projects/{projectId}", Method.Get)); + var branch = string.IsNullOrWhiteSpace(branchName) ? (await GetProject(projectId)).DefaultBranch : branchName; var request = CreateRequest($"/projects/{projectId}/repository/commits", Method.Post); request.AddJsonBody(new { - branch = branchName ?? repository.DefaultBranch, + branch, commit_message = commitMessage, actions = new[] { diff --git a/Apps.GitLab/Extensions/TransformationExtensions.cs b/Apps.GitLab/Extensions/TransformationExtensions.cs new file mode 100644 index 0000000..5818fb3 --- /dev/null +++ b/Apps.GitLab/Extensions/TransformationExtensions.cs @@ -0,0 +1,46 @@ +using Blackbird.Filters.Transformations; + +namespace Apps.Gitlab.Extensions; + +public static class TransformationExtensions +{ + private static (string blobUrl, string editUrl) BuildUrls(string filePath, string branchName, string repoWebUrl) + { + var encodedPath = string.Join("/", filePath.Split('/').Select(Uri.EscapeDataString)); + return ($"{repoWebUrl}/-/blob/{branchName}/{encodedPath}", $"{repoWebUrl}/-/edit/{branchName}/{encodedPath}"); + } + + public static Transformation AddSourceMetadata( + this Transformation t, + string filePath, + string fileName, + string branchName, + string repoWebUrl) + { + var (blobUrl, editUrl) = BuildUrls(filePath, branchName, repoWebUrl); + t.SourceLanguage = fileName.Split('.')[0]; + t.SourceSystemReference.ContentId = blobUrl; + t.SourceSystemReference.AdminUrl = editUrl; + t.SourceSystemReference.ContentName = fileName; + t.SourceSystemReference.SystemName = "GitLab"; + t.SourceSystemReference.SystemRef = "https://gitlab.com/"; + return t; + } + + public static Transformation AddTargetMetadata( + this Transformation t, + string filePath, + string fileName, + string branchName, + string repoWebUrl) + { + var (blobUrl, editUrl) = BuildUrls(filePath, branchName, repoWebUrl); + t.TargetLanguage = fileName.Split('.')[0]; + t.TargetSystemReference.ContentId = blobUrl; + t.TargetSystemReference.AdminUrl = editUrl; + t.TargetSystemReference.ContentName = fileName; + t.TargetSystemReference.SystemName = "GitLab"; + t.TargetSystemReference.SystemRef = "https://gitlab.com/"; + return t; + } +} \ No newline at end of file diff --git a/Apps.GitLab/Models/Commit/Responses/UploadFileResponse.cs b/Apps.GitLab/Models/Commit/Responses/UploadFileResponse.cs new file mode 100644 index 0000000..d94fd60 --- /dev/null +++ b/Apps.GitLab/Models/Commit/Responses/UploadFileResponse.cs @@ -0,0 +1,6 @@ +using Apps.GitLab.Dtos; +using Blackbird.Applications.Sdk.Common.Files; + +namespace Apps.GitLab.Models.Commit.Responses; + +public record UploadFileResponse(CommitDto Commit, FileReference File); \ No newline at end of file diff --git a/Apps.GitLab/Models/Respository/Responses/RepositoryFileResponse.cs b/Apps.GitLab/Models/Respository/Responses/RepositoryFileResponse.cs index cad7760..8ac1f1c 100644 --- a/Apps.GitLab/Models/Respository/Responses/RepositoryFileResponse.cs +++ b/Apps.GitLab/Models/Respository/Responses/RepositoryFileResponse.cs @@ -1,7 +1,18 @@ -namespace Apps.GitLab.Models.Respository.Responses; +using Newtonsoft.Json; + +namespace Apps.GitLab.Models.Respository.Responses; public class RepositoryFileResponse { + [JsonProperty("content")] public string Content { get; set; } = string.Empty; + + [JsonProperty("file_path")] + public string FilePath { get; set; } = string.Empty; + + [JsonProperty("file_name")] + public string FileName { get; set; } = string.Empty; + + } diff --git a/Apps.GitLab/Utils/File/DownloadedFile.cs b/Apps.GitLab/Utils/File/DownloadedFile.cs new file mode 100644 index 0000000..daf2404 --- /dev/null +++ b/Apps.GitLab/Utils/File/DownloadedFile.cs @@ -0,0 +1,3 @@ +namespace Apps.GitLab.Utils.File; + +public record DownloadedFile(string Content, string Path, string RepoWebUrl, string BranchName); \ No newline at end of file diff --git a/Apps.GitLab/Utils/File/FileContentResult.cs b/Apps.GitLab/Utils/File/FileContentResult.cs deleted file mode 100644 index 29f1522..0000000 --- a/Apps.GitLab/Utils/File/FileContentResult.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Apps.GitLab.Utils.File; - -public record FileContentResult(Stream FileStream, string MimeType, string FileName); \ No newline at end of file diff --git a/Apps.GitLab/Utils/File/FileHelper.cs b/Apps.GitLab/Utils/File/FileHelper.cs index 0bf1080..2e5c064 100644 --- a/Apps.GitLab/Utils/File/FileHelper.cs +++ b/Apps.GitLab/Utils/File/FileHelper.cs @@ -1,18 +1,21 @@ using System.Net.Mime; +using Apps.Gitlab.Extensions; using Blackbird.Applications.Sdk.Common.Exceptions; +using Blackbird.Applications.Sdk.Utils.Extensions.Files; +using Blackbird.Filters.Enums; using Blackbird.Filters.Transformations; namespace Apps.GitLab.Utils.File; public static class FileHelper { - public static FileContentResult ProcessDownloadedFile(FileToProcess fileToProcess) + public static ProcessedDownloadedFile ProcessDownloadedFile(DownloadedFile downloadedFile) { - var filename = Path.GetFileName(fileToProcess.Path); + var filename = Path.GetFileName(downloadedFile.Path); if (!MimeTypes.TryGetMimeType(filename, out var mimeType)) mimeType = MediaTypeNames.Application.Octet; - var stream = new MemoryStream(Convert.FromBase64String(fileToProcess.Content)); + var stream = new MemoryStream(Convert.FromBase64String(downloadedFile.Content)); var transformationResult = Transformation.Load(stream, filename, mimeType); if (!transformationResult.Success) @@ -20,19 +23,12 @@ public static FileContentResult ProcessDownloadedFile(FileToProcess fileToProces stream.Position = 0; return new(stream, mimeType, filename); } - - string encodedPath = string.Join("/", fileToProcess.Path.Split('/').Select(Uri.EscapeDataString)); - string blobUrl = $"{fileToProcess.RepoWebUrl}/-/blob/{fileToProcess.BranchName}/{encodedPath}"; - string editUrl = $"{fileToProcess.RepoWebUrl}/-/edit/{fileToProcess.BranchName}/{encodedPath}"; - var transformation = transformationResult.Value!; - - transformation.SourceLanguage = filename.Split('.')[0]; - transformation.SourceSystemReference.ContentId = blobUrl; - transformation.SourceSystemReference.AdminUrl = editUrl; - transformation.SourceSystemReference.ContentName = filename; - transformation.SourceSystemReference.SystemName = "GitLab"; - transformation.SourceSystemReference.SystemRef = "https://gitlab.com/"; + var transformation = transformationResult.Value.AddSourceMetadata( + downloadedFile.Path, + filename, + downloadedFile.BranchName, + downloadedFile.RepoWebUrl); var sourceLoadResult = transformation.Source(); if (!sourceLoadResult.Success) @@ -40,4 +36,32 @@ public static FileContentResult ProcessDownloadedFile(FileToProcess fileToProces return new(sourceLoadResult.Value.ToStream(), mimeType, filename); } + + public static async Task ProcessUploadFile(UploadedFile uploadedFile) + { + var transformationResult = Transformation.Load(uploadedFile.Stream, uploadedFile.Name, uploadedFile.ContentType); + bool isJson = uploadedFile.Name.EndsWith(".json", StringComparison.OrdinalIgnoreCase); + + if (transformationResult.Success && isJson) + { + var sourceLoadResult = transformationResult.Value.Source(); + if (!sourceLoadResult.Success) + throw new PluginMisconfigurationException(sourceLoadResult.Error); + + var bytes = await sourceLoadResult.Value.ToStream(MetadataHandling.Exclude).GetByteData(); + return new(bytes, null); + } + if (transformationResult.Success) + { + var targetLoadResult = transformationResult.Value.Target(); + if (!targetLoadResult.Success) + throw new PluginMisconfigurationException(targetLoadResult.Error); + + var bytes = await targetLoadResult.Value.ToStream(MetadataHandling.Exclude).GetByteData(); + return new(bytes, transformationResult.Value); + } + + var raw = await uploadedFile.Stream.GetByteData(); + return new(raw, null); + } } \ No newline at end of file diff --git a/Apps.GitLab/Utils/File/FileToProcess.cs b/Apps.GitLab/Utils/File/FileToProcess.cs deleted file mode 100644 index dd5fba5..0000000 --- a/Apps.GitLab/Utils/File/FileToProcess.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Apps.GitLab.Utils.File; - -public record FileToProcess(string Content, string Path, string RepoWebUrl, string BranchName); \ No newline at end of file diff --git a/Apps.GitLab/Utils/File/ProcessedDownloadedFile.cs b/Apps.GitLab/Utils/File/ProcessedDownloadedFile.cs new file mode 100644 index 0000000..ab186b2 --- /dev/null +++ b/Apps.GitLab/Utils/File/ProcessedDownloadedFile.cs @@ -0,0 +1,3 @@ +namespace Apps.GitLab.Utils.File; + +public record ProcessedDownloadedFile(Stream FileStream, string MimeType, string FileName); \ No newline at end of file diff --git a/Apps.GitLab/Utils/File/ProcessedUploadedFile.cs b/Apps.GitLab/Utils/File/ProcessedUploadedFile.cs new file mode 100644 index 0000000..dc760aa --- /dev/null +++ b/Apps.GitLab/Utils/File/ProcessedUploadedFile.cs @@ -0,0 +1,5 @@ +using Blackbird.Filters.Transformations; + +namespace Apps.GitLab.Utils.File; + +public record ProcessedUploadedFile(byte[] FileBytes, Transformation? Transformation); \ No newline at end of file diff --git a/Apps.GitLab/Utils/File/UploadedFile.cs b/Apps.GitLab/Utils/File/UploadedFile.cs new file mode 100644 index 0000000..c6b4655 --- /dev/null +++ b/Apps.GitLab/Utils/File/UploadedFile.cs @@ -0,0 +1,3 @@ +namespace Apps.GitLab.Utils.File; + +public record UploadedFile(Stream Stream, string Name, string ContentType); \ No newline at end of file diff --git a/Tests.GitLab/RepositoryActionTests.cs b/Tests.GitLab/RepositoryActionTests.cs index 2e5ce75..a96e12e 100644 --- a/Tests.GitLab/RepositoryActionTests.cs +++ b/Tests.GitLab/RepositoryActionTests.cs @@ -1,6 +1,7 @@ using Apps.Gitlab.Actions; using Apps.Gitlab.Models.Respository.Requests; using Apps.GitLab.Constants; +using Apps.Gitlab.Models.Branch.Requests; using Apps.GitLab.Models.Respository.Requests; using Blackbird.Applications.Sdk.Common.Invocation; using Tests.GitLab.Base; @@ -37,5 +38,22 @@ public async Task CreateRepository_WithExistingRepository_ReturnsRepository(Invo Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(result, Newtonsoft.Json.Formatting.Indented)); Assert.IsNotNull(result); } + + [TestMethod, ContextDataSource(ConnectionTypes.OAuth)] + public async Task GetFile_WithExistingFile_ReturnsFile(InvocationContext context) + { + // Arrange + var action = new RepositoryActions(context, FileManagementClient); + var repoRequest = new GetRepositoryRequest { RepositoryId = "84026361" }; + var branchRequest = new GetOptionalBranchRequest { }; + var fileRequest = new GetFileRequest { FilePath = "uk-UA.test.html" }; + + // Act + var result = await action.GetFile(repoRequest, branchRequest, fileRequest); + + // Assert + PrintResult(result); + Assert.IsNotNull(result); + } } From c04308af898180bd7f76f0e0c2eed807ccb2dea5 Mon Sep 17 00:00:00 2001 From: Fesenko-A <111450737+Fesenko-A@users.noreply.github.com> Date: Fri, 3 Jul 2026 16:37:24 +0300 Subject: [PATCH 4/6] update docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 864d57f..2a12207 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ If your GitLab instance is hosted on a custom domain, use the **OAuth Self-manag - **List added or modified files in X hours** Search files added or modified during specified number of hours. Advanced settings: - **Path pattern**: Use forward slash '/' to represent directory separator. Use '*' to represent wildcards in file and directory names. Use '**' to represent arbitrary directory depth. -- **Create or update file** Create file or update existing file in a repository. +- **Upload file** Create file or update existing file in a repository. - **Update file** Update existing file in a repository. - **Delete file** Delete file from a repository. From 0a3a6b6a743dd93d1da67a48b153a93f7097087d Mon Sep 17 00:00:00 2001 From: Fesenko-A <111450737+Fesenko-A@users.noreply.github.com> Date: Tue, 7 Jul 2026 10:36:45 +0300 Subject: [PATCH 5/6] add support for self-hosted instance URLs for blacklake metadata --- Apps.GitLab/Actions/CommitActions.cs | 3 ++- Apps.GitLab/Actions/RepositoryActions.cs | 7 +++++- .../Extensions/TransformationExtensions.cs | 10 +++++--- Apps.GitLab/Utils/File/DownloadedFile.cs | 2 +- Apps.GitLab/Utils/File/FileHelper.cs | 3 ++- Tests.GitLab/CommitActionTests.cs | 25 +++++++++++++++++++ 6 files changed, 42 insertions(+), 8 deletions(-) diff --git a/Apps.GitLab/Actions/CommitActions.cs b/Apps.GitLab/Actions/CommitActions.cs index 8974c6b..38984a0 100644 --- a/Apps.GitLab/Actions/CommitActions.cs +++ b/Apps.GitLab/Actions/CommitActions.cs @@ -263,7 +263,8 @@ private async Task CommitFile( uploadedFileInfo.FilePath, uploadedFile.Name, branch, - repository.WebUrl); + repository.WebUrl, + RestClient.BaseUrl); var transformedFile = await fileManagementClient.UploadAsync( transformation.ToStream(), diff --git a/Apps.GitLab/Actions/RepositoryActions.cs b/Apps.GitLab/Actions/RepositoryActions.cs index 3a7ec0b..3b4f029 100644 --- a/Apps.GitLab/Actions/RepositoryActions.cs +++ b/Apps.GitLab/Actions/RepositoryActions.cs @@ -54,7 +54,12 @@ public async Task GetFile( var branch = branchRequest.Name ?? repository.DefaultBranch; var fileInfo = await RestClient.GetFileInfo(projectId, getFileRequest.FilePath, branch); - var fileToProcess = new DownloadedFile(fileInfo.Content, getFileRequest.FilePath, repository.WebUrl, branch); + var fileToProcess = new DownloadedFile( + fileInfo.Content, + getFileRequest.FilePath, + repository.WebUrl, + branch, + RestClient.BaseUrl); var fileData = FileHelper.ProcessDownloadedFile(fileToProcess); var fileReference = await fileManagementClient.UploadAsync(fileData.FileStream, fileData.MimeType, fileData.FileName); diff --git a/Apps.GitLab/Extensions/TransformationExtensions.cs b/Apps.GitLab/Extensions/TransformationExtensions.cs index 5818fb3..a3d16c1 100644 --- a/Apps.GitLab/Extensions/TransformationExtensions.cs +++ b/Apps.GitLab/Extensions/TransformationExtensions.cs @@ -15,7 +15,8 @@ public static Transformation AddSourceMetadata( string filePath, string fileName, string branchName, - string repoWebUrl) + string repoWebUrl, + string baseUrl) { var (blobUrl, editUrl) = BuildUrls(filePath, branchName, repoWebUrl); t.SourceLanguage = fileName.Split('.')[0]; @@ -23,7 +24,7 @@ public static Transformation AddSourceMetadata( t.SourceSystemReference.AdminUrl = editUrl; t.SourceSystemReference.ContentName = fileName; t.SourceSystemReference.SystemName = "GitLab"; - t.SourceSystemReference.SystemRef = "https://gitlab.com/"; + t.SourceSystemReference.SystemRef = baseUrl; return t; } @@ -32,7 +33,8 @@ public static Transformation AddTargetMetadata( string filePath, string fileName, string branchName, - string repoWebUrl) + string repoWebUrl, + string baseUrl) { var (blobUrl, editUrl) = BuildUrls(filePath, branchName, repoWebUrl); t.TargetLanguage = fileName.Split('.')[0]; @@ -40,7 +42,7 @@ public static Transformation AddTargetMetadata( t.TargetSystemReference.AdminUrl = editUrl; t.TargetSystemReference.ContentName = fileName; t.TargetSystemReference.SystemName = "GitLab"; - t.TargetSystemReference.SystemRef = "https://gitlab.com/"; + t.TargetSystemReference.SystemRef = baseUrl; return t; } } \ No newline at end of file diff --git a/Apps.GitLab/Utils/File/DownloadedFile.cs b/Apps.GitLab/Utils/File/DownloadedFile.cs index daf2404..1c3918d 100644 --- a/Apps.GitLab/Utils/File/DownloadedFile.cs +++ b/Apps.GitLab/Utils/File/DownloadedFile.cs @@ -1,3 +1,3 @@ namespace Apps.GitLab.Utils.File; -public record DownloadedFile(string Content, string Path, string RepoWebUrl, string BranchName); \ No newline at end of file +public record DownloadedFile(string Content, string Path, string RepoWebUrl, string BranchName, string BaseUrl); \ No newline at end of file diff --git a/Apps.GitLab/Utils/File/FileHelper.cs b/Apps.GitLab/Utils/File/FileHelper.cs index 2e5c064..4e9a9a7 100644 --- a/Apps.GitLab/Utils/File/FileHelper.cs +++ b/Apps.GitLab/Utils/File/FileHelper.cs @@ -28,7 +28,8 @@ public static ProcessedDownloadedFile ProcessDownloadedFile(DownloadedFile downl downloadedFile.Path, filename, downloadedFile.BranchName, - downloadedFile.RepoWebUrl); + downloadedFile.RepoWebUrl, + downloadedFile.BaseUrl); var sourceLoadResult = transformation.Source(); if (!sourceLoadResult.Success) diff --git a/Tests.GitLab/CommitActionTests.cs b/Tests.GitLab/CommitActionTests.cs index ff34a13..d8f5aa0 100644 --- a/Tests.GitLab/CommitActionTests.cs +++ b/Tests.GitLab/CommitActionTests.cs @@ -2,7 +2,9 @@ using Apps.Gitlab.Models.Branch.Requests; using Apps.Gitlab.Models.Respository.Requests; using Apps.GitLab.Constants; +using Apps.Gitlab.Models.Commit.Requests; using Apps.GitLab.Models.Commit.Requests; +using Blackbird.Applications.Sdk.Common.Files; using Blackbird.Applications.Sdk.Common.Invocation; using Tests.GitLab.Base; @@ -195,4 +197,27 @@ public async Task FindCommit_WithCommitBeforeExactBoundary_ExcludesBoundaryCommi Assert.AreEqual("9e41fdbe88e1a2099db6dd31c769267a2bf420f3", result.Id); } + + [TestMethod, ContextDataSource(ConnectionTypes.OAuth)] + public async Task PushFile_WithValidFile_ReturnsUploadResponse(InvocationContext context) + { + // Arrange + string fileName = "ja-JP.test.html"; + var actions = new CommitActions(context, FileManagementClient); + var repoRequest = new GetRepositoryRequest { RepositoryId = "84026361" }; + var branchRequest = new GetOptionalBranchRequest { }; + var pushFileInput = new PushFileRequest + { + CommitMessage = "test from tests", + DestinationFilePath = fileName, + File = new FileReference { Name = fileName } + }; + + // Act + var result = await actions.PushFile(repoRequest, branchRequest, pushFileInput); + + // Assert + PrintResult(result); + Assert.IsNotNull(result); + } } From cf820ed64e91358d49897c79b12f75af3b061bdc Mon Sep 17 00:00:00 2001 From: mathijs-bb Date: Wed, 8 Jul 2026 11:49:14 +0200 Subject: [PATCH 6/6] Blacklake interoperability fixes --- Apps.GitLab/Actions/CommitActions.cs | 68 +++++++++++++------ Apps.GitLab/Actions/RepositoryActions.cs | 2 +- Apps.GitLab/Apps.GitLab.csproj | 2 +- .../Extensions/TransformationExtensions.cs | 38 +---------- .../Respository/Requests/GetFileRequest.cs | 6 ++ Apps.GitLab/Utils/File/FileHelper.cs | 57 ++++------------ 6 files changed, 73 insertions(+), 100 deletions(-) diff --git a/Apps.GitLab/Actions/CommitActions.cs b/Apps.GitLab/Actions/CommitActions.cs index 38984a0..ccf3fd0 100644 --- a/Apps.GitLab/Actions/CommitActions.cs +++ b/Apps.GitLab/Actions/CommitActions.cs @@ -1,12 +1,11 @@ -using System.Net; using Apps.Gitlab.Actions.Base; +using Apps.Gitlab.Extensions; using Apps.Gitlab.Models.Branch.Requests; using Apps.Gitlab.Models.Commit.Requests; using Apps.Gitlab.Models.Respository.Requests; using Apps.Gitlab.Webhooks; using Apps.GitLab.Constants; using Apps.GitLab.Dtos; -using Apps.Gitlab.Extensions; using Apps.GitLab.Models.Commit.Requests; using Apps.GitLab.Models.Commit.Responses; using Apps.GitLab.Utils.File; @@ -16,9 +15,13 @@ using Blackbird.Applications.Sdk.Common.Invocation; using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces; using Blackbird.Filters.Constants; +using Blackbird.Filters.Enums; +using Blackbird.Filters.Extensions; +using Blackbird.Filters.Transformations; using GitLabApiClient.Models.Commits.Responses; using GitLabApiClient.Models.Projects.Responses; using RestSharp; +using System.Net; namespace Apps.Gitlab.Actions; @@ -241,37 +244,64 @@ private async Task CommitFile( { int projectId = repository.Id; var fileStream = await fileManagementClient.DownloadAsync(input.File); - var uploadedFile = new UploadedFile(fileStream, input.File.Name, input.File.ContentType); - var processedFile = await FileHelper.ProcessUploadFile(uploadedFile); + var transformationResult = Transformation.Load(fileStream, input.File.Name, input.File.ContentType); + + string? content = null; + var contentResult = transformationResult.Target(); + if (contentResult.Success) + { + content = contentResult.Value.ToStream(MetadataHandling.Exclude).ReadString(); + } + else + { + InvocationContext.Logger?.LogInformation($"Not a Blackbird interoperable file: {transformationResult.Error}", []); + content = fileStream.ReadString(); + } var pushResult = await RestClient.PushChanges( projectId, branch, input.CommitMessage, - input.DestinationFilePath, - processedFile.FileBytes, + input.DestinationFilePath, + System.Text.Encoding.UTF8.GetBytes(content), action); var commitDto = new CommitDto(pushResult); - if (processedFile.Transformation is null) + if (!transformationResult.Success) return new(commitDto, input.File); - var uploadedFileInfo = await RestClient.GetFileInfo(projectId, input.DestinationFilePath, branch); + var (blobUrl, editUrl) = TransformationExtensions.BuildUrls(input.DestinationFilePath, branch, RestClient.BaseUrl); - var transformation = processedFile.Transformation.AddTargetMetadata( - uploadedFileInfo.FilePath, - uploadedFile.Name, - branch, - repository.WebUrl, - RestClient.BaseUrl); + var transformation = transformationResult.Value; + transformation.TargetSystemReference.ContentName = input.File.Name; + transformation.TargetSystemReference.AdminUrl = editUrl; + transformation.TargetSystemReference.SystemName = "Gitlab"; + transformation.TargetSystemReference.SystemRef = "https://gitlab.com/"; + + if (transformationResult.WasBilingual) + { + var transformedFile = await fileManagementClient.UploadAsync( + transformation.ToStream(), + MediaTypes.Xliff2, + transformation.BilingualFileName); + + return new(commitDto, transformedFile); + } + + var targetResult = transformation.Target(); + if (!targetResult.Success) throw new PluginMisconfigurationException(targetResult.Error); + + var target = targetResult.Value; + target.SystemReference = transformation.TargetSystemReference; + + var targetFile = await fileManagementClient.UploadAsync( + target.ToStream(), + target.OriginalMediaType, + target.OriginalName); - var transformedFile = await fileManagementClient.UploadAsync( - transformation.ToStream(), - MediaTypes.Xliff2, - transformation.BilingualFileName); + return new(commitDto, targetFile); - return new(commitDto, transformedFile); } public async Task CheckFileExists(int projectId, string filePath, string branch) diff --git a/Apps.GitLab/Actions/RepositoryActions.cs b/Apps.GitLab/Actions/RepositoryActions.cs index 3b4f029..4e8d8ac 100644 --- a/Apps.GitLab/Actions/RepositoryActions.cs +++ b/Apps.GitLab/Actions/RepositoryActions.cs @@ -60,7 +60,7 @@ public async Task GetFile( repository.WebUrl, branch, RestClient.BaseUrl); - var fileData = FileHelper.ProcessDownloadedFile(fileToProcess); + var fileData = FileHelper.ProcessDownloadedFile(fileToProcess, InvocationContext.Logger, getFileRequest.LanguageCode, getFileRequest.ContentId); var fileReference = await fileManagementClient.UploadAsync(fileData.FileStream, fileData.MimeType, fileData.FileName); return new GetFileResponse diff --git a/Apps.GitLab/Apps.GitLab.csproj b/Apps.GitLab/Apps.GitLab.csproj index 24cdecf..8e195fe 100644 --- a/Apps.GitLab/Apps.GitLab.csproj +++ b/Apps.GitLab/Apps.GitLab.csproj @@ -14,7 +14,7 @@ - + diff --git a/Apps.GitLab/Extensions/TransformationExtensions.cs b/Apps.GitLab/Extensions/TransformationExtensions.cs index a3d16c1..00b755c 100644 --- a/Apps.GitLab/Extensions/TransformationExtensions.cs +++ b/Apps.GitLab/Extensions/TransformationExtensions.cs @@ -4,45 +4,9 @@ namespace Apps.Gitlab.Extensions; public static class TransformationExtensions { - private static (string blobUrl, string editUrl) BuildUrls(string filePath, string branchName, string repoWebUrl) + public static (string blobUrl, string editUrl) BuildUrls(string filePath, string branchName, string repoWebUrl) { var encodedPath = string.Join("/", filePath.Split('/').Select(Uri.EscapeDataString)); return ($"{repoWebUrl}/-/blob/{branchName}/{encodedPath}", $"{repoWebUrl}/-/edit/{branchName}/{encodedPath}"); } - - public static Transformation AddSourceMetadata( - this Transformation t, - string filePath, - string fileName, - string branchName, - string repoWebUrl, - string baseUrl) - { - var (blobUrl, editUrl) = BuildUrls(filePath, branchName, repoWebUrl); - t.SourceLanguage = fileName.Split('.')[0]; - t.SourceSystemReference.ContentId = blobUrl; - t.SourceSystemReference.AdminUrl = editUrl; - t.SourceSystemReference.ContentName = fileName; - t.SourceSystemReference.SystemName = "GitLab"; - t.SourceSystemReference.SystemRef = baseUrl; - return t; - } - - public static Transformation AddTargetMetadata( - this Transformation t, - string filePath, - string fileName, - string branchName, - string repoWebUrl, - string baseUrl) - { - var (blobUrl, editUrl) = BuildUrls(filePath, branchName, repoWebUrl); - t.TargetLanguage = fileName.Split('.')[0]; - t.TargetSystemReference.ContentId = blobUrl; - t.TargetSystemReference.AdminUrl = editUrl; - t.TargetSystemReference.ContentName = fileName; - t.TargetSystemReference.SystemName = "GitLab"; - t.TargetSystemReference.SystemRef = baseUrl; - return t; - } } \ No newline at end of file diff --git a/Apps.GitLab/Models/Respository/Requests/GetFileRequest.cs b/Apps.GitLab/Models/Respository/Requests/GetFileRequest.cs index af7ee36..7fdee25 100644 --- a/Apps.GitLab/Models/Respository/Requests/GetFileRequest.cs +++ b/Apps.GitLab/Models/Respository/Requests/GetFileRequest.cs @@ -9,4 +9,10 @@ public class GetFileRequest [Display("File path")] [FileDataSource(typeof(FilePickerDataHandler))] public string FilePath { get; set; } + + [Display("Source language code", Description = "The language of the file used in later Actions.")] + public string? LanguageCode { get; set; } + + [Display("Content ID", Description = "The ID of the content, used by Blacklake when diffing.")] + public string? ContentId { get; set; } } diff --git a/Apps.GitLab/Utils/File/FileHelper.cs b/Apps.GitLab/Utils/File/FileHelper.cs index 4e9a9a7..114275d 100644 --- a/Apps.GitLab/Utils/File/FileHelper.cs +++ b/Apps.GitLab/Utils/File/FileHelper.cs @@ -1,68 +1,41 @@ -using System.Net.Mime; using Apps.Gitlab.Extensions; using Blackbird.Applications.Sdk.Common.Exceptions; +using Blackbird.Applications.Sdk.Common.Invocation; using Blackbird.Applications.Sdk.Utils.Extensions.Files; using Blackbird.Filters.Enums; using Blackbird.Filters.Transformations; +using System.Net.Mime; namespace Apps.GitLab.Utils.File; public static class FileHelper { - public static ProcessedDownloadedFile ProcessDownloadedFile(DownloadedFile downloadedFile) + public static ProcessedDownloadedFile ProcessDownloadedFile(DownloadedFile downloadedFile, Logger? logger, string? language, string? contentId) { var filename = Path.GetFileName(downloadedFile.Path); if (!MimeTypes.TryGetMimeType(filename, out var mimeType)) mimeType = MediaTypeNames.Application.Octet; var stream = new MemoryStream(Convert.FromBase64String(downloadedFile.Content)); - var transformationResult = Transformation.Load(stream, filename, mimeType); + var fileResult = Transformation.Load(stream, filename, mimeType).Source(); - if (!transformationResult.Success) + if (!fileResult.Success) { stream.Position = 0; + logger?.LogInformation($"Not a Blackbird interoperable file: {fileResult.Error}", []); return new(stream, mimeType, filename); } - - var transformation = transformationResult.Value.AddSourceMetadata( - downloadedFile.Path, - filename, - downloadedFile.BranchName, - downloadedFile.RepoWebUrl, - downloadedFile.BaseUrl); - - var sourceLoadResult = transformation.Source(); - if (!sourceLoadResult.Success) - throw new PluginMisconfigurationException(sourceLoadResult.Error); - - return new(sourceLoadResult.Value.ToStream(), mimeType, filename); - } - - public static async Task ProcessUploadFile(UploadedFile uploadedFile) - { - var transformationResult = Transformation.Load(uploadedFile.Stream, uploadedFile.Name, uploadedFile.ContentType); - bool isJson = uploadedFile.Name.EndsWith(".json", StringComparison.OrdinalIgnoreCase); - - if (transformationResult.Success && isJson) - { - var sourceLoadResult = transformationResult.Value.Source(); - if (!sourceLoadResult.Success) - throw new PluginMisconfigurationException(sourceLoadResult.Error); - var bytes = await sourceLoadResult.Value.ToStream(MetadataHandling.Exclude).GetByteData(); - return new(bytes, null); - } - if (transformationResult.Success) - { - var targetLoadResult = transformationResult.Value.Target(); - if (!targetLoadResult.Success) - throw new PluginMisconfigurationException(targetLoadResult.Error); + var fileContent = fileResult.Value; + var (blobUrl, editUrl) = TransformationExtensions.BuildUrls(downloadedFile.Path, downloadedFile.BranchName, downloadedFile.RepoWebUrl); - var bytes = await targetLoadResult.Value.ToStream(MetadataHandling.Exclude).GetByteData(); - return new(bytes, transformationResult.Value); - } + fileContent.Language = language; + fileContent.SystemReference.ContentId = contentId; + fileContent.SystemReference.AdminUrl = editUrl; + fileContent.SystemReference.ContentName = filename; + fileContent.SystemReference.SystemName = "Gitlab"; + fileContent.SystemReference.SystemRef = "https://gitlab.com/"; - var raw = await uploadedFile.Stream.GetByteData(); - return new(raw, null); + return new(fileContent.ToStream(), mimeType, filename); } } \ No newline at end of file