diff --git a/Apps.GitLab/Actions/Base/GitLabActions.cs b/Apps.GitLab/Actions/Base/GitLabActions.cs index 62276b9..83c1730 100644 --- a/Apps.GitLab/Actions/Base/GitLabActions.cs +++ b/Apps.GitLab/Actions/Base/GitLabActions.cs @@ -1,7 +1,7 @@ -using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Authentication; using Blackbird.Applications.Sdk.Common.Invocation; -using GitLabApiClient; +using Apps.GitLab.Utils; namespace Apps.Gitlab.Actions.Base; @@ -9,14 +9,14 @@ public class GitLabActions : BaseInvocable { protected IEnumerable Creds => InvocationContext.AuthenticationCredentialsProviders; - - private BlackbirdGitlabClient GitLabClient { get; set; } - protected GitLabClient Client => GitLabClient.Client; - protected BlackbirdGitlabClient RestClient => GitLabClient; + protected BlackbirdGitlabClient RestClient { get; } public GitLabActions(InvocationContext invocationContext) : base(invocationContext) { - GitLabClient = new(Creds); + RestClient = new(Creds); } -} \ No newline at end of file + + protected static int ParseProjectId(string repositoryId) + => ParsingUtils.ParseIntOrThrow(repositoryId, "Repository ID"); +} diff --git a/Apps.GitLab/Actions/BranchActions.cs b/Apps.GitLab/Actions/BranchActions.cs index 7aa090e..1ddd2f3 100644 --- a/Apps.GitLab/Actions/BranchActions.cs +++ b/Apps.GitLab/Actions/BranchActions.cs @@ -1,34 +1,29 @@ -using Apps.Gitlab.Actions.Base; +using Apps.Gitlab.Actions.Base; using Apps.Gitlab.Dtos; using Apps.Gitlab.Models.Branch.Requests; using Apps.Gitlab.Models.Branch.Responses; using Apps.Gitlab.Models.Respository.Requests; -using Apps.GitLab.Utils; using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Actions; using Blackbird.Applications.Sdk.Common.Invocation; using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces; -using GitLabApiClient.Internal.Paths; +using GitLabApiClient.Models.Branches.Responses; +using RestSharp; namespace Apps.Gitlab.Actions; [ActionList("Branch")] -public class BranchActions : GitLabActions +public class BranchActions(InvocationContext invocationContext) + : GitLabActions(invocationContext) { - private readonly IFileManagementClient _fileManagementClient; - - public BranchActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) - : base(invocationContext) - { - _fileManagementClient = fileManagementClient; - } [Action("List branches", Description = "List respository branches")] public async Task ListRepositoryBranches([ActionParameter] GetRepositoryRequest input) { - var projectId = (ProjectId)int.Parse(input.RepositoryId); - var branches = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - Client.Branches.GetAsync(projectId, _ => { })); + var projectId = ParseProjectId(input.RepositoryId); + var request = RestClient.CreateRequest($"/projects/{projectId}/repository/branches", Method.Get); + var branches = await RestClient.ExecuteWithErrorHandling>(request); + return new ListRepositoryBranchesResponse { Branches = branches.Select(b => new BranchDto(b)) @@ -40,10 +35,11 @@ public async Task GetBranch( [ActionParameter] GetRepositoryRequest repositoryRequest, [ActionParameter] GetBranchRequest input) { - var projectId = (ProjectId)int.Parse(repositoryRequest.RepositoryId); - - var branch = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - Client.Branches.GetAsync(projectId, input.Name)); + var projectId = ParseProjectId(repositoryRequest.RepositoryId); + var request = RestClient.CreateRequest( + $"/projects/{projectId}/repository/branches/{Uri.EscapeDataString(input.Name)}", + Method.Get); + var branch = await RestClient.ExecuteWithErrorHandling(request); return new BranchDto(branch); } @@ -53,14 +49,12 @@ public async Task CreateBranch( [ActionParameter] GetRepositoryRequest repositoryRequest, [ActionParameter] Models.Branch.Requests.CreateBranchRequest input) { - var projectId = (ProjectId)int.Parse(repositoryRequest.RepositoryId); - - var request = new GitLabApiClient.Models.Branches.Requests.CreateBranchRequest( - input.NewBranchName, - input.BaseBranchName); + var projectId = ParseProjectId(repositoryRequest.RepositoryId); + var request = RestClient.CreateRequest($"/projects/{projectId}/repository/branches", Method.Post); + request.AddParameter("branch", input.NewBranchName); + request.AddParameter("ref", input.BaseBranchName); - var branch = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - Client.Branches.CreateAsync(projectId, request)); + var branch = await RestClient.ExecuteWithErrorHandling(request); return new BranchDto(branch); } -} \ No newline at end of file +} diff --git a/Apps.GitLab/Actions/CommitActions.cs b/Apps.GitLab/Actions/CommitActions.cs index 677ecb0..d693816 100644 --- a/Apps.GitLab/Actions/CommitActions.cs +++ b/Apps.GitLab/Actions/CommitActions.cs @@ -1,21 +1,21 @@ -using Apps.Gitlab.Actions.Base; +using Apps.Gitlab.Actions.Base; 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.Models.Commit.Requests; using Apps.GitLab.Models.Commit.Responses; using Apps.GitLab.Models.Respository.Requests; -using Apps.GitLab.Utils; 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 GitLabApiClient.Internal.Paths; using GitLabApiClient.Models.Commits.Responses; +using RestSharp; namespace Apps.Gitlab.Actions; @@ -35,13 +35,13 @@ public async Task ListRepositoryCommits( [ActionParameter] GetRepositoryRequest repositoryRequest, [ActionParameter] GetOptionalBranchRequest branchRequest) { - var projectId = (ProjectId)int.Parse(repositoryRequest.RepositoryId); - var commits = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - Client.Commits.GetAsync(projectId, options => - { - if (!string.IsNullOrWhiteSpace(branchRequest.Name)) - options.RefName = branchRequest.Name; - })); + var projectId = ParseProjectId(repositoryRequest.RepositoryId); + var request = RestClient.CreateRequest($"/projects/{projectId}/repository/commits", Method.Get); + + if (!string.IsNullOrWhiteSpace(branchRequest.Name)) + request.AddQueryParameter("ref_name", branchRequest.Name); + + var commits = await RestClient.ExecuteWithErrorHandling>(request); return new() { Commits = commits @@ -53,10 +53,12 @@ public async Task GetCommit( [ActionParameter] GetRepositoryRequest repositoryRequest, [ActionParameter] GetCommitRequest input) { - var projectId = (ProjectId)int.Parse(repositoryRequest.RepositoryId); - var commit = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - Client.Commits.GetAsync(projectId, input.CommitId)); - return commit; + var projectId = ParseProjectId(repositoryRequest.RepositoryId); + var request = RestClient.CreateRequest( + $"/projects/{projectId}/repository/commits/{Uri.EscapeDataString(input.CommitId)}", + Method.Get); + + return await RestClient.ExecuteWithErrorHandling(request); } [Action("List added or modified files in X hours", Description = "List added or modified files in X hours")] @@ -68,21 +70,23 @@ public async Task ListAddedOrModifiedInHours { if (hoursRequest.Hours <= 0) throw new ArgumentException("Specify more than 0 hours!"); - var projectId = (ProjectId)int.Parse(repositoryRequest.RepositoryId); - - var commits = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - Client.Commits.GetAsync(projectId, options => - { - options.Since = DateTime.Now.AddHours(-hoursRequest.Hours); - if (!string.IsNullOrWhiteSpace(branchRequest.Name)) - options.RefName = branchRequest.Name; - })); + + var projectId = ParseProjectId(repositoryRequest.RepositoryId); + var request = RestClient.CreateRequest($"/projects/{projectId}/repository/commits", Method.Get); + request.AddQueryParameter("since", DateTime.Now.AddHours(-hoursRequest.Hours).ToString("O")); + + if (!string.IsNullOrWhiteSpace(branchRequest.Name)) + request.AddQueryParameter("ref_name", branchRequest.Name); + + var commits = await RestClient.ExecuteWithErrorHandling>(request); var files = new List(); - foreach (var c in commits) + foreach (var commit in commits) { - var diffs = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - Client.Commits.GetDiffsAsync(projectId, c.Id)); + var diffRequest = RestClient.CreateRequest( + $"/projects/{projectId}/repository/commits/{Uri.EscapeDataString(commit.Id)}/diff", + Method.Get); + var diffs = await RestClient.ExecuteWithErrorHandling>(diffRequest); files.AddRange( diffs @@ -92,7 +96,11 @@ public async Task ListAddedOrModifiedInHours PushWebhooks.IsFilePathMatchingPattern(folderInput.FolderPath, f.NewPath)) .Select(x => new AddedOrModifiedFile(x))); } - return new ListAddedOrModifiedInHoursResponse() { Files = files.DistinctBy(x => x.Filename).ToList() }; + + return new ListAddedOrModifiedInHoursResponse + { + Files = files.DistinctBy(x => x.Filename).ToList() + }; } [Action("Create or update file", Description = "Create or update file")] @@ -101,18 +109,18 @@ public async Task PushFile( [ActionParameter] GetOptionalBranchRequest branchRequest, [ActionParameter] PushFileRequest input) { - var projectId = (ProjectId)int.Parse(repositoryRequest.RepositoryId); + 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 == "blob") - .Any(p => p.Path == input.DestinationFilePath)) // update in case of existing file + 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() + new PushFileRequest { DestinationFilePath = input.DestinationFilePath, File = input.File, @@ -120,15 +128,16 @@ public async Task PushFile( }); } - if (repContent.Content.Where(x => x.Type == "tree").Select(x => x.Path) + if (repContent.Content.Where(x => x.Type == GitLabItemTypes.Tree).Select(x => x.Path) .Contains(input.DestinationFilePath.Trim('/'))) - throw new PluginApplicationException("Destination file path is invalid!"); + { + throw new PluginMisconfigurationException("Destination file path is invalid!"); + } var file = await _fileManagementClient.DownloadAsync(input.File); var fileBytes = await file.GetByteData(); - var pushFileResult = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - RestClient.PushChanges(projectId, branchRequest.Name, input.CommitMessage, - input.DestinationFilePath, fileBytes, "create")); + var pushFileResult = await RestClient.PushChanges(projectId, branchRequest.Name, input.CommitMessage, + input.DestinationFilePath, fileBytes, GitLabCommitActions.Create); return new(pushFileResult); } @@ -139,20 +148,21 @@ public async Task UpdateFile( [ActionParameter] GetOptionalBranchRequest branchRequest, [ActionParameter] PushFileRequest input) { - var projectId = (ProjectId)int.Parse(repositoryRequest.RepositoryId); + var projectId = ParseProjectId(repositoryRequest.RepositoryId); var repContent = await new RepositoryActions(InvocationContext, _fileManagementClient).ListRepositoryContent( repositoryRequest, branchRequest, - new FolderContentWithTypeRequest() { IncludeSubfolders = true, ContentType = "blob" }); + 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 ErrorHandler.ExecuteWithErrorHandlingAsync(() => - RestClient.PushChanges(projectId, branchRequest.Name, input.CommitMessage, - input.DestinationFilePath, fileBytes, "update")); + var fileUpload = await RestClient.PushChanges(projectId, branchRequest.Name, input.CommitMessage, + input.DestinationFilePath, fileBytes, GitLabCommitActions.Update); + return new(fileUpload); } @@ -162,11 +172,10 @@ public async Task DeleteFile( [ActionParameter] GetOptionalBranchRequest branchRequest, [ActionParameter] DeleteFileRequest input) { - var projectId = (ProjectId)int.Parse(repositoryRequest.RepositoryId); + var projectId = ParseProjectId(repositoryRequest.RepositoryId); + var fileDelete = await RestClient.PushChanges(projectId, branchRequest.Name, input.CommitMessage, + input.FilePath, null, GitLabCommitActions.Delete); - var fileDelete = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - RestClient.PushChanges(projectId, branchRequest.Name, input.CommitMessage, - input.FilePath, null, "delete")); return new() { CommitId = fileDelete.Id, @@ -174,4 +183,4 @@ public async Task DeleteFile( Message = fileDelete.Message }; } -} \ No newline at end of file +} diff --git a/Apps.GitLab/Actions/PullRequestActions.cs b/Apps.GitLab/Actions/PullRequestActions.cs index b28a289..4fa3530 100644 --- a/Apps.GitLab/Actions/PullRequestActions.cs +++ b/Apps.GitLab/Actions/PullRequestActions.cs @@ -1,36 +1,29 @@ -using Apps.Gitlab.Actions.Base; +using Apps.Gitlab.Actions.Base; using Apps.Gitlab.Models.PullRequest.Requests; using Apps.Gitlab.Models.PullRequest.Responses; using Apps.Gitlab.Models.Respository.Requests; -using Apps.GitLab.Utils; using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Actions; using Blackbird.Applications.Sdk.Common.Invocation; -using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces; -using GitLabApiClient.Internal.Paths; -using GitLabApiClient.Models.MergeRequests.Requests; +using Apps.GitLab.Utils; using GitLabApiClient.Models.MergeRequests.Responses; +using RestSharp; namespace Apps.Gitlab.Actions; [ActionList("Pull request")] -public class PullRequestActions : GitLabActions +public class PullRequestActions(InvocationContext invocationContext) + : GitLabActions(invocationContext) { - private readonly IFileManagementClient _fileManagementClient; - - public PullRequestActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) - : base(invocationContext) - { - _fileManagementClient = fileManagementClient; - } [Action("List merge requests", Description = "List merge requests")] public async Task ListPullRequests( [ActionParameter] GetRepositoryRequest repositoryRequest) { - var projectId = (ProjectId)int.Parse(repositoryRequest.RepositoryId); - var pulls = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - Client.MergeRequests.GetAsync(projectId, _ => { })); + var projectId = ParseProjectId(repositoryRequest.RepositoryId); + var request = RestClient.CreateRequest($"/projects/{projectId}/merge_requests", Method.Get); + var pulls = await RestClient.ExecuteWithErrorHandling>(request); + return new ListPullRequestsResponse { PullRequests = pulls @@ -42,10 +35,13 @@ public async Task GetPullRequest( [ActionParameter] GetRepositoryRequest repositoryRequest, [ActionParameter] GetPullRequest input) { - var projectId = (ProjectId)int.Parse(repositoryRequest.RepositoryId); - var pull = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - Client.MergeRequests.GetAsync(projectId, int.Parse(input.PullRequestId))); - return pull; + var projectId = ParseProjectId(repositoryRequest.RepositoryId); + var mergeRequestId = ParsingUtils.ParseIntOrThrow(input.PullRequestId, "Pull request ID"); + var request = RestClient.CreateRequest( + $"/projects/{projectId}/merge_requests/{mergeRequestId}", + Method.Get); + + return await RestClient.ExecuteWithErrorHandling(request); } [Action("Create merge request", Description = "Create merge request")] @@ -53,16 +49,17 @@ public async Task CreatePullRequest( [ActionParameter] GetRepositoryRequest repositoryRequest, [ActionParameter] CreatePullRequest input) { - var projectId = (ProjectId)int.Parse(repositoryRequest.RepositoryId); - var request = new CreateMergeRequest(input.BaseBranch, input.HeadBranch, input.Title) + var projectId = ParseProjectId(repositoryRequest.RepositoryId); + var request = RestClient.CreateRequest($"/projects/{projectId}/merge_requests", Method.Post); + request.AddJsonBody(new { - Description = input.Description - }; + source_branch = input.HeadBranch, + target_branch = input.BaseBranch, + title = input.Title, + description = input.Description + }); - var pull = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - Client.MergeRequests.CreateAsync(projectId, request)); - - return pull; + return await RestClient.ExecuteWithErrorHandling(request); } [Action("Complete merge request", Description = "Complete merge request")] @@ -71,13 +68,16 @@ public async Task MergePullRequest( [ActionParameter] GetPullRequest mergeRequest, [ActionParameter] MergePullRequest input) { - var projectId = (ProjectId)int.Parse(repositoryRequest.RepositoryId); - var acceptRequest = new AcceptMergeRequest + var projectId = ParseProjectId(repositoryRequest.RepositoryId); + var mergeRequestId = ParsingUtils.ParseIntOrThrow(mergeRequest.PullRequestId, "Pull request ID"); + var request = RestClient.CreateRequest( + $"/projects/{projectId}/merge_requests/{mergeRequestId}/merge", + Method.Put); + request.AddJsonBody(new { - MergeCommitMessage = input.MergeCommitMessage - }; + merge_commit_message = input.MergeCommitMessage + }); - return await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - Client.MergeRequests.AcceptAsync(projectId, int.Parse(mergeRequest.PullRequestId), acceptRequest)); + return await RestClient.ExecuteWithErrorHandling(request); } -} \ No newline at end of file +} diff --git a/Apps.GitLab/Actions/RepositoryActions.cs b/Apps.GitLab/Actions/RepositoryActions.cs index ef37ce9..c34720a 100644 --- a/Apps.GitLab/Actions/RepositoryActions.cs +++ b/Apps.GitLab/Actions/RepositoryActions.cs @@ -1,4 +1,4 @@ -using Apps.Gitlab.Actions.Base; +using Apps.Gitlab.Actions.Base; using Apps.Gitlab.Constants; using Apps.Gitlab.Dtos; using Apps.Gitlab.Models.Branch.Requests; @@ -7,7 +7,7 @@ using Apps.Gitlab.Models.Respository.Responses; using Apps.GitLab; using Apps.GitLab.Models.Respository.Requests; -using Apps.GitLab.Utils; +using Apps.GitLab.Models.Respository.Responses; using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Actions; using Blackbird.Applications.Sdk.Common.Exceptions; @@ -17,8 +17,8 @@ using Blackbird.Applications.Sdk.Utils.Extensions.Http; using Blackbird.Applications.Sdk.Utils.Models; using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces; -using GitLabApiClient.Internal.Paths; using GitLabApiClient.Models.Projects.Responses; +using GitLabApiClient.Models.Trees.Responses; using RestSharp; using System.Net.Mime; @@ -38,7 +38,7 @@ public RepositoryActions(InvocationContext invocationContext, IFileManagementCli [Action("Create new repository", Description = "Create new repository")] public Task CreateRepository([ActionParameter] CreateRepositoryInput input) { - var endpoint = "/api/v4/projects"; + var endpoint = "/projects"; if (input.UserId != null) endpoint += $"/user/{input.UserId}"; @@ -55,25 +55,27 @@ public async Task GetFile( [ActionParameter] GetOptionalBranchRequest branchRequest, [ActionParameter] GetFileRequest getFileRequest) { - var projectId = (ProjectId)int.Parse(repositoryRequest.RepositoryId); - var repository = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => Client.Projects.GetAsync(projectId)); - + var projectId = ParseProjectId(repositoryRequest.RepositoryId); + var repository = await GetProject(projectId); var branch = branchRequest.Name ?? repository.DefaultBranch; - var fileData = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - Client.Files.GetAsync(projectId, getFileRequest.FilePath, branch)); + var request = RestClient.CreateRequest( + $"/projects/{projectId}/repository/files/{Uri.EscapeDataString(getFileRequest.FilePath)}", + Method.Get); + request.AddQueryParameter("ref", branch); + + var fileData = await RestClient.ExecuteWithErrorHandling(request); if (fileData == null) - throw new ArgumentException($"File does not exist ({getFileRequest.FilePath})"); + 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; - File.WriteAllBytes("test", Convert.FromBase64String(fileData.Content)); using (var stream = new MemoryStream(Convert.FromBase64String(fileData.Content))) { - file = _fileManagementClient.UploadAsync(stream, mimeType, filename).Result; + file = await _fileManagementClient.UploadAsync(stream, mimeType, filename); } return new GetFileResponse @@ -90,32 +92,32 @@ public async Task GetAllFilesInFolder( [ActionParameter] GetOptionalBranchRequest branchRequest, [ActionParameter] FolderContentRequest folderContentRequest) { - var projectId = (ProjectId)int.Parse(repositoryRequest.RepositoryId); + var projectId = ParseProjectId(repositoryRequest.RepositoryId); var resultFiles = new List(); - var content = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - RestClient.GetArchive(projectId, branchRequest.Name)); - if (content == null || content.Length == 0) - { - throw new PluginApplicationException("Repository is empty!"); - } + var content = await RestClient.GetArchive(projectId, branchRequest.Name); + if (content.Length == 0) + throw new PluginMisconfigurationException("Repository is empty!"); - var filesFromZip = new List(); + List filesFromZip; using (var stream = new MemoryStream(content)) { filesFromZip = (await stream.GetFilesFromZip()).ToList(); } - var includeSubFolders = folderContentRequest.IncludeSubfolders.HasValue && folderContentRequest.IncludeSubfolders.Value; + + var includeSubFolders = folderContentRequest.IncludeSubfolders.GetValueOrDefault(); foreach (var file in filesFromZip) { file.Path = file.Path.Substring(file.Path.IndexOf('/') + 1); if (file.FileStream.Length == 0) - { continue; - } - else if (!string.IsNullOrEmpty(folderContentRequest.Path)) + + if (!string.IsNullOrEmpty(folderContentRequest.Path)) { + var normalizedFolderPath = folderContentRequest.Path.Trim('/'); + var normalizedDirectory = Path.GetDirectoryName(file.Path)?.TrimStart('\\').Replace('\\', '/'); + if ((includeSubFolders && !file.Path.StartsWith(folderContentRequest.Path)) || - (!includeSubFolders && Path.GetDirectoryName(file.Path).TrimStart('\\').Replace('\\', '/') != folderContentRequest.Path.Trim('/'))) + (!includeSubFolders && normalizedDirectory != normalizedFolderPath)) { continue; } @@ -124,34 +126,33 @@ public async Task GetAllFilesInFolder( { continue; } + var filename = Path.GetFileName(file.Path); if (!MimeTypes.TryGetMimeType(filename, out var mimeType)) mimeType = MediaTypeNames.Application.Octet; + var uploadedFile = await _fileManagementClient.UploadAsync(file.FileStream, mimeType, filename); - resultFiles.Add(new GitLabFile() + resultFiles.Add(new GitLabFile { File = uploadedFile, FilePath = file.Path }); } + return new GetRepositoryFilesFromFilepathsResponse { Files = resultFiles }; } [Action("Get repository", Description = "Get repository info")] - public async Task GetRepositoryById([ActionParameter] GetRepositoryRequest input) - { - var projectId = (ProjectId)int.Parse(input.RepositoryId); - var repository = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - Client.Projects.GetAsync(projectId)); - return repository; - } + public Task GetRepositoryById([ActionParameter] GetRepositoryRequest input) + => GetProject(ParseProjectId(input.RepositoryId)); [Action("Get repository issues", Description = "Get opened issues against repository")] public async Task GetIssuesInRepository([ActionParameter] RepositoryRequest input) { - var projectId = (ProjectId)int.Parse(input.RepositoryId); - var issues = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - Client.Issues.GetAllAsync(projectId)); + var projectId = ParseProjectId(input.RepositoryId); + var request = RestClient.CreateRequest($"/projects/{projectId}/issues", Method.Get); + var issues = await RestClient.ExecuteWithErrorHandling>(request); + return new() { Issues = issues.Select(issue => new IssueDto(issue)) @@ -161,9 +162,10 @@ public async Task GetIssuesInRepository([ActionParameter] Rep [Action("Get repository merge requests", Description = "Get opened merge requests in a repository")] public async Task GetPullRequestsInRepository([ActionParameter] RepositoryRequest input) { - var projectId = (ProjectId)int.Parse(input.RepositoryId); - var pullRequests = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - Client.MergeRequests.GetAsync(projectId, _ => { })); + var projectId = ParseProjectId(input.RepositoryId); + var request = RestClient.CreateRequest($"/projects/{projectId}/merge_requests", Method.Get); + var pullRequests = await RestClient.ExecuteWithErrorHandling>(request); + return new() { PullRequests = pullRequests.Select(p => new PullRequestDto(p)) @@ -176,17 +178,18 @@ public async Task ListRepositoryContent( [ActionParameter] GetOptionalBranchRequest branchRequest, [ActionParameter] FolderContentWithTypeRequest input) { - var projectId = (ProjectId)int.Parse(repositoryRequest.RepositoryId); - var tree = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - Client.Trees.GetAsync(projectId, options => - { - options.Recursive = input.IncludeSubfolders ?? false; - options.Path = input.Path ?? "/"; - if (!string.IsNullOrWhiteSpace(branchRequest.Name)) - options.Reference = branchRequest.Name; - })); + var projectId = ParseProjectId(repositoryRequest.RepositoryId); + var request = RestClient.CreateRequest($"/projects/{projectId}/repository/tree", Method.Get); + request.AddQueryParameter("recursive", (input.IncludeSubfolders ?? false).ToString().ToLowerInvariant()); + request.AddQueryParameter("path", input.Path ?? "/"); + + if (!string.IsNullOrWhiteSpace(branchRequest.Name)) + request.AddQueryParameter("ref", branchRequest.Name); + + var tree = await RestClient.ExecuteWithErrorHandling>(request); if (!string.IsNullOrEmpty(input.ContentType)) tree = tree.Where(x => input.ContentType.Split(' ').Contains(x.Type)).ToList(); + return new() { Content = tree @@ -196,8 +199,10 @@ public async Task ListRepositoryContent( [Action("List repositories", Description = "List all repositories")] public async Task ListRepositories() { - var projects = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - Client.Projects.GetAsync(options => { options.IsMemberOf = true; })); + var request = RestClient.CreateRequest("/projects", Method.Get); + request.AddQueryParameter("membership", "true"); + + var projects = await RestClient.ExecuteWithErrorHandling>(request); return new(projects.ToArray()); } @@ -207,7 +212,6 @@ public async Task GetRepositoryFilesFro [ActionParameter] GetOptionalBranchRequest branchRequest, [ActionParameter] GetRepositoryFilesFromFilepathsRequest input) { - var files = new List(); foreach (var filePath in input.FilePaths) { @@ -234,10 +238,17 @@ public async Task BranchExists( [ActionParameter] GetRepositoryRequest repositoryRequest, [ActionParameter][Display("Branch name")] string branchNameRequest) { - var projectId = (ProjectId)int.Parse(repositoryRequest.RepositoryId); - var branches = await ErrorHandler.ExecuteWithErrorHandlingAsync(() => - Client.Branches.GetAsync(projectId, options => { options.Search = branchNameRequest; })); + var projectId = ParseProjectId(repositoryRequest.RepositoryId); + var request = RestClient.CreateRequest($"/projects/{projectId}/repository/branches", Method.Get); + request.AddQueryParameter("search", branchNameRequest); + var branches = await RestClient.ExecuteWithErrorHandling>(request); return branches.Any(x => x.Name == branchNameRequest); } -} \ No newline at end of file + + 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 966969e..90edc59 100644 --- a/Apps.GitLab/Apps.GitLab.csproj +++ b/Apps.GitLab/Apps.GitLab.csproj @@ -6,14 +6,14 @@ enable GitLab Software development & version control - 1.0.10 + 1.0.11 Apps.GitLab - - - + + + @@ -21,6 +21,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Apps.GitLab/Auth/OAuth2/OAuth2AuthorizeService.cs b/Apps.GitLab/Auth/OAuth2/OAuth2AuthorizeService.cs index d437f4d..e7e48fe 100644 --- a/Apps.GitLab/Auth/OAuth2/OAuth2AuthorizeService.cs +++ b/Apps.GitLab/Auth/OAuth2/OAuth2AuthorizeService.cs @@ -49,7 +49,7 @@ private static string GetAuthorizationEndpoint(Dictionary values return connectionType switch { ConnectionTypes.OAuth => "https://gitlab.com/oauth/authorize", - ConnectionTypes.OAuthSelfManaged => $"{values[CredNames.BaseUrl].TrimEnd('/')}/oauth/authorize", + ConnectionTypes.OAuthSelfManaged => $"{OAuth2UrlHelper.GetSelfManagedBaseUrl(values)}/oauth/authorize", _ => throw new Exception($"Unsupported connection type for OAuth authorization: {connectionType}") }; } @@ -63,4 +63,4 @@ private static string GetClientId(Dictionary values, string conn _ => throw new Exception($"Unsupported connection type for OAuth authorization: {connectionType}") }; } -} \ No newline at end of file +} diff --git a/Apps.GitLab/Auth/OAuth2/OAuth2TokenService.cs b/Apps.GitLab/Auth/OAuth2/OAuth2TokenService.cs index 8dec154..4f82437 100644 --- a/Apps.GitLab/Auth/OAuth2/OAuth2TokenService.cs +++ b/Apps.GitLab/Auth/OAuth2/OAuth2TokenService.cs @@ -131,7 +131,7 @@ private static string GetTokenEndpoint(Dictionary values, string return connectionType switch { ConnectionTypes.OAuth => "https://gitlab.com/oauth/token", - ConnectionTypes.OAuthSelfManaged => $"{values[CredNames.BaseUrl].TrimEnd('/')}/oauth/token", + ConnectionTypes.OAuthSelfManaged => $"{OAuth2UrlHelper.GetSelfManagedBaseUrl(values)}/oauth/token", _ => throw new Exception($"Unsupported connection type for OAuth token exchange: {connectionType}") }; } @@ -155,4 +155,4 @@ private static string GetClientSecret(Dictionary values, string _ => throw new Exception($"Unsupported connection type for OAuth client secret: {connectionType}") }; } -} \ No newline at end of file +} diff --git a/Apps.GitLab/Auth/OAuth2/OAuth2UrlHelper.cs b/Apps.GitLab/Auth/OAuth2/OAuth2UrlHelper.cs new file mode 100644 index 0000000..45c6e48 --- /dev/null +++ b/Apps.GitLab/Auth/OAuth2/OAuth2UrlHelper.cs @@ -0,0 +1,9 @@ +using Apps.GitLab.Constants; + +namespace Apps.Gitlab.Auth.OAuth2; + +internal static class OAuth2UrlHelper +{ + public static string GetSelfManagedBaseUrl(Dictionary values) + => values[CredNames.BaseUrl].TrimEnd('/'); +} diff --git a/Apps.GitLab/BlackbirdGitLabClient.cs b/Apps.GitLab/BlackbirdGitLabClient.cs index b66d242..72a3264 100644 --- a/Apps.GitLab/BlackbirdGitLabClient.cs +++ b/Apps.GitLab/BlackbirdGitLabClient.cs @@ -1,24 +1,25 @@ -using Apps.GitLab.Constants; +using Apps.GitLab.Constants; using Apps.GitLab.Dtos; +using Apps.Gitlab.Constants; 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; -using GitLabApiClient.Internal.Paths; using GitLabApiClient.Models.Commits.Responses; +using Newtonsoft.Json; using RestSharp; namespace Apps.Gitlab; public class BlackbirdGitlabClient : BlackBirdRestClient { + private const string ApiPrefix = "/api/v4"; private readonly IEnumerable _authenticationCredentials; - private readonly string _baseUrl; - public readonly GitLabClient _client; - public GitLabClient Client => _client; + protected override JsonSerializerSettings? JsonSettings => JsonConfig.JsonSettings; + + public string BaseUrl { get; } public BlackbirdGitlabClient(IEnumerable authenticationCredentialsProviders) : base(new() @@ -27,13 +28,10 @@ public BlackbirdGitlabClient(IEnumerable auth }) { _authenticationCredentials = authenticationCredentialsProviders; - _baseUrl = GetBaseUrl(authenticationCredentialsProviders); - - var apiToken = authenticationCredentialsProviders.Get(CredNames.Authorization).Value; - _client = new GitLabClient(_baseUrl, apiToken); + BaseUrl = GetBaseUrl(authenticationCredentialsProviders); } - private static string GetBaseUrl(IEnumerable creds) + public static string GetBaseUrl(IEnumerable creds) { var connectionType = creds.Get(CredNames.ConnectionType).Value; @@ -46,27 +44,31 @@ private static string GetBaseUrl(IEnumerable }; } - public async Task GetArchive(ProjectId projectId, string? branchName) + public GitLabRequest CreateRequest(string resource, Method method) + => new(NormalizeApiResource(resource), method, _authenticationCredentials); + + public async Task ExecuteForBytesWithErrorHandling(RestRequest request) { - var branchCommit = !string.IsNullOrWhiteSpace(branchName) ? $"?sha={branchName}" : ""; - var request = new RestRequest($"/api/v4/projects/{projectId}/repository/archive.zip{branchCommit}", Method.Get); - request.AddHeader("Authorization", $"Bearer {_authenticationCredentials.Get(CredNames.Authorization).Value}"); + var response = await ExecuteWithErrorHandling(request); - var result = await new RestClient(_baseUrl).ExecuteAsync(request); + return response.RawBytes ?? []; + } - if (!result.IsSuccessStatusCode) - throw ConfigureErrorException(result); + public async Task GetArchive(int projectId, string? branchName) + { + var branchCommit = !string.IsNullOrWhiteSpace(branchName) ? $"?sha={Uri.EscapeDataString(branchName)}" : ""; + var request = CreateRequest($"/projects/{projectId}/repository/archive.zip{branchCommit}", Method.Get); - return result.RawBytes; + return await ExecuteForBytesWithErrorHandling(request); } - public async Task PushChanges(ProjectId projectId, string? branchName, string commitMessage, - string filePath, byte[] file, string action) + public async Task PushChanges(int projectId, string? branchName, string commitMessage, + string filePath, byte[]? file, string action) { - var repository = await Client.Projects.GetAsync(projectId); + var repository = await ExecuteWithErrorHandling( + CreateRequest($"/projects/{projectId}", Method.Get)); - var request = new RestRequest($"/api/v4/projects/{projectId}/repository/commits", Method.Post); - request.AddHeader("Authorization", $"Bearer {_authenticationCredentials.Get(CredNames.Authorization).Value}"); + var request = CreateRequest($"/projects/{projectId}/repository/commits", Method.Post); request.AddJsonBody(new { branch = branchName ?? repository.DefaultBranch, @@ -77,16 +79,29 @@ public async Task PushChanges(ProjectId projectId, string? branchName, s } }); - var result = await new RestClient(_baseUrl).ExecuteAsync(request); + return await ExecuteWithErrorHandling(request); + } - if (!result.IsSuccessStatusCode) - throw ConfigureErrorException(result); + protected override Exception ConfigureErrorException(RestResponse response) + { + var message = $"{(int)response.StatusCode}: {response.ErrorMessage ?? response.Content}"; - return result.Data; + return response.StatusCode switch + { + System.Net.HttpStatusCode.Unauthorized or System.Net.HttpStatusCode.Forbidden => + new PluginMisconfigurationException(message), + _ => new PluginApplicationException(message) + }; } - protected override Exception ConfigureErrorException(RestResponse response) + private static string NormalizeApiResource(string resource) { - return new PluginApplicationException(response.Content); + if (string.IsNullOrWhiteSpace(resource)) + return ApiPrefix; + + if (resource.StartsWith(ApiPrefix, StringComparison.OrdinalIgnoreCase)) + return resource; + + return $"{ApiPrefix}{resource}"; } -} \ No newline at end of file +} diff --git a/Apps.GitLab/Connections/ConnectionDefinition.cs b/Apps.GitLab/Connections/ConnectionDefinition.cs index 427e367..3d24685 100644 --- a/Apps.GitLab/Connections/ConnectionDefinition.cs +++ b/Apps.GitLab/Connections/ConnectionDefinition.cs @@ -53,15 +53,22 @@ var ct when ConnectionTypes.SupportedConnectionTypes.Contains(ct) => ct, providers.Add(new AuthenticationCredentialsProvider(CredNames.ConnectionType, connectionType)); - if (values.TryGetValue("access_token", out var accessToken)) - { - providers.Add(new AuthenticationCredentialsProvider(CredNames.Authorization, accessToken)); - } - else if (values.TryGetValue(CredNames.ApiKey, out var apiKey)) + var authorizationToken = GetAuthorizationToken(values); + if (!string.IsNullOrWhiteSpace(authorizationToken)) { - providers.Add(new AuthenticationCredentialsProvider(CredNames.Authorization, apiKey)); + providers.Add(new AuthenticationCredentialsProvider(CredNames.Authorization, authorizationToken)); } return providers; } -} \ No newline at end of file + + private static string? GetAuthorizationToken(Dictionary values) + { + if (values.TryGetValue("access_token", out var accessToken)) + return accessToken; + + return values.TryGetValue(CredNames.ApiKey, out var apiKey) + ? apiKey + : null; + } +} diff --git a/Apps.GitLab/Connections/ConnectionValidator.cs b/Apps.GitLab/Connections/ConnectionValidator.cs index 27baec4..c6825c3 100644 --- a/Apps.GitLab/Connections/ConnectionValidator.cs +++ b/Apps.GitLab/Connections/ConnectionValidator.cs @@ -1,5 +1,7 @@ -using Blackbird.Applications.Sdk.Common.Authentication; +using Blackbird.Applications.Sdk.Common.Authentication; using Blackbird.Applications.Sdk.Common.Connections; +using Blackbird.Applications.Sdk.Common.Exceptions; +using RestSharp; namespace Apps.Gitlab.Connections; @@ -10,20 +12,28 @@ public async ValueTask ValidateConnection( { try { - await new BlackbirdGitlabClient(authProviders).Client.Projects.GetAsync((options) => { options.IsMemberOf = true; }); - + var client = new BlackbirdGitlabClient(authProviders); + await client.ExecuteWithErrorHandling(client.CreateRequest("/user", Method.Get)); + } + catch (PluginMisconfigurationException ex) + { return new() { - IsValid = true + IsValid = false, + Message = ex.Message }; } - catch (Exception ex) + catch (Exception) { return new() { - IsValid = false, - Message = ex.Message + IsValid = true }; } + + return new() + { + IsValid = true + }; } -} \ No newline at end of file +} diff --git a/Apps.GitLab/Constants/GitLabCommitActions.cs b/Apps.GitLab/Constants/GitLabCommitActions.cs new file mode 100644 index 0000000..1dadd0b --- /dev/null +++ b/Apps.GitLab/Constants/GitLabCommitActions.cs @@ -0,0 +1,8 @@ +namespace Apps.GitLab.Constants; + +public static class GitLabCommitActions +{ + public const string Create = "create"; + public const string Update = "update"; + public const string Delete = "delete"; +} diff --git a/Apps.GitLab/Constants/GitLabItemTypes.cs b/Apps.GitLab/Constants/GitLabItemTypes.cs new file mode 100644 index 0000000..79e547a --- /dev/null +++ b/Apps.GitLab/Constants/GitLabItemTypes.cs @@ -0,0 +1,7 @@ +namespace Apps.GitLab.Constants; + +public static class GitLabItemTypes +{ + public const string Blob = "blob"; + public const string Tree = "tree"; +} diff --git a/Apps.GitLab/DataSourceHandlers/BranchDataHandler.cs b/Apps.GitLab/DataSourceHandlers/BranchDataHandler.cs index 995f021..084746f 100644 --- a/Apps.GitLab/DataSourceHandlers/BranchDataHandler.cs +++ b/Apps.GitLab/DataSourceHandlers/BranchDataHandler.cs @@ -1,9 +1,12 @@ -using Apps.Gitlab.Models.Respository.Requests; +using Apps.Gitlab.Models.Respository.Requests; using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Authentication; using Blackbird.Applications.Sdk.Common.Dynamic; using Blackbird.Applications.Sdk.Common.Invocation; -using GitLabApiClient.Internal.Paths; +using Apps.GitLab.Utils; +using GitLabApiClient.Models.Branches.Responses; +using RestSharp; +using Blackbird.Applications.Sdk.Common.Exceptions; namespace Apps.Gitlab.DataSourceHandlers; @@ -24,9 +27,16 @@ public async Task> GetDataAsync( CancellationToken cancellationToken) { if (RepositoryRequest == null || string.IsNullOrWhiteSpace(RepositoryRequest.RepositoryId)) - throw new ArgumentException("Please, specify repository first"); - var projectId = (ProjectId)int.Parse(RepositoryRequest.RepositoryId); - var branches = await new BlackbirdGitlabClient(Creds).Client.Branches.GetAsync(projectId, (options) => { }); + throw new PluginMisconfigurationException("Please, specify repository first"); + + var projectId = ParsingUtils.ParseIntOrThrow(RepositoryRequest.RepositoryId, "Repository ID"); + var client = new BlackbirdGitlabClient(Creds); + var request = client.CreateRequest($"/projects/{projectId}/repository/branches", Method.Get); + + if (!string.IsNullOrWhiteSpace(context.SearchString)) + request.AddQueryParameter("search", context.SearchString); + + var branches = await client.ExecuteWithErrorHandling>(request); return branches .Where(x => context.SearchString == null || @@ -34,4 +44,4 @@ public async Task> GetDataAsync( .Take(20) .ToDictionary(x => x.Name, x => x.Name); } -} \ No newline at end of file +} diff --git a/Apps.GitLab/DataSourceHandlers/FilePickerDataHandler.cs b/Apps.GitLab/DataSourceHandlers/FilePickerDataHandler.cs new file mode 100644 index 0000000..76f492d --- /dev/null +++ b/Apps.GitLab/DataSourceHandlers/FilePickerDataHandler.cs @@ -0,0 +1,87 @@ +using Apps.Gitlab.Models.Branch.Requests; +using Apps.Gitlab.Models.Respository.Requests; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Authentication; +using Blackbird.Applications.Sdk.Common.Exceptions; +using Blackbird.Applications.Sdk.Common.Invocation; +using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces; +using Blackbird.Applications.SDK.Extensions.FileManagement.Models.FileDataSourceItems; +using Apps.GitLab.Utils; +using GitLabApiClient.Models.Trees.Responses; +using RestSharp; +using File = Blackbird.Applications.SDK.Extensions.FileManagement.Models.FileDataSourceItems.File; + +namespace Apps.Gitlab.DataSourceHandlers; + +public class FilePickerDataHandler( + InvocationContext invocationContext, + [ActionParameter] GetRepositoryRequest repositoryRequest, + [ActionParameter] GetOptionalBranchRequest branchRequest) + : GitLabDataHandler(invocationContext), IAsyncFileDataSourceItemHandler +{ + + public async Task> GetFolderContentAsync( + FolderContentDataSourceContext context, + CancellationToken cancellationToken) + { + var projectId = GetProjectId(repositoryRequest.RepositoryId); + var folderPath = GitLabPathHelper.NormalizeFolderId(context?.FolderId); + var request = RestClient.CreateRequest($"/projects/{projectId}/repository/tree", Method.Get); + request.AddQueryParameter("path", string.IsNullOrEmpty(folderPath) ? "/" : folderPath); + + if (!string.IsNullOrWhiteSpace(branchRequest.Name)) + request.AddQueryParameter("ref", branchRequest.Name); + + var tree = await RestClient.ExecuteWithErrorHandling>(request); + + return tree + .OrderBy(x => x.Type == "blob") + .ThenBy(x => GetDisplayName(x.Path), StringComparer.OrdinalIgnoreCase) + .Select(x => x.Type == "tree" + ? (FileDataItem)new Folder + { + Id = GitLabPathHelper.NormalizePath(x.Path), + DisplayName = GetDisplayName(x.Path), + IsSelectable = false + } + : new File + { + Id = GitLabPathHelper.NormalizePath(x.Path), + DisplayName = GetDisplayName(x.Path), + IsSelectable = true + }) + .ToList(); + } + + public Task> GetFolderPathAsync( + FolderPathDataSourceContext context, + CancellationToken cancellationToken) + { + var path = new List + { + new() { Id = GitLabPathHelper.RootId, DisplayName = GitLabPathHelper.RootDisplayName } + }; + + var normalizedPath = GitLabPathHelper.NormalizePath(context?.FileDataItemId); + if (string.IsNullOrEmpty(normalizedPath)) + return Task.FromResult>(path); + + var directoryPath = Path.GetDirectoryName(normalizedPath)?.Replace('\\', '/'); + if (string.IsNullOrWhiteSpace(directoryPath)) + return Task.FromResult>(path); + + var currentPath = string.Empty; + foreach (var part in directoryPath.Split('/', StringSplitOptions.RemoveEmptyEntries)) + { + currentPath = string.IsNullOrEmpty(currentPath) ? part : $"{currentPath}/{part}"; + path.Add(new FolderPathItem + { + Id = currentPath, + DisplayName = part + }); + } + + return Task.FromResult>(path); + } + +} diff --git a/Apps.GitLab/DataSourceHandlers/FolderPickerDataHandler.cs b/Apps.GitLab/DataSourceHandlers/FolderPickerDataHandler.cs new file mode 100644 index 0000000..8ac3e43 --- /dev/null +++ b/Apps.GitLab/DataSourceHandlers/FolderPickerDataHandler.cs @@ -0,0 +1,75 @@ +using Apps.Gitlab.Models.Branch.Requests; +using Apps.Gitlab.Models.Respository.Requests; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Authentication; +using Blackbird.Applications.Sdk.Common.Exceptions; +using Blackbird.Applications.Sdk.Common.Invocation; +using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces; +using Blackbird.Applications.SDK.Extensions.FileManagement.Models.FileDataSourceItems; +using Apps.GitLab.Utils; +using GitLabApiClient.Models.Trees.Responses; +using RestSharp; + +namespace Apps.Gitlab.DataSourceHandlers; + +public class FolderPickerDataHandler( + InvocationContext invocationContext, + [ActionParameter] GetRepositoryRequest repositoryRequest, + [ActionParameter] GetOptionalBranchRequest branchRequest) + : GitLabDataHandler(invocationContext), IAsyncFileDataSourceItemHandler +{ + + public async Task> GetFolderContentAsync( + FolderContentDataSourceContext context, + CancellationToken cancellationToken) + { + var projectId = GetProjectId(repositoryRequest.RepositoryId); + var folderPath = GitLabPathHelper.NormalizeFolderId(context?.FolderId); + var request = RestClient.CreateRequest($"/projects/{projectId}/repository/tree", Method.Get); + request.AddQueryParameter("path", string.IsNullOrEmpty(folderPath) ? "/" : folderPath); + + if (!string.IsNullOrWhiteSpace(branchRequest.Name)) + request.AddQueryParameter("ref", branchRequest.Name); + + var tree = await RestClient.ExecuteWithErrorHandling>(request); + + return tree + .Where(x => x.Type == "tree") + .OrderBy(x => GetDisplayName(x.Path), StringComparer.OrdinalIgnoreCase) + .Select(x => (FileDataItem)new Folder + { + Id = GitLabPathHelper.NormalizePath(x.Path), + DisplayName = GetDisplayName(x.Path), + IsSelectable = true + }) + .ToList(); + } + + public Task> GetFolderPathAsync( + FolderPathDataSourceContext context, + CancellationToken cancellationToken) + { + var path = new List + { + new() { Id = GitLabPathHelper.RootId, DisplayName = GitLabPathHelper.RootDisplayName } + }; + + var normalizedPath = GitLabPathHelper.NormalizePath(context?.FileDataItemId); + if (string.IsNullOrEmpty(normalizedPath)) + return Task.FromResult>(path); + + var currentPath = string.Empty; + foreach (var part in normalizedPath.Split('/', StringSplitOptions.RemoveEmptyEntries)) + { + currentPath = string.IsNullOrEmpty(currentPath) ? part : $"{currentPath}/{part}"; + path.Add(new FolderPathItem + { + Id = currentPath, + DisplayName = part + }); + } + + return Task.FromResult>(path); + } + +} diff --git a/Apps.GitLab/DataSourceHandlers/GitLabDataHandler.cs b/Apps.GitLab/DataSourceHandlers/GitLabDataHandler.cs new file mode 100644 index 0000000..9e69907 --- /dev/null +++ b/Apps.GitLab/DataSourceHandlers/GitLabDataHandler.cs @@ -0,0 +1,30 @@ +using Apps.GitLab.Utils; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Authentication; +using Blackbird.Applications.Sdk.Common.Exceptions; +using Blackbird.Applications.Sdk.Common.Invocation; + +namespace Apps.Gitlab.DataSourceHandlers; + +public abstract class GitLabDataHandler(InvocationContext invocationContext) : BaseInvocable(invocationContext) +{ + protected IEnumerable Creds => + InvocationContext.AuthenticationCredentialsProviders; + + protected BlackbirdGitlabClient RestClient { get; } = + new(invocationContext.AuthenticationCredentialsProviders); + + protected static int GetProjectId(string? repositoryId) + { + if (string.IsNullOrWhiteSpace(repositoryId)) + throw new PluginMisconfigurationException("You should select a repository first."); + + return ParsingUtils.ParseIntOrThrow(repositoryId, "Repository ID"); + } + + protected static string GetDisplayName(string path) + { + var normalizedPath = GitLabPathHelper.NormalizePath(path); + return normalizedPath.Split('/').LastOrDefault() ?? normalizedPath; + } +} diff --git a/Apps.GitLab/DataSourceHandlers/GitLabPathHelper.cs b/Apps.GitLab/DataSourceHandlers/GitLabPathHelper.cs new file mode 100644 index 0000000..2863781 --- /dev/null +++ b/Apps.GitLab/DataSourceHandlers/GitLabPathHelper.cs @@ -0,0 +1,22 @@ +namespace Apps.Gitlab.DataSourceHandlers; + +public static class GitLabPathHelper +{ + public const string RootId = "root"; + public const string RootDisplayName = "Root"; + + public static string NormalizeFolderId(string? folderId) + { + if (string.IsNullOrWhiteSpace(folderId) || folderId == RootId || folderId == "/") + return string.Empty; + + return folderId.Trim().Trim('/'); + } + + public static string NormalizePath(string? path) + { + return string.IsNullOrWhiteSpace(path) + ? string.Empty + : path.Trim().Trim('/'); + } +} diff --git a/Apps.GitLab/DataSourceHandlers/MergeRequestDataHandler.cs b/Apps.GitLab/DataSourceHandlers/MergeRequestDataHandler.cs index 919b1e0..9cfdaaf 100644 --- a/Apps.GitLab/DataSourceHandlers/MergeRequestDataHandler.cs +++ b/Apps.GitLab/DataSourceHandlers/MergeRequestDataHandler.cs @@ -1,10 +1,12 @@ -using Apps.Gitlab.Models.Respository.Requests; using Apps.Gitlab; +using Apps.Gitlab.Models.Respository.Requests; +using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Authentication; using Blackbird.Applications.Sdk.Common.Dynamic; using Blackbird.Applications.Sdk.Common.Invocation; -using Blackbird.Applications.Sdk.Common; -using GitLabApiClient.Internal.Paths; +using Apps.GitLab.Utils; +using GitLabApiClient.Models.MergeRequests.Responses; +using RestSharp; namespace Apps.GitLab.DataSourceHandlers; @@ -26,8 +28,11 @@ public async Task> GetDataAsync( { if (RepositoryRequest == null || string.IsNullOrWhiteSpace(RepositoryRequest.RepositoryId)) throw new ArgumentException("Please, specify repository first"); - var projectId = (ProjectId)int.Parse(RepositoryRequest.RepositoryId); - var mergeRequests = await new BlackbirdGitlabClient(Creds).Client.MergeRequests.GetAsync(projectId, (options) => { }); + + var projectId = ParsingUtils.ParseIntOrThrow(RepositoryRequest.RepositoryId, "Repository ID"); + var client = new BlackbirdGitlabClient(Creds); + var request = client.CreateRequest($"/projects/{projectId}/merge_requests", Method.Get); + var mergeRequests = await client.ExecuteWithErrorHandling>(request); return mergeRequests .Where(x => context.SearchString == null || @@ -35,4 +40,4 @@ public async Task> GetDataAsync( .Take(20) .ToDictionary(x => x.Iid.ToString(), x => x.Title); } -} \ No newline at end of file +} diff --git a/Apps.GitLab/DataSourceHandlers/RepositoryDataHandler.cs b/Apps.GitLab/DataSourceHandlers/RepositoryDataHandler.cs index 365cf59..e480a65 100644 --- a/Apps.GitLab/DataSourceHandlers/RepositoryDataHandler.cs +++ b/Apps.GitLab/DataSourceHandlers/RepositoryDataHandler.cs @@ -1,7 +1,9 @@ -using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Authentication; using Blackbird.Applications.Sdk.Common.Dynamic; using Blackbird.Applications.Sdk.Common.Invocation; +using GitLabApiClient.Models.Projects.Responses; +using RestSharp; namespace Apps.Gitlab.DataSourceHandlers; @@ -18,7 +20,11 @@ public async Task> GetDataAsync( DataSourceContext context, CancellationToken cancellationToken) { - var content = await new BlackbirdGitlabClient(Creds).Client.Projects.GetAsync((options) => { options.IsMemberOf = true; }); + var client = new BlackbirdGitlabClient(Creds); + var request = client.CreateRequest("/projects", Method.Get); + request.AddQueryParameter("membership", "true"); + + var content = await client.ExecuteWithErrorHandling>(request); return content .Where(x => context.SearchString == null || x.Name.Contains(context.SearchString, StringComparison.OrdinalIgnoreCase)) @@ -26,4 +32,4 @@ public async Task> GetDataAsync( .Take(20) .ToDictionary(x => x.Id.ToString(), x => x.Name); } -} \ No newline at end of file +} diff --git a/Apps.GitLab/DataSourceHandlers/UsersDataHandler.cs b/Apps.GitLab/DataSourceHandlers/UsersDataHandler.cs index 44057c7..77718af 100644 --- a/Apps.GitLab/DataSourceHandlers/UsersDataHandler.cs +++ b/Apps.GitLab/DataSourceHandlers/UsersDataHandler.cs @@ -1,7 +1,9 @@ -using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Authentication; using Blackbird.Applications.Sdk.Common.Dynamic; using Blackbird.Applications.Sdk.Common.Invocation; +using Apps.GitLab.Models.User.Responses; +using RestSharp; namespace Apps.Gitlab.DataSourceHandlers; @@ -21,7 +23,11 @@ public async Task> GetDataAsync( if (string.IsNullOrWhiteSpace(context.SearchString)) return new Dictionary(); - var content = await new BlackbirdGitlabClient(Creds).Client.Users.GetAsync(); - return content.Take(30).ToDictionary(x => x.Id.ToString(), x => $"{x.Username}"); + var client = new BlackbirdGitlabClient(Creds); + var request = client.CreateRequest("/users", Method.Get); + request.AddQueryParameter("search", context.SearchString); + + var content = await client.ExecuteWithErrorHandling>(request); + return content.ToDictionary(x => x.Id.ToString(), x => x.Username); } -} \ No newline at end of file +} diff --git a/Apps.GitLab/Dtos/RepositoryInfo.cs b/Apps.GitLab/Dtos/RepositoryInfo.cs new file mode 100644 index 0000000..5bac2ff --- /dev/null +++ b/Apps.GitLab/Dtos/RepositoryInfo.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json; + +namespace Apps.GitLab.Dtos; + +public class RepositoryInfo +{ + [JsonProperty("default_branch")] + public string? DefaultBranch { get; set; } +} diff --git a/Apps.GitLab/Models/Commit/Requests/DeleteFileRequest.cs b/Apps.GitLab/Models/Commit/Requests/DeleteFileRequest.cs index e2b0619..925b501 100644 --- a/Apps.GitLab/Models/Commit/Requests/DeleteFileRequest.cs +++ b/Apps.GitLab/Models/Commit/Requests/DeleteFileRequest.cs @@ -1,12 +1,15 @@ -using Blackbird.Applications.Sdk.Common; +using Apps.Gitlab.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.SDK.Extensions.FileManagement.Models.FileDataSourceItems; namespace Apps.Gitlab.Models.Commit.Requests; public class DeleteFileRequest { [Display("File path")] + [FileDataSource(typeof(FilePickerDataHandler))] public string FilePath { get; set; } [Display("Commit message")] public string CommitMessage { get; set; } -} \ No newline at end of file +} diff --git a/Apps.GitLab/Models/Respository/Requests/FolderContentRequest.cs b/Apps.GitLab/Models/Respository/Requests/FolderContentRequest.cs index df99233..97b169d 100644 --- a/Apps.GitLab/Models/Respository/Requests/FolderContentRequest.cs +++ b/Apps.GitLab/Models/Respository/Requests/FolderContentRequest.cs @@ -1,4 +1,7 @@ -using Blackbird.Applications.Sdk.Common; +using Apps.Gitlab.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.SDK.Extensions.FileManagement.Models.FileDataSourceItems; + namespace Apps.Gitlab.Models.Respository.Requests; public class FolderContentRequest @@ -14,8 +17,9 @@ public FolderContentRequest(string? path, bool? includeSubfolders) } [Display("Folder path (e.g. \"Folder1/Folder2\")")] + [FileDataSource(typeof(FolderPickerDataHandler))] public string? Path { get; set; } [Display("Include subfolders")] public bool? IncludeSubfolders { get; set; } -} \ 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 2ae947c..af7ee36 100644 --- a/Apps.GitLab/Models/Respository/Requests/GetFileRequest.cs +++ b/Apps.GitLab/Models/Respository/Requests/GetFileRequest.cs @@ -1,9 +1,12 @@ -using Blackbird.Applications.Sdk.Common; +using Apps.Gitlab.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.SDK.Extensions.FileManagement.Models.FileDataSourceItems; namespace Apps.Gitlab.Models.Respository.Requests; public class GetFileRequest { [Display("File path")] + [FileDataSource(typeof(FilePickerDataHandler))] public string FilePath { get; set; } -} \ No newline at end of file +} diff --git a/Apps.GitLab/Models/Respository/Responses/RepositoryFileResponse.cs b/Apps.GitLab/Models/Respository/Responses/RepositoryFileResponse.cs new file mode 100644 index 0000000..cad7760 --- /dev/null +++ b/Apps.GitLab/Models/Respository/Responses/RepositoryFileResponse.cs @@ -0,0 +1,7 @@ +namespace Apps.GitLab.Models.Respository.Responses; + +public class RepositoryFileResponse +{ + public string Content { get; set; } = string.Empty; +} + diff --git a/Apps.GitLab/Models/User/Responses/UserResponse.cs b/Apps.GitLab/Models/User/Responses/UserResponse.cs new file mode 100644 index 0000000..90e1880 --- /dev/null +++ b/Apps.GitLab/Models/User/Responses/UserResponse.cs @@ -0,0 +1,8 @@ +namespace Apps.GitLab.Models.User.Responses; + +public class UserResponse +{ + public int Id { get; set; } + + public string Username { get; set; } = string.Empty; +} diff --git a/Apps.GitLab/Utils/ErrorHandler.cs b/Apps.GitLab/Utils/ErrorHandler.cs deleted file mode 100644 index 5ef9513..0000000 --- a/Apps.GitLab/Utils/ErrorHandler.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Blackbird.Applications.Sdk.Common.Exceptions; -using GitLabApiClient; - -namespace Apps.GitLab.Utils; - -public static class ErrorHandler -{ - public static async Task ExecuteWithErrorHandlingAsync(Func action) - { - try - { - await action(); - } - catch (GitLabException ex) - { - throw MapGitLabException(ex); - } - catch (Exception ex) - { - throw new PluginApplicationException(ex.Message); - } - } - - public static async Task ExecuteWithErrorHandlingAsync(Func> action) - { - try - { - return await action(); - } - catch (GitLabException ex) - { - throw MapGitLabException(ex); - } - catch (Exception ex) - { - throw new PluginApplicationException(ex.Message); - } - } - - private static Exception MapGitLabException(GitLabException ex) - { - var statusCode = TryGetStatusCode(ex); - - return statusCode switch - { - 401 or 403 => new PluginMisconfigurationException( - "GitLab credentials are invalid or do not have sufficient permissions. Please check your access token and scopes."), - 404 => new PluginApplicationException("Resource was not found in GitLab. Please verify the repository ID / path / branch."), - 429 => new PluginApplicationException("GitLab rate limit reached. Please try again later."), - _ => new PluginApplicationException(ex.Message) - }; - } - - private static int? TryGetStatusCode(GitLabException ex) - { - var type = ex.GetType(); - - var statusCodeProp = type.GetProperty("StatusCode"); - if (statusCodeProp?.GetValue(ex) is int sc1) return sc1; - - var statusProp = type.GetProperty("Status"); - if (statusProp?.GetValue(ex) is int sc2) return sc2; - - if (statusCodeProp?.GetValue(ex) is System.Net.HttpStatusCode enumSc) - return (int)enumSc; - - return null; - } -} diff --git a/Apps.GitLab/Utils/ParsingUtils.cs b/Apps.GitLab/Utils/ParsingUtils.cs new file mode 100644 index 0000000..04af4fb --- /dev/null +++ b/Apps.GitLab/Utils/ParsingUtils.cs @@ -0,0 +1,14 @@ +using Blackbird.Applications.Sdk.Common.Exceptions; + +namespace Apps.GitLab.Utils; + +public static class ParsingUtils +{ + public static int ParseIntOrThrow(string value, string fieldName) + { + if (int.TryParse(value, out var parsed)) + return parsed; + + throw new PluginMisconfigurationException($"{fieldName} should be a valid integer."); + } +} diff --git a/Apps.GitLab/Webhooks/Handlers/PushEventHandler.cs b/Apps.GitLab/Webhooks/Handlers/PushEventHandler.cs index bf52615..c0bef57 100644 --- a/Apps.GitLab/Webhooks/Handlers/PushEventHandler.cs +++ b/Apps.GitLab/Webhooks/Handlers/PushEventHandler.cs @@ -1,49 +1,57 @@ -using Blackbird.Applications.Sdk.Common.Webhooks; -using Blackbird.Applications.Sdk.Common; +using Apps.Gitlab; +using Apps.Gitlab.Models.Branch.Requests; using Apps.Gitlab.Webhooks.Payloads; +using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Authentication; using Blackbird.Applications.Sdk.Common.Invocation; -using Apps.Gitlab; -using GitLabApiClient.Internal.Paths; -using Apps.Gitlab.Models.Branch.Requests; +using Blackbird.Applications.Sdk.Common.Webhooks; +using Apps.GitLab.Utils; +using Newtonsoft.Json; +using RestSharp; +using Apps.GitLab.Webhooks.Payloads; namespace Apps.GitLab.Webhooks.Handlers; public class PushEventHandler : BaseInvocable, IWebhookEventHandler { - private ProjectId RepositoryId { get; set; } + private int RepositoryId { get; set; } private GetOptionalBranchRequest BranchRequest { get; set; } - public PushEventHandler(InvocationContext invocationContext, - [WebhookParameter(true)] WebhookRepositoryInput repositoryRequest, + public PushEventHandler(InvocationContext invocationContext, + [WebhookParameter(true)] WebhookRepositoryInput repositoryRequest, [WebhookParameter(true)] GetOptionalBranchRequest branchRequest) : base(invocationContext) { - RepositoryId = (ProjectId)int.Parse(repositoryRequest.RepositoryId); + RepositoryId = ParsingUtils.ParseIntOrThrow(repositoryRequest.RepositoryId, "Repository ID"); BranchRequest = branchRequest; } public async Task SubscribeAsync(IEnumerable authenticationCredentialsProviders, Dictionary values) { - var createWebhookRequest = new GitLabApiClient.Models.Webhooks.Requests.CreateWebhookRequest(values["payloadUrl"]) + var client = new BlackbirdGitlabClient(authenticationCredentialsProviders); + var request = client.CreateRequest($"/projects/{RepositoryId}/hooks", Method.Post); + request.AddJsonBody(new CreateWebhookRequest { + Url = values["payloadUrl"], PushEvents = true, - }; - if (BranchRequest != null && !string.IsNullOrEmpty(BranchRequest.Name)) - { - createWebhookRequest.PushEventsBranchFilter = BranchRequest.Name; - } - await new BlackbirdGitlabClient(authenticationCredentialsProviders).Client.Webhooks.CreateAsync(RepositoryId, createWebhookRequest); + PushEventsBranchFilter = !string.IsNullOrEmpty(BranchRequest?.Name) ? BranchRequest.Name : null + }); + + await client.ExecuteWithErrorHandling(request); } public async Task UnsubscribeAsync(IEnumerable authenticationCredentialsProviders, Dictionary values) { - var projectWebhooks = await new BlackbirdGitlabClient(authenticationCredentialsProviders).Client.Webhooks.GetAsync(RepositoryId); + var client = new BlackbirdGitlabClient(authenticationCredentialsProviders); + var listRequest = client.CreateRequest($"/projects/{RepositoryId}/hooks", Method.Get); + var projectWebhooks = await client.ExecuteWithErrorHandling>(listRequest); var webhook = projectWebhooks.FirstOrDefault(x => x.PushEvents); - if(webhook != null) + + if (webhook != null) { - await new BlackbirdGitlabClient(authenticationCredentialsProviders).Client.Webhooks.DeleteAsync(RepositoryId, webhook.Id); - } + var deleteRequest = client.CreateRequest($"/projects/{RepositoryId}/hooks/{webhook.Id}", Method.Delete); + await client.ExecuteWithErrorHandling(deleteRequest); + } } -} \ No newline at end of file +} diff --git a/Apps.GitLab/Webhooks/Payloads/CreateWebhookRequest.cs b/Apps.GitLab/Webhooks/Payloads/CreateWebhookRequest.cs new file mode 100644 index 0000000..d527a8e --- /dev/null +++ b/Apps.GitLab/Webhooks/Payloads/CreateWebhookRequest.cs @@ -0,0 +1,15 @@ +using Newtonsoft.Json; + +namespace Apps.GitLab.Webhooks.Payloads; + +public class CreateWebhookRequest +{ + [JsonProperty("url")] + public string Url { get; set; } = string.Empty; + + [JsonProperty("push_events")] + public bool PushEvents { get; set; } + + [JsonProperty("push_events_branch_filter")] + public string? PushEventsBranchFilter { get; set; } +} \ No newline at end of file diff --git a/Apps.GitLab/Webhooks/Payloads/WebhookResponse.cs b/Apps.GitLab/Webhooks/Payloads/WebhookResponse.cs new file mode 100644 index 0000000..ccd4b2f --- /dev/null +++ b/Apps.GitLab/Webhooks/Payloads/WebhookResponse.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Apps.GitLab.Webhooks.Payloads; + +public class WebhookResponse +{ + [JsonProperty("id")] + public int Id { get; set; } + + [JsonProperty("push_events")] + public bool PushEvents { get; set; } +} diff --git a/Tests.GitLab/BranchActionTests.cs b/Tests.GitLab/BranchActionTests.cs index 76b833f..93c94c4 100644 --- a/Tests.GitLab/BranchActionTests.cs +++ b/Tests.GitLab/BranchActionTests.cs @@ -13,7 +13,7 @@ public class BranchActionTests : TestBaseWithContext [TestMethod, ContextDataSource(ConnectionTypes.PersonalAccessToken)] public async Task GetBranch_WithValidData_ReturnsBranch(InvocationContext context) { - var action = new BranchActions(context, FileManagementClient); + var action = new BranchActions(context); var result = await action.ListRepositoryBranches(new GetRepositoryRequest { RepositoryId = "70992272" }); diff --git a/Tests.GitLab/GitLabPickerDataHandlerTests.cs b/Tests.GitLab/GitLabPickerDataHandlerTests.cs new file mode 100644 index 0000000..f19be54 --- /dev/null +++ b/Tests.GitLab/GitLabPickerDataHandlerTests.cs @@ -0,0 +1,30 @@ +using Apps.Gitlab.DataSourceHandlers; +using Apps.GitLab.Constants; +using Blackbird.Applications.Sdk.Common.Invocation; +using Blackbird.Applications.SDK.Extensions.FileManagement.Models.FileDataSourceItems; +using Tests.GitLab.Base; + +namespace Tests.GitLab; + +[TestClass] +public class PickerDataHandlerTests : TestBaseWithContext +{ + [TestMethod, ContextDataSource(ConnectionTypes.PersonalAccessToken)] + public async Task FilePicker_GetFolderContentAsync_ReturnsMappedRootItems(InvocationContext context) + { + var handler = new FilePickerDataHandler(context, new Apps.Gitlab.Models.Respository.Requests.GetRepositoryRequest { RepositoryId= "71835863" }, + new Apps.Gitlab.Models.Branch.Requests.GetOptionalBranchRequest { }); + + var result = await handler.GetFolderContentAsync(new FolderContentDataSourceContext + { + + }, CancellationToken.None); + + foreach (var item in result) + { + Console.WriteLine($"Name: {item.DisplayName}, Id: {item.Id}"); + } + + Assert.IsNotNull(result); + } +} diff --git a/Tests.GitLab/Tests.GitLab.csproj b/Tests.GitLab/Tests.GitLab.csproj index cfbd27b..d5fde1f 100644 --- a/Tests.GitLab/Tests.GitLab.csproj +++ b/Tests.GitLab/Tests.GitLab.csproj @@ -15,6 +15,7 @@ +