Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 101 additions & 53 deletions Apps.GitLab/Actions/CommitActions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
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;
Expand All @@ -7,29 +8,28 @@
using Apps.GitLab.Dtos;
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 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;

[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<ListRepositoryCommitsResponse> ListRepositoryCommits(
Expand Down Expand Up @@ -202,67 +202,115 @@ public async Task<ListAddedOrModifiedInHoursResponse> ListAddedOrModifiedInHours
};
}

[Action("Create or update file", Description = "Create file or update existing file in a repository")]
public async Task<CommitDto> PushFile(
[Action("Upload file", Description = "Create file or update existing file in a repository")]
public async Task<UploadFileResponse> PushFile(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] GetOptionalBranchRequest branchRequest,
[ActionParameter] PushFileRequest input)
{
var projectId = ParseProjectId(repositoryRequest.RepositoryId);
var repository = await RestClient.GetProject(projectId);
var branch = branchRequest.Name ?? repository.DefaultBranch;
Comment thread
Fesenko-A marked this conversation as resolved.

var action = await CheckFileExists(projectId, input.DestinationFilePath, branch)
? GitLabCommitActions.Update
: GitLabCommitActions.Create;

return await CommitFile(repository, branch, input, action);
}

var repContent =
await new RepositoryActions(InvocationContext, _fileManagementClient).ListRepositoryContent(
repositoryRequest, branchRequest, new FolderContentWithTypeRequest { IncludeSubfolders = true });
[Action("Update file", Description = "Update existing file in a repository")]
public async Task<UploadFileResponse> UpdateFile(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] GetOptionalBranchRequest branchRequest,
[ActionParameter] PushFileRequest input)
{
var projectId = ParseProjectId(repositoryRequest.RepositoryId);
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<UploadFileResponse> CommitFile(
Project repository,
string branch,
PushFileRequest input,
string action)
{
int projectId = repository.Id;
var fileStream = await fileManagementClient.DownloadAsync(input.File);
var transformationResult = Transformation.Load(fileStream, input.File.Name, input.File.ContentType);

if (repContent.Content.Where(x => x.Type == GitLabItemTypes.Blob).Any(p => p.Path == input.DestinationFilePath))
string? content = null;
var contentResult = transformationResult.Target();
if (contentResult.Success)
{
return await UpdateFile(
repositoryRequest,
branchRequest,
new PushFileRequest
{
DestinationFilePath = input.DestinationFilePath,
File = input.File,
CommitMessage = input.CommitMessage
});
content = contentResult.Value.ToStream(MetadataHandling.Exclude).ReadString();
}

if (repContent.Content.Where(x => x.Type == GitLabItemTypes.Tree).Select(x => x.Path)
.Contains(input.DestinationFilePath.Trim('/')))
else
{
throw new PluginMisconfigurationException("Destination file path is invalid!");
InvocationContext.Logger?.LogInformation($"Not a Blackbird interoperable file: {transformationResult.Error}", []);
content = fileStream.ReadString();
}

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);
var pushResult = await RestClient.PushChanges(
projectId,
branch,
input.CommitMessage,
input.DestinationFilePath,
System.Text.Encoding.UTF8.GetBytes(content),
action);

var commitDto = new CommitDto(pushResult);

return new(pushFileResult);
}
if (!transformationResult.Success)
return new(commitDto, input.File);

[Action("Update file", Description = "Update existing file in a repository")]
public async Task<CommitDto> UpdateFile(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] GetOptionalBranchRequest branchRequest,
[ActionParameter] PushFileRequest input)
{
var projectId = ParseProjectId(repositoryRequest.RepositoryId);
var (blobUrl, editUrl) = TransformationExtensions.BuildUrls(input.DestinationFilePath, branch, 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/";

var repContent =
await new RepositoryActions(InvocationContext, _fileManagementClient).ListRepositoryContent(
repositoryRequest, branchRequest,
new FolderContentWithTypeRequest { IncludeSubfolders = true, ContentType = GitLabItemTypes.Blob });
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);

if (!repContent.Content.Select(x => x.Path).Contains(input.DestinationFilePath.Trim('/')))
throw new PluginApplicationException("File does not exist by specified file path!");
var target = targetResult.Value;
target.SystemReference = transformation.TargetSystemReference;

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);
var targetFile = await fileManagementClient.UploadAsync(
target.ToStream(),
target.OriginalMediaType,
target.OriginalName);

return new(fileUpload);
return new(commitDto, targetFile);

}

public async Task<bool> 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")]
Expand Down
58 changes: 18 additions & 40 deletions Apps.GitLab/Actions/RepositoryActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<RepositoryResponse> CreateRepository([ActionParameter] CreateRepositoryInput input)
{
Expand All @@ -50,39 +43,30 @@ public async Task<RepositoryResponse> 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<GetFileResponse> GetFile(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] GetOptionalBranchRequest branchRequest,
[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;

var request = RestClient.CreateRequest(
$"/projects/{projectId}/repository/files/{Uri.EscapeDataString(getFileRequest.FilePath)}",
Method.Get);
request.AddQueryParameter("ref", branch);

var fileData = await RestClient.ExecuteWithErrorHandling<RepositoryFileResponse>(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 fileInfo = await RestClient.GetFileInfo(projectId, getFileRequest.FilePath, branch);
var fileToProcess = new DownloadedFile(
fileInfo.Content,
getFileRequest.FilePath,
repository.WebUrl,
branch,
RestClient.BaseUrl);
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
{
FilePath = getFileRequest.FilePath,
File = file,
File = fileReference,
FileExtension = Path.GetExtension(getFileRequest.FilePath)
};
}
Expand Down Expand Up @@ -132,7 +116,7 @@ public async Task<GetRepositoryFilesFromFilepathsResponse> 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,
Expand All @@ -146,7 +130,7 @@ public async Task<GetRepositoryFilesFromFilepathsResponse> GetAllFilesInFolder(
[Action("Get repository", Description = "Get repository details")]
public async Task<RepositoryResponse> GetRepositoryById([ActionParameter] GetRepositoryRequest input)
{
var project = await GetProject(ParseProjectId(input.RepositoryId));
var project = await RestClient.GetProject(ParseProjectId(input.RepositoryId));
return RepositoryResponse.FromProject(project);
}

Expand Down Expand Up @@ -249,10 +233,4 @@ public async Task<bool> BranchExists(
var branches = await RestClient.ExecuteWithErrorHandling<List<GitLabApiClient.Models.Branches.Responses.Branch>>(request);
return branches.Any(x => x.Name == branchNameRequest);
}

private Task<Project> GetProject(int projectId)
{
var request = RestClient.CreateRequest($"/projects/{projectId}", Method.Get);
return RestClient.ExecuteWithErrorHandling<Project>(request);
}
}
11 changes: 9 additions & 2 deletions Apps.GitLab/Apps.GitLab.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
<Nullable>enable</Nullable>
<Product>GitLab</Product>
<Description>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.</Description>
<Version>1.0.14</Version>
<Version>1.0.16</Version>
<AssemblyName>Apps.GitLab</AssemblyName>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Blackbird.Applications.SDK.Blueprints" Version="1.0.2" />
<PackageReference Include="Blackbird.Applications.SDK.Extensions.FileManagement" Version="1.1.0" />
<PackageReference Include="Blackbird.Applications.Sdk.Utils" Version="1.0.26" />
<PackageReference Include="Blackbird.Filters" Version="1.2.9" />
<PackageReference Include="GitLabApiClient" Version="1.8.0" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.4" />
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="8.0.0" />
Expand All @@ -25,6 +26,12 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource CopyToOutputDirectory="Always" Include="image\icon.jpg" />
<EmbeddedResource CopyToOutputDirectory="Always" Include="..\README.md" Link="README.md" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="..\README.md">
<Link>README.md</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
</Project>
Loading