From 384014faee194e970247b6feabff5858b6890920 Mon Sep 17 00:00:00 2001 From: sokolenkoolha Date: Mon, 11 Mar 2024 11:17:36 +0200 Subject: [PATCH 01/55] create job action --- Apps.Lionbridge/Actions/JobActions.cs | 39 ++++++++++++++ Apps.Lionbridge/Apps.Lionbridge.csproj | 4 -- .../ProviderDataSourceHandler.cs | 37 ++++++++++++++ .../Extensions/EnumerableExtensions.cs | 19 +++++++ .../Models/Dtos/EmbeddedItemsWrapper.cs | 9 ++++ Apps.Lionbridge/Models/Dtos/JobDto.cs | 51 +++++++++++++++++++ Apps.Lionbridge/Models/Dtos/ProviderDto.cs | 3 ++ .../Models/Requests/Job/CreateJobRequest.cs | 33 ++++++++++++ .../Responses/Provider/ProvidersResponse.cs | 10 ++++ 9 files changed, 201 insertions(+), 4 deletions(-) create mode 100644 Apps.Lionbridge/Actions/JobActions.cs create mode 100644 Apps.Lionbridge/DataSourceHandlers/ProviderDataSourceHandler.cs create mode 100644 Apps.Lionbridge/Extensions/EnumerableExtensions.cs create mode 100644 Apps.Lionbridge/Models/Dtos/EmbeddedItemsWrapper.cs create mode 100644 Apps.Lionbridge/Models/Dtos/JobDto.cs create mode 100644 Apps.Lionbridge/Models/Dtos/ProviderDto.cs create mode 100644 Apps.Lionbridge/Models/Requests/Job/CreateJobRequest.cs create mode 100644 Apps.Lionbridge/Models/Responses/Provider/ProvidersResponse.cs diff --git a/Apps.Lionbridge/Actions/JobActions.cs b/Apps.Lionbridge/Actions/JobActions.cs new file mode 100644 index 0000000..f8a40ea --- /dev/null +++ b/Apps.Lionbridge/Actions/JobActions.cs @@ -0,0 +1,39 @@ +using Apps.Lionbridge.Api; +using Apps.Lionbridge.Extensions; +using Apps.Lionbridge.Models.Dtos; +using Apps.Lionbridge.Models.Requests.Job; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Actions; +using Blackbird.Applications.Sdk.Common.Invocation; +using Blackbird.Applications.Sdk.Utils.Extensions.Http; +using RestSharp; + +namespace Apps.Lionbridge.Actions; + +[ActionList] +public class JobActions : LionbridgeInvocable +{ + public JobActions(InvocationContext invocationContext) : base(invocationContext) + { + } + + #region Post + + [Action("Create job", Description = "Create a new translation job.")] + public async Task CreateJob([ActionParameter] CreateJobRequest input) + { + var request = new LionbridgeRequest("/jobs", Method.Post) + .WithJsonBody(new + { + jobName = input.JobName, + description = input.Description, + providerId = input.ProviderId, + extendedMetadata = EnumerableExtensions.ToDictionary(input.MetadataKeys, input.MetadataValues), + labels = EnumerableExtensions.ToDictionary(input.LabelKeys, input.LabelValues) + }); + + return await Client.ExecuteWithErrorHandling(request); + } + + #endregion +} \ No newline at end of file diff --git a/Apps.Lionbridge/Apps.Lionbridge.csproj b/Apps.Lionbridge/Apps.Lionbridge.csproj index abbb7b9..345119d 100644 --- a/Apps.Lionbridge/Apps.Lionbridge.csproj +++ b/Apps.Lionbridge/Apps.Lionbridge.csproj @@ -14,10 +14,6 @@ - - - - diff --git a/Apps.Lionbridge/DataSourceHandlers/ProviderDataSourceHandler.cs b/Apps.Lionbridge/DataSourceHandlers/ProviderDataSourceHandler.cs new file mode 100644 index 0000000..3635d30 --- /dev/null +++ b/Apps.Lionbridge/DataSourceHandlers/ProviderDataSourceHandler.cs @@ -0,0 +1,37 @@ +using Apps.Lionbridge.Api; +using Apps.Lionbridge.Models.Dtos; +using Apps.Lionbridge.Models.Responses.Provider; +using Blackbird.Applications.Sdk.Common.Dynamic; +using Blackbird.Applications.Sdk.Common.Invocation; + +namespace Apps.Lionbridge.DataSourceHandlers; + +public class ProviderDataSourceHandler : LionbridgeInvocable, IAsyncDataSourceHandler +{ + public ProviderDataSourceHandler(InvocationContext invocationContext) : base(invocationContext) + { + } + + public async Task> GetDataAsync(DataSourceContext context, + CancellationToken cancellationToken) + { + var endpoint = "/providers?includeDeletedOnes=false"; + var providers = new List(); + string? nextToken; + + do + { + var request = new LionbridgeRequest(endpoint); + var response = await Client.ExecuteWithErrorHandling(request); + + providers.AddRange(response.Embedded.Providers.Where(provider => + context.SearchString == null || + provider.ProviderName.Contains(context.SearchString, StringComparison.OrdinalIgnoreCase))); + + nextToken = response.Next; + endpoint = $"/providers?includeDeletedOnes=false&next={nextToken}"; + } while (nextToken != null); + + return providers.ToDictionary(provider => provider.ProviderId, provider => provider.ProviderName); + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Extensions/EnumerableExtensions.cs b/Apps.Lionbridge/Extensions/EnumerableExtensions.cs new file mode 100644 index 0000000..7227091 --- /dev/null +++ b/Apps.Lionbridge/Extensions/EnumerableExtensions.cs @@ -0,0 +1,19 @@ +namespace Apps.Lionbridge.Extensions; + +public static class EnumerableExtensions +{ + public static Dictionary ToDictionary(IEnumerable? first, + IEnumerable? second) where TKey : notnull + { + if (first == null || second == null) + return new Dictionary(); + + var firstArray = first.ToArray(); + var secondArray = second.ToArray(); + + return firstArray + .Take(Math.Min(firstArray.Length, secondArray.Length)) + .Zip(secondArray, (firstValue, secondValue) => new KeyValuePair(firstValue, secondValue)) + .ToDictionary(); + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Dtos/EmbeddedItemsWrapper.cs b/Apps.Lionbridge/Models/Dtos/EmbeddedItemsWrapper.cs new file mode 100644 index 0000000..8789e5b --- /dev/null +++ b/Apps.Lionbridge/Models/Dtos/EmbeddedItemsWrapper.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json; + +namespace Apps.Lionbridge.Models.Dtos; + +public class EmbeddedItemsWrapper +{ + [JsonProperty("_embedded")] + public T Embedded { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Dtos/JobDto.cs b/Apps.Lionbridge/Models/Dtos/JobDto.cs new file mode 100644 index 0000000..a004638 --- /dev/null +++ b/Apps.Lionbridge/Models/Dtos/JobDto.cs @@ -0,0 +1,51 @@ +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Lionbridge.Models.Dtos; + +public class JobDto +{ + [Display("Job ID")] + public string JobId { get; set; } + + [Display("Job name")] + public string JobName { get; set; } + + [Display("Job description")] + public string Description { get; set; } + + [Display("Status code")] + public string StatusCode { get; set; } + + [Display("Has error")] + public bool HasError { get; set; } + + [Display("Creator ID")] + public string CreatorId { get; set; } + + [Display("Provider ID")] + public string ProviderId { get; set; } + + [Display("Created date")] + public DateTime CreatedDate { get; set; } + + [Display("Modified date")] + public DateTime ModifiedDate { get; set; } + + [Display("Is archived")] + public bool Archived { get; set; } + + [Display("Archive status")] + public string ArchiveStatus { get; set; } + + [Display("Estimated archival date")] + public DateTime EstimatedArchivalDate { get; set; } + + [Display("ShouldQuote")] + public bool ShouldQuote { get; set; } + + [Display("Site ID")] + public string SiteId { get; set; } + + [Display("Global tracking ID")] + public string GlobalTrackingId { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Dtos/ProviderDto.cs b/Apps.Lionbridge/Models/Dtos/ProviderDto.cs new file mode 100644 index 0000000..f4dde23 --- /dev/null +++ b/Apps.Lionbridge/Models/Dtos/ProviderDto.cs @@ -0,0 +1,3 @@ +namespace Apps.Lionbridge.Models.Dtos; + +public record ProviderDto(string ProviderId, string ProviderName); \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/Job/CreateJobRequest.cs b/Apps.Lionbridge/Models/Requests/Job/CreateJobRequest.cs new file mode 100644 index 0000000..0b9415c --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/Job/CreateJobRequest.cs @@ -0,0 +1,33 @@ +using Apps.Lionbridge.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Lionbridge.Models.Requests.Job; + +public class CreateJobRequest +{ + [Display("Job name")] + public string JobName { get; set; } + + public string? Description { get; set; } + + [Display("Provider ID")] + [DataSource(typeof(ProviderDataSourceHandler))] + public string? ProviderId { get; set; } + + [Display("Metadata keys", Description = "Extended metadata keys. For each specified key, a respective value " + + "should be added in the 'Metadata values' input parameter.")] + public IEnumerable? MetadataKeys { get; set; } + + [Display("Metadata values", Description = "Extended metadata values. For each specified value, a respective " + + "key should be added in the 'Metadata keys' input parameter.")] + public IEnumerable? MetadataValues { get; set; } + + [Display("Label keys", Description = "Label keys. For each specified key, a respective value should be added " + + "in the 'Label values' input parameter.")] + public IEnumerable? LabelKeys { get; set; } + + [Display("Label values", Description = "Label values. For each specified value, a respective key should be " + + "added in the 'Label keys' input parameter.")] + public IEnumerable? LabelValues { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Responses/Provider/ProvidersResponse.cs b/Apps.Lionbridge/Models/Responses/Provider/ProvidersResponse.cs new file mode 100644 index 0000000..fef410a --- /dev/null +++ b/Apps.Lionbridge/Models/Responses/Provider/ProvidersResponse.cs @@ -0,0 +1,10 @@ +using Apps.Lionbridge.Models.Dtos; + +namespace Apps.Lionbridge.Models.Responses.Provider; + +public class ProvidersResponse : EmbeddedItemsWrapper +{ + public string? Next { get; set; } +} + +public record ProvidersWrapper(IEnumerable Providers); \ No newline at end of file From c3aaa51819ccdecf34231a7597e456b0f63e1329 Mon Sep 17 00:00:00 2001 From: sokolenkoolha Date: Mon, 11 Mar 2024 11:48:37 +0200 Subject: [PATCH 02/55] added retry logic --- Apps.Lionbridge/Api/LionbridgeClient.cs | 31 ++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/Apps.Lionbridge/Api/LionbridgeClient.cs b/Apps.Lionbridge/Api/LionbridgeClient.cs index d361c57..825a829 100644 --- a/Apps.Lionbridge/Api/LionbridgeClient.cs +++ b/Apps.Lionbridge/Api/LionbridgeClient.cs @@ -1,4 +1,5 @@ -using Apps.Lionbridge.Constants; +using System.Net; +using Apps.Lionbridge.Constants; using Apps.Lionbridge.Extensions; using Apps.Lionbridge.Models.Dtos; using Blackbird.Applications.Sdk.Common.Authentication; @@ -22,6 +23,34 @@ public LionbridgeClient(IEnumerable authentic this.AddDefaultHeader("Authorization", $"Bearer {accessToken}"); } + public override async Task ExecuteWithErrorHandling(RestRequest request) + { + var response = await ExecuteAsync(request); + + if (response.StatusCode == HttpStatusCode.TooManyRequests || + response.StatusCode == HttpStatusCode.ServiceUnavailable) + { + const int scalingFactor = 2; + var retryAfterMilliseconds = 1000; + + for (int i = 0; i < 5; i++) + { + await Task.Delay(retryAfterMilliseconds); + response = await ExecuteAsync(request); + + if (response.IsSuccessStatusCode) + break; + + retryAfterMilliseconds *= scalingFactor; + } + } + + if (!response.IsSuccessStatusCode) + throw ConfigureErrorException(response); + + return response; + } + protected override Exception ConfigureErrorException(RestResponse response) { if (string.IsNullOrWhiteSpace(response.Content)) From 2556db735f7b11597a43daa87090362fdba3fb74 Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Wed, 13 Mar 2024 19:01:56 +0200 Subject: [PATCH 03/55] Added job actions --- Apps.Lionbridge/Actions/JobActions.cs | 23 +++++++++++------- Apps.Lionbridge/Api/LionbridgeClient.cs | 6 +++++ Apps.Lionbridge/Constants/ApiEndpoints.cs | 7 ++++++ .../JobDataSourceHandler.cs | 24 +++++++++++++++++++ .../ProviderDataSourceHandler.cs | 7 ++---- .../Models/Requests/Job/GetJobRequest.cs | 11 +++++++++ .../Models/Responses/Job/JobsResponse.cs | 8 +++++++ 7 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 Apps.Lionbridge/Constants/ApiEndpoints.cs create mode 100644 Apps.Lionbridge/DataSourceHandlers/JobDataSourceHandler.cs create mode 100644 Apps.Lionbridge/Models/Requests/Job/GetJobRequest.cs create mode 100644 Apps.Lionbridge/Models/Responses/Job/JobsResponse.cs diff --git a/Apps.Lionbridge/Actions/JobActions.cs b/Apps.Lionbridge/Actions/JobActions.cs index f8a40ea..249e478 100644 --- a/Apps.Lionbridge/Actions/JobActions.cs +++ b/Apps.Lionbridge/Actions/JobActions.cs @@ -1,4 +1,5 @@ using Apps.Lionbridge.Api; +using Apps.Lionbridge.Constants; using Apps.Lionbridge.Extensions; using Apps.Lionbridge.Models.Dtos; using Apps.Lionbridge.Models.Requests.Job; @@ -11,14 +12,8 @@ namespace Apps.Lionbridge.Actions; [ActionList] -public class JobActions : LionbridgeInvocable +public class JobActions(InvocationContext invocationContext) : LionbridgeInvocable(invocationContext) { - public JobActions(InvocationContext invocationContext) : base(invocationContext) - { - } - - #region Post - [Action("Create job", Description = "Create a new translation job.")] public async Task CreateJob([ActionParameter] CreateJobRequest input) { @@ -35,5 +30,17 @@ public async Task CreateJob([ActionParameter] CreateJobRequest input) return await Client.ExecuteWithErrorHandling(request); } - #endregion + [Action("Delete job", Description = "Delete a translation job.")] + public async Task DeleteJob([ActionParameter] GetJobRequest request) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}", Method.Delete); + await Client.ExecuteWithErrorHandling(apiRequest); + } + + [Action("Get job", Description = "Get a translation job.")] + public async Task GetJob([ActionParameter] GetJobRequest request) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}"); + return await Client.ExecuteWithErrorHandling(apiRequest); + } } \ No newline at end of file diff --git a/Apps.Lionbridge/Api/LionbridgeClient.cs b/Apps.Lionbridge/Api/LionbridgeClient.cs index 825a829..8ebd7c3 100644 --- a/Apps.Lionbridge/Api/LionbridgeClient.cs +++ b/Apps.Lionbridge/Api/LionbridgeClient.cs @@ -50,6 +50,12 @@ public override async Task ExecuteWithErrorHandling(RestRequest re return response; } + + public override async Task ExecuteWithErrorHandling(RestRequest request) + { + var response = await ExecuteWithErrorHandling(request); + return JsonConvert.DeserializeObject(response.Content, JsonSettings); + } protected override Exception ConfigureErrorException(RestResponse response) { diff --git a/Apps.Lionbridge/Constants/ApiEndpoints.cs b/Apps.Lionbridge/Constants/ApiEndpoints.cs new file mode 100644 index 0000000..3147bed --- /dev/null +++ b/Apps.Lionbridge/Constants/ApiEndpoints.cs @@ -0,0 +1,7 @@ +namespace Apps.Lionbridge.Constants; + +public static class ApiEndpoints +{ + public const string Providers = "/providers"; + public const string Jobs = "/jobs"; +} \ No newline at end of file diff --git a/Apps.Lionbridge/DataSourceHandlers/JobDataSourceHandler.cs b/Apps.Lionbridge/DataSourceHandlers/JobDataSourceHandler.cs new file mode 100644 index 0000000..0ea8b63 --- /dev/null +++ b/Apps.Lionbridge/DataSourceHandlers/JobDataSourceHandler.cs @@ -0,0 +1,24 @@ +using Apps.Lionbridge.Api; +using Apps.Lionbridge.Constants; +using Apps.Lionbridge.Models.Responses.Job; +using Blackbird.Applications.Sdk.Common.Dynamic; +using Blackbird.Applications.Sdk.Common.Invocation; + +namespace Apps.Lionbridge.DataSourceHandlers; + +public class JobDataSourceHandler(InvocationContext invocationContext) + : LionbridgeInvocable(invocationContext), IAsyncDataSourceHandler +{ + public async Task> GetDataAsync(DataSourceContext context, + CancellationToken cancellationToken) + { + var endpoint = ApiEndpoints.Jobs; + var request = new LionbridgeRequest(endpoint); + var response = await Client.ExecuteWithErrorHandling(request); + + return response.Embedded.Jobs.Where(job => + context.SearchString == null || + job.JobName.Contains(context.SearchString, StringComparison.OrdinalIgnoreCase)) + .ToDictionary(job => job.JobId, job => job.JobName); + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/DataSourceHandlers/ProviderDataSourceHandler.cs b/Apps.Lionbridge/DataSourceHandlers/ProviderDataSourceHandler.cs index 3635d30..f10503a 100644 --- a/Apps.Lionbridge/DataSourceHandlers/ProviderDataSourceHandler.cs +++ b/Apps.Lionbridge/DataSourceHandlers/ProviderDataSourceHandler.cs @@ -6,12 +6,9 @@ namespace Apps.Lionbridge.DataSourceHandlers; -public class ProviderDataSourceHandler : LionbridgeInvocable, IAsyncDataSourceHandler +public class ProviderDataSourceHandler(InvocationContext invocationContext) + : LionbridgeInvocable(invocationContext), IAsyncDataSourceHandler { - public ProviderDataSourceHandler(InvocationContext invocationContext) : base(invocationContext) - { - } - public async Task> GetDataAsync(DataSourceContext context, CancellationToken cancellationToken) { diff --git a/Apps.Lionbridge/Models/Requests/Job/GetJobRequest.cs b/Apps.Lionbridge/Models/Requests/Job/GetJobRequest.cs new file mode 100644 index 0000000..4305505 --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/Job/GetJobRequest.cs @@ -0,0 +1,11 @@ +using Apps.Lionbridge.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Lionbridge.Models.Requests.Job; + +public class GetJobRequest +{ + [Display("Job ID"), DataSource(typeof(JobDataSourceHandler))] + public string JobId { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Responses/Job/JobsResponse.cs b/Apps.Lionbridge/Models/Responses/Job/JobsResponse.cs new file mode 100644 index 0000000..16c0fed --- /dev/null +++ b/Apps.Lionbridge/Models/Responses/Job/JobsResponse.cs @@ -0,0 +1,8 @@ +using Apps.Lionbridge.Models.Dtos; + +namespace Apps.Lionbridge.Models.Responses.Job; + +public class JobsResponse : EmbeddedItemsWrapper +{ } + +public record JobsWrapper(IEnumerable Jobs); \ No newline at end of file From c35195c1b84c835f7acba9339b176a10af196afc Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Wed, 13 Mar 2024 19:35:29 +0200 Subject: [PATCH 04/55] Put action in job --- Apps.Lionbridge/Actions/JobActions.cs | 85 +++++++++++++++++++ Apps.Lionbridge/Models/Dtos/JobDto.cs | 6 ++ .../Models/Dtos/UpdateJobApiRequest.cs | 41 +++++++++ .../Models/Requests/Job/UpdateJobRequest.cs | 38 +++++++++ .../Requests/Provider/GetProviderRequest.cs | 11 +++ 5 files changed, 181 insertions(+) create mode 100644 Apps.Lionbridge/Models/Dtos/UpdateJobApiRequest.cs create mode 100644 Apps.Lionbridge/Models/Requests/Job/UpdateJobRequest.cs create mode 100644 Apps.Lionbridge/Models/Requests/Provider/GetProviderRequest.cs diff --git a/Apps.Lionbridge/Actions/JobActions.cs b/Apps.Lionbridge/Actions/JobActions.cs index 249e478..1636ae1 100644 --- a/Apps.Lionbridge/Actions/JobActions.cs +++ b/Apps.Lionbridge/Actions/JobActions.cs @@ -3,6 +3,7 @@ using Apps.Lionbridge.Extensions; using Apps.Lionbridge.Models.Dtos; using Apps.Lionbridge.Models.Requests.Job; +using Apps.Lionbridge.Models.Requests.Provider; using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Actions; using Blackbird.Applications.Sdk.Common.Invocation; @@ -43,4 +44,88 @@ public async Task GetJob([ActionParameter] GetJobRequest request) var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}"); return await Client.ExecuteWithErrorHandling(apiRequest); } + + [Action("Update job", Description = "Update a translation job")] + public async Task UpdateJob([ActionParameter] GetJobRequest jobRequest, [ActionParameter] UpdateJobRequest request) + { + var apiUpdateRequest = new UpdateJobApiRequest(); + + if(request.JobName != null) + { + apiUpdateRequest.JobName = request.JobName; + } + + if(request.Description != null) + { + apiUpdateRequest.Description = request.Description; + } + + if(request.ProviderId != null) + { + apiUpdateRequest.ProviderId = request.ProviderId; + } + + if(request.MetadataKeys != null && request.MetadataValues != null) + { + apiUpdateRequest.ExtendedMetadata = EnumerableExtensions.ToDictionary(request.MetadataKeys, request.MetadataValues); + } + + if(request.DueDate != null) + { + apiUpdateRequest.DueDate = request.DueDate.Value.ToString("yyyy-MM-ddTHH:mm:ssZ"); + } + + if(request.CustomData != null) + { + apiUpdateRequest.CustomData = request.CustomData; + } + + if(request.ShouldQuote != null) + { + apiUpdateRequest.ShouldQuote = request.ShouldQuote; + } + + if(request.ConnectorName != null) + { + apiUpdateRequest.ConnectorName = request.ConnectorName; + } + + if(request.ConnectorVersion != null) + { + apiUpdateRequest.ConnectorVersion = request.ConnectorVersion; + } + + if(request.ServiceType != null) + { + apiUpdateRequest.ServiceType = request.ServiceType; + } + + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}", Method.Put) + .WithJsonBody(apiUpdateRequest); + + return await Client.ExecuteWithErrorHandling(apiRequest); + } + + [Action("Submit job", Description = "Submit a translation job")] + public async Task SubmitJob([ActionParameter] GetJobRequest request, [ActionParameter] GetProviderRequest providerRequest) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}/submit", Method.Put) + .WithJsonBody(new { providerId = providerRequest.ProviderId }); + + await Client.ExecuteWithErrorHandling(apiRequest); + } + + [Action("Archive job", Description = "Archive a translation job")] + public async Task ArchiveJob([ActionParameter] GetJobRequest request) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}/archive", Method.Put); + await Client.ExecuteWithErrorHandling(apiRequest); + } + + [Action("Unarchive job", Description = "Unarchive a translation job")] + public async Task UnarchiveJob([ActionParameter] GetJobRequest request) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}/unarchive", Method.Put); + await Client.ExecuteWithErrorHandling(apiRequest); + } } \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Dtos/JobDto.cs b/Apps.Lionbridge/Models/Dtos/JobDto.cs index a004638..b93bf0d 100644 --- a/Apps.Lionbridge/Models/Dtos/JobDto.cs +++ b/Apps.Lionbridge/Models/Dtos/JobDto.cs @@ -48,4 +48,10 @@ public class JobDto [Display("Global tracking ID")] public string GlobalTrackingId { get; set; } + + [Display("Extended metadata")] + public Dictionary ExtendedMetadata { get; set; } + + [Display("Labels")] + public Dictionary Labels { get; set; } } \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Dtos/UpdateJobApiRequest.cs b/Apps.Lionbridge/Models/Dtos/UpdateJobApiRequest.cs new file mode 100644 index 0000000..53fb197 --- /dev/null +++ b/Apps.Lionbridge/Models/Dtos/UpdateJobApiRequest.cs @@ -0,0 +1,41 @@ +using Apps.Lionbridge.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Lionbridge.Models.Dtos; + +public class UpdateJobApiRequest +{ + [Display("Job name")] + public string? JobName { get; set; } + + public string? Description { get; set; } + + [Display("PO reference")] + public string? PoReference { get; set; } + + [Display("Due date")] + public string? DueDate { get; set; } + + [Display("Custom data")] + public string? CustomData { get; set; } + + [Display("Should quote")] + public bool? ShouldQuote { get; set; } + + [Display("Provider ID")] + [DataSource(typeof(ProviderDataSourceHandler))] + public string? ProviderId { get; set; } + + [Display("Connector name")] + public string? ConnectorName { get; set; } + + [Display("Connector version")] + public string? ConnectorVersion { get; set; } + + [Display("Service type")] + public string? ServiceType { get; set; } + + [Display("Extended metadata")] + public Dictionary ExtendedMetadata { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/Job/UpdateJobRequest.cs b/Apps.Lionbridge/Models/Requests/Job/UpdateJobRequest.cs new file mode 100644 index 0000000..99dc695 --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/Job/UpdateJobRequest.cs @@ -0,0 +1,38 @@ +using Apps.Lionbridge.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Lionbridge.Models.Requests.Job; + +public class UpdateJobRequest +{ + [Display("Job name")] public string? JobName { get; set; } + + public string? Description { get; set; } + + [Display("PO reference")] public string? PoReference { get; set; } + + [Display("Due date")] public DateTime? DueDate { get; set; } + + [Display("Custom data")] public string? CustomData { get; set; } + + [Display("Should quote")] public bool? ShouldQuote { get; set; } + + [Display("Provider ID")] + [DataSource(typeof(ProviderDataSourceHandler))] + public string? ProviderId { get; set; } + + [Display("Connector name")] public string? ConnectorName { get; set; } + + [Display("Connector version")] public string? ConnectorVersion { get; set; } + + [Display("Service type")] public string? ServiceType { get; set; } + + [Display("Metadata keys", Description = "Extended metadata keys. For each specified key, a respective value " + + "should be added in the 'Metadata values' input parameter.")] + public IEnumerable? MetadataKeys { get; set; } + + [Display("Metadata values", Description = "Extended metadata values. For each specified value, a respective " + + "key should be added in the 'Metadata keys' input parameter.")] + public IEnumerable? MetadataValues { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/Provider/GetProviderRequest.cs b/Apps.Lionbridge/Models/Requests/Provider/GetProviderRequest.cs new file mode 100644 index 0000000..99999be --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/Provider/GetProviderRequest.cs @@ -0,0 +1,11 @@ +using Apps.Lionbridge.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Lionbridge.Models.Requests.Provider; + +public class GetProviderRequest +{ + [Display("Provider ID"), DataSource(typeof(ProviderDataSourceHandler))] + public string ProviderId { get; set; } +} \ No newline at end of file From 233a1d7bee19dd335cf956e0b4143ff3a998dd35 Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Thu, 14 Mar 2024 11:41:04 +0200 Subject: [PATCH 05/55] Updated job outputs --- Apps.Lionbridge/Actions/JobActions.cs | 12 ++++++------ Apps.Lionbridge/Models/Dtos/JobDto.cs | 6 ------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/Apps.Lionbridge/Actions/JobActions.cs b/Apps.Lionbridge/Actions/JobActions.cs index 1636ae1..793b78d 100644 --- a/Apps.Lionbridge/Actions/JobActions.cs +++ b/Apps.Lionbridge/Actions/JobActions.cs @@ -107,25 +107,25 @@ public async Task UpdateJob([ActionParameter] GetJobRequest jobRequest, } [Action("Submit job", Description = "Submit a translation job")] - public async Task SubmitJob([ActionParameter] GetJobRequest request, [ActionParameter] GetProviderRequest providerRequest) + public async Task SubmitJob([ActionParameter] GetJobRequest request, [ActionParameter] GetProviderRequest providerRequest) { var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}/submit", Method.Put) .WithJsonBody(new { providerId = providerRequest.ProviderId }); - await Client.ExecuteWithErrorHandling(apiRequest); + return await Client.ExecuteWithErrorHandling(apiRequest); } [Action("Archive job", Description = "Archive a translation job")] - public async Task ArchiveJob([ActionParameter] GetJobRequest request) + public async Task ArchiveJob([ActionParameter] GetJobRequest request) { var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}/archive", Method.Put); - await Client.ExecuteWithErrorHandling(apiRequest); + return await Client.ExecuteWithErrorHandling(apiRequest); } [Action("Unarchive job", Description = "Unarchive a translation job")] - public async Task UnarchiveJob([ActionParameter] GetJobRequest request) + public async Task UnarchiveJob([ActionParameter] GetJobRequest request) { var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}/unarchive", Method.Put); - await Client.ExecuteWithErrorHandling(apiRequest); + return await Client.ExecuteWithErrorHandling(apiRequest); } } \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Dtos/JobDto.cs b/Apps.Lionbridge/Models/Dtos/JobDto.cs index b93bf0d..a004638 100644 --- a/Apps.Lionbridge/Models/Dtos/JobDto.cs +++ b/Apps.Lionbridge/Models/Dtos/JobDto.cs @@ -48,10 +48,4 @@ public class JobDto [Display("Global tracking ID")] public string GlobalTrackingId { get; set; } - - [Display("Extended metadata")] - public Dictionary ExtendedMetadata { get; set; } - - [Display("Labels")] - public Dictionary Labels { get; set; } } \ No newline at end of file From aae3269ce703cccbcbde8619a1152d02abdc77dc Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Thu, 14 Mar 2024 13:05:54 +0200 Subject: [PATCH 06/55] Added requests actions --- Apps.Lionbridge/Actions/RequestActions.cs | 80 +++++++++++++++++++ Apps.Lionbridge/Constants/ApiEndpoints.cs | 4 + .../RequestDataSourceHandler.cs | 45 +++++++++++ Apps.Lionbridge/Models/Dtos/RequestDto.cs | 39 +++++++++ .../Requests/Request/AddRequestModel.cs | 33 ++++++++ .../Models/Requests/Request/GetRequest.cs | 14 ++++ .../Models/Requests/Request/GetRequests.cs | 14 ++++ .../Requests/Request/UpdateContentRequest.cs | 15 ++++ .../Responses/Request/GetRequestsResponse.cs | 8 ++ .../Responses/Request/RequestsResponse.cs | 9 +++ 10 files changed, 261 insertions(+) create mode 100644 Apps.Lionbridge/Actions/RequestActions.cs create mode 100644 Apps.Lionbridge/DataSourceHandlers/RequestDataSourceHandler.cs create mode 100644 Apps.Lionbridge/Models/Dtos/RequestDto.cs create mode 100644 Apps.Lionbridge/Models/Requests/Request/AddRequestModel.cs create mode 100644 Apps.Lionbridge/Models/Requests/Request/GetRequest.cs create mode 100644 Apps.Lionbridge/Models/Requests/Request/GetRequests.cs create mode 100644 Apps.Lionbridge/Models/Requests/Request/UpdateContentRequest.cs create mode 100644 Apps.Lionbridge/Models/Responses/Request/GetRequestsResponse.cs create mode 100644 Apps.Lionbridge/Models/Responses/Request/RequestsResponse.cs diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs new file mode 100644 index 0000000..e2461de --- /dev/null +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -0,0 +1,80 @@ +using Apps.Lionbridge.Api; +using Apps.Lionbridge.Constants; +using Apps.Lionbridge.Models.Dtos; +using Apps.Lionbridge.Models.Requests.Job; +using Apps.Lionbridge.Models.Requests.Request; +using Apps.Lionbridge.Models.Responses.Request; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Actions; +using Blackbird.Applications.Sdk.Common.Invocation; +using Blackbird.Applications.Sdk.Utils.Extensions.Http; +using RestSharp; + +namespace Apps.Lionbridge.Actions; + +[ActionList] +public class RequestActions(InvocationContext invocationContext) : LionbridgeInvocable(invocationContext) +{ + [Action("Create request", Description = "Create a new translation request.")] + public async Task CreateRequest([ActionParameter] AddRequestModel request, [ActionParameter] GetJobRequest jobRequest) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Add, Method.Post) + .WithJsonBody(request); + + return await Client.ExecuteWithErrorHandling(apiRequest); + } + + [Action("Get request", Description = "Get a translation request.")] + public async Task GetRequest([ActionParameter] GetRequest request) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Requests}/{request.RequestId}"); + return await Client.ExecuteWithErrorHandling(apiRequest); + } + + [Action("Delete request", Description = "Delete a translation request.")] + public async Task DeleteRequest([ActionParameter] GetRequest request) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Requests}/{request.RequestId}", Method.Delete); + await Client.ExecuteWithErrorHandling(apiRequest); + } + + [Action("Approve request", Description = "Approve a translation request")] + public async Task ApproveRequest([ActionParameter] GetRequests request) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Approve, Method.Put) + .WithJsonBody(new + { + requestIds = request.RequestIds + }); + + await Client.ExecuteWithErrorHandling(apiRequest); + } + + [Action("Reject request", Description = "Reject a translation request")] + public async Task RejectRequest([ActionParameter] GetRequests request) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Reject, Method.Put) + .WithJsonBody(new + { + requestIds = request.RequestIds + }); + + await Client.ExecuteWithErrorHandling(apiRequest); + } + + [Action("Update request content", Description = "Update a translation request content")] + public async Task UpdateRequestContent([ActionParameter]GetRequests request, [ActionParameter] UpdateContentRequest updateRequestContentModel) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}" + ApiEndpoints.Requests, Method.Put) + .WithJsonBody(new + { + requestIds = request.RequestIds, + fieldNames = updateRequestContentModel.FieldNames, + fieldValues = updateRequestContentModel.FieldValues, + fieldComments = updateRequestContentModel.FieldComments + }); + + var response = await Client.ExecuteWithErrorHandling(apiRequest); + return new GetRequestsResponse { Requests = response.Embedded.Requests.ToList() }; + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Constants/ApiEndpoints.cs b/Apps.Lionbridge/Constants/ApiEndpoints.cs index 3147bed..805e24b 100644 --- a/Apps.Lionbridge/Constants/ApiEndpoints.cs +++ b/Apps.Lionbridge/Constants/ApiEndpoints.cs @@ -4,4 +4,8 @@ public static class ApiEndpoints { public const string Providers = "/providers"; public const string Jobs = "/jobs"; + public const string Requests = "/requests"; + public const string Add = "/add"; + public const string Approve = "/approve"; + public const string Reject = "/reject"; } \ No newline at end of file diff --git a/Apps.Lionbridge/DataSourceHandlers/RequestDataSourceHandler.cs b/Apps.Lionbridge/DataSourceHandlers/RequestDataSourceHandler.cs new file mode 100644 index 0000000..537f54f --- /dev/null +++ b/Apps.Lionbridge/DataSourceHandlers/RequestDataSourceHandler.cs @@ -0,0 +1,45 @@ +using Apps.Lionbridge.Api; +using Apps.Lionbridge.Constants; +using Apps.Lionbridge.Models.Dtos; +using Apps.Lionbridge.Models.Requests.Request; +using Apps.Lionbridge.Models.Responses.Job; +using Apps.Lionbridge.Models.Responses.Request; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; +using Blackbird.Applications.Sdk.Common.Invocation; + +namespace Apps.Lionbridge.DataSourceHandlers; + +public class RequestDataSourceHandler : LionbridgeInvocable, IAsyncDataSourceHandler +{ + private readonly string? _jobId; + + public RequestDataSourceHandler(InvocationContext invocationContext, [ActionParameter] GetRequest request) : base( + invocationContext) + { + _jobId = request.JobId; + } + + public async Task> GetDataAsync(DataSourceContext context, + CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(_jobId)) + { + throw new InvalidOperationException("You should input Job ID first"); + } + + var endpoint = $"{ApiEndpoints.Jobs}/{_jobId}{ApiEndpoints.Requests}"; + var request = new LionbridgeRequest(endpoint); + var response = await Client.ExecuteWithErrorHandling(request); + + return response.Embedded.Requests.Where(job => + context.SearchString == null || + job.RequestName.Contains(context.SearchString, StringComparison.OrdinalIgnoreCase)) + .ToDictionary(job => job.RequestId, BuildReadableName); + } + + private string BuildReadableName(RequestDto requestDto) + { + return $"{requestDto.RequestName} [{requestDto.TargetNativeLanguageCode}]"; + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Dtos/RequestDto.cs b/Apps.Lionbridge/Models/Dtos/RequestDto.cs new file mode 100644 index 0000000..6d1b1cc --- /dev/null +++ b/Apps.Lionbridge/Models/Dtos/RequestDto.cs @@ -0,0 +1,39 @@ +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Lionbridge.Models.Dtos; + +public class RequestDto +{ + [Display("Request ID")] + public string RequestId { get; set; } + + [Display("Job ID")] + public string JobId { get; set; } + + [Display("Status code")] + public string StatusCode { get; set; } + + [Display("Has error")] + public bool HasError { get; set; } + + [Display("Target native language")] + public string TargetNativeLanguageCode { get; set; } + + [Display("Created date")] + public DateTime CreatedDate { get; set; } + + [Display("Modified date")] + public DateTime ModifiedDate { get; set; } + + [Display("Word count")] + public int WordCount { get; set; } + + [Display("Request name")] + public string RequestName { get; set; } + + [Display("Source native ID")] + public string SourceNativeId { get; set; } + + [Display("Source native language")] + public string SourceNativeLanguageCode { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/Request/AddRequestModel.cs b/Apps.Lionbridge/Models/Requests/Request/AddRequestModel.cs new file mode 100644 index 0000000..66a7b2a --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/Request/AddRequestModel.cs @@ -0,0 +1,33 @@ +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Lionbridge.Models.Requests.Request; + +public class AddRequestModel +{ + [Display("Request name")] + public string RequestName { get; set; } + + [Display("Source native ID")] + public string SourceNativeId { get; set; } + + [Display("Source native language")] + public string SourceNativeLanguageCode { get; set; } + + [Display("Target native IDs")] + public IEnumerable? TargetNativeIds { get; set; } + + [Display("Target native languages")] + public IEnumerable TargetNativeLanguageCodes { get; set; } + + [Display("Word count")] + public int? WordCount { get; set; } = 0; + + [Display("Field names")] + public IEnumerable? FieldNames { get; set; } + + [Display("Field values")] + public IEnumerable? FieldValues { get; set; } + + [Display("Field comments")] + public IEnumerable? FieldComments { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/Request/GetRequest.cs b/Apps.Lionbridge/Models/Requests/Request/GetRequest.cs new file mode 100644 index 0000000..131b60d --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/Request/GetRequest.cs @@ -0,0 +1,14 @@ +using Apps.Lionbridge.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Lionbridge.Models.Requests.Request; + +public class GetRequest +{ + [Display("Job ID"), DataSource(typeof(JobDataSourceHandler))] + public string JobId { get; set; } + + [Display("Request ID"), DataSource(typeof(RequestDataSourceHandler))] + public string RequestId { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/Request/GetRequests.cs b/Apps.Lionbridge/Models/Requests/Request/GetRequests.cs new file mode 100644 index 0000000..4508128 --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/Request/GetRequests.cs @@ -0,0 +1,14 @@ +using Apps.Lionbridge.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Lionbridge.Models.Requests.Request; + +public class GetRequests +{ + [Display("Job ID"), DataSource(typeof(JobDataSourceHandler))] + public string JobId { get; set; } + + [Display("Request IDs"), DataSource(typeof(RequestDataSourceHandler))] + public IEnumerable RequestIds { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/Request/UpdateContentRequest.cs b/Apps.Lionbridge/Models/Requests/Request/UpdateContentRequest.cs new file mode 100644 index 0000000..ca88e1e --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/Request/UpdateContentRequest.cs @@ -0,0 +1,15 @@ +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Lionbridge.Models.Requests.Request; + +public class UpdateContentRequest +{ + [Display("Field names")] + public IEnumerable? FieldNames { get; set; } + + [Display("Field values")] + public IEnumerable? FieldValues { get; set; } + + [Display("Field comments")] + public IEnumerable? FieldComments { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Responses/Request/GetRequestsResponse.cs b/Apps.Lionbridge/Models/Responses/Request/GetRequestsResponse.cs new file mode 100644 index 0000000..8e90e45 --- /dev/null +++ b/Apps.Lionbridge/Models/Responses/Request/GetRequestsResponse.cs @@ -0,0 +1,8 @@ +using Apps.Lionbridge.Models.Dtos; + +namespace Apps.Lionbridge.Models.Responses.Request; + +public class GetRequestsResponse +{ + public List Requests { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Responses/Request/RequestsResponse.cs b/Apps.Lionbridge/Models/Responses/Request/RequestsResponse.cs new file mode 100644 index 0000000..c2ab1fe --- /dev/null +++ b/Apps.Lionbridge/Models/Responses/Request/RequestsResponse.cs @@ -0,0 +1,9 @@ +using Apps.Lionbridge.Models.Dtos; + +namespace Apps.Lionbridge.Models.Responses.Request; + +public class RequestsResponse : EmbeddedItemsWrapper +{ +} + +public record RequestsWrapper(IEnumerable Requests); \ No newline at end of file From 4f7bd5acff58b98ff9822f5b0a455253a9e591e0 Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Thu, 14 Mar 2024 15:58:51 +0200 Subject: [PATCH 07/55] Updates into request actions --- Apps.Lionbridge/Actions/RequestActions.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index e2461de..d69ce9a 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -19,7 +19,18 @@ public class RequestActions(InvocationContext invocationContext) : LionbridgeInv public async Task CreateRequest([ActionParameter] AddRequestModel request, [ActionParameter] GetJobRequest jobRequest) { var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Add, Method.Post) - .WithJsonBody(request); + .WithJsonBody(new + { + requestName = request.RequestName, + sourceNativeId = request.SourceNativeId, + sourceNativeLanguageCode = request.SourceNativeLanguageCode, + targetNativeIds = request.TargetNativeIds, + targetNativeLanguageCodes = request.TargetNativeLanguageCodes, + wordCount = request.WordCount, + fieldNames = request.FieldNames, + fieldValues = request.FieldValues, + fieldComments = request.FieldComments + }); return await Client.ExecuteWithErrorHandling(apiRequest); } @@ -27,14 +38,14 @@ public async Task CreateRequest([ActionParameter] AddRequestModel re [Action("Get request", Description = "Get a translation request.")] public async Task GetRequest([ActionParameter] GetRequest request) { - var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Requests}/{request.RequestId}"); + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}" + $"{ApiEndpoints.Requests}/{request.RequestId}"); return await Client.ExecuteWithErrorHandling(apiRequest); } [Action("Delete request", Description = "Delete a translation request.")] public async Task DeleteRequest([ActionParameter] GetRequest request) { - var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Requests}/{request.RequestId}", Method.Delete); + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}" + $"{ApiEndpoints.Requests}/{request.RequestId}", Method.Delete); await Client.ExecuteWithErrorHandling(apiRequest); } From ee7ad878a8a8fe05bcd3383a32c3ae022e4121bc Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Thu, 14 Mar 2024 16:04:15 +0200 Subject: [PATCH 08/55] Updates into request actions --- Apps.Lionbridge/Actions/RequestActions.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index d69ce9a..05d2783 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -16,7 +16,7 @@ namespace Apps.Lionbridge.Actions; public class RequestActions(InvocationContext invocationContext) : LionbridgeInvocable(invocationContext) { [Action("Create request", Description = "Create a new translation request.")] - public async Task CreateRequest([ActionParameter] AddRequestModel request, [ActionParameter] GetJobRequest jobRequest) + public async Task CreateRequest([ActionParameter] AddRequestModel request, [ActionParameter] GetJobRequest jobRequest) { var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Add, Method.Post) .WithJsonBody(new @@ -32,7 +32,8 @@ public async Task CreateRequest([ActionParameter] AddRequestModel re fieldComments = request.FieldComments }); - return await Client.ExecuteWithErrorHandling(apiRequest); + var response = await Client.ExecuteWithErrorHandling(apiRequest); + return new GetRequestsResponse { Requests = response.Embedded.Requests.ToList() }; } [Action("Get request", Description = "Get a translation request.")] From 5119d40352e10691e00ba444668d82ed1d7fa1bc Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Thu, 14 Mar 2024 16:09:01 +0200 Subject: [PATCH 09/55] Divided Create request action into two --- Apps.Lionbridge/Actions/RequestActions.cs | 25 ++++++++++++-- .../Requests/Request/AddRequestModel.cs | 4 +-- .../Requests/Request/AddRequestsModel.cs | 33 +++++++++++++++++++ 3 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 Apps.Lionbridge/Models/Requests/Request/AddRequestsModel.cs diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index 05d2783..4f6f3af 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -15,8 +15,8 @@ namespace Apps.Lionbridge.Actions; [ActionList] public class RequestActions(InvocationContext invocationContext) : LionbridgeInvocable(invocationContext) { - [Action("Create request", Description = "Create a new translation request.")] - public async Task CreateRequest([ActionParameter] AddRequestModel request, [ActionParameter] GetJobRequest jobRequest) + [Action("Create requests", Description = "Create a new translation requests.")] + public async Task CreateRequest([ActionParameter] AddRequestsModel request, [ActionParameter] GetJobRequest jobRequest) { var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Add, Method.Post) .WithJsonBody(new @@ -36,6 +36,27 @@ public async Task CreateRequest([ActionParameter] AddReques return new GetRequestsResponse { Requests = response.Embedded.Requests.ToList() }; } + [Action("Create request", Description = "Create a new translation request.")] + public async Task CreateSingleRequest([ActionParameter] AddRequestModel request, [ActionParameter] GetJobRequest jobRequest) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Add, Method.Post) + .WithJsonBody(new + { + requestName = request.RequestName, + sourceNativeId = request.SourceNativeId, + sourceNativeLanguageCode = request.SourceNativeLanguageCode, + targetNativeIds = request.TargetNativeIds, + targetNativeLanguageCodes = new List { request.TargetNativeLanguage }, + wordCount = request.WordCount, + fieldNames = request.FieldNames, + fieldValues = request.FieldValues, + fieldComments = request.FieldComments + }); + + var response = await Client.ExecuteWithErrorHandling(apiRequest); + return response.Embedded.Requests.First(); + } + [Action("Get request", Description = "Get a translation request.")] public async Task GetRequest([ActionParameter] GetRequest request) { diff --git a/Apps.Lionbridge/Models/Requests/Request/AddRequestModel.cs b/Apps.Lionbridge/Models/Requests/Request/AddRequestModel.cs index 66a7b2a..3f8bcba 100644 --- a/Apps.Lionbridge/Models/Requests/Request/AddRequestModel.cs +++ b/Apps.Lionbridge/Models/Requests/Request/AddRequestModel.cs @@ -16,8 +16,8 @@ public class AddRequestModel [Display("Target native IDs")] public IEnumerable? TargetNativeIds { get; set; } - [Display("Target native languages")] - public IEnumerable TargetNativeLanguageCodes { get; set; } + [Display("Target native language")] + public string TargetNativeLanguage { get; set; } [Display("Word count")] public int? WordCount { get; set; } = 0; diff --git a/Apps.Lionbridge/Models/Requests/Request/AddRequestsModel.cs b/Apps.Lionbridge/Models/Requests/Request/AddRequestsModel.cs new file mode 100644 index 0000000..75e5856 --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/Request/AddRequestsModel.cs @@ -0,0 +1,33 @@ +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Lionbridge.Models.Requests.Request; + +public class AddRequestsModel +{ + [Display("Request name")] + public string RequestName { get; set; } + + [Display("Source native ID")] + public string SourceNativeId { get; set; } + + [Display("Source native language")] + public string SourceNativeLanguageCode { get; set; } + + [Display("Target native IDs")] + public IEnumerable? TargetNativeIds { get; set; } + + [Display("Target native languages")] + public IEnumerable TargetNativeLanguageCodes { get; set; } + + [Display("Word count")] + public int? WordCount { get; set; } = 0; + + [Display("Field names")] + public IEnumerable? FieldNames { get; set; } + + [Display("Field values")] + public IEnumerable? FieldValues { get; set; } + + [Display("Field comments")] + public IEnumerable? FieldComments { get; set; } +} \ No newline at end of file From 509fbcad32a9b9efc833133c7536e6a4efc3d375 Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Thu, 14 Mar 2024 16:13:16 +0200 Subject: [PATCH 10/55] Updated Update request content --- Apps.Lionbridge/Actions/RequestActions.cs | 2 +- Apps.Lionbridge/Constants/ApiEndpoints.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index 4f6f3af..ff73cb3 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -98,7 +98,7 @@ public async Task RejectRequest([ActionParameter] GetRequests request) [Action("Update request content", Description = "Update a translation request content")] public async Task UpdateRequestContent([ActionParameter]GetRequests request, [ActionParameter] UpdateContentRequest updateRequestContentModel) { - var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}" + ApiEndpoints.Requests, Method.Put) + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}" + ApiEndpoints.Requests + ApiEndpoints.UpdateContent, Method.Put) .WithJsonBody(new { requestIds = request.RequestIds, diff --git a/Apps.Lionbridge/Constants/ApiEndpoints.cs b/Apps.Lionbridge/Constants/ApiEndpoints.cs index 805e24b..542bf44 100644 --- a/Apps.Lionbridge/Constants/ApiEndpoints.cs +++ b/Apps.Lionbridge/Constants/ApiEndpoints.cs @@ -8,4 +8,6 @@ public static class ApiEndpoints public const string Add = "/add"; public const string Approve = "/approve"; public const string Reject = "/reject"; + public const string UpdateContent = "/updatecontent"; + public const string UpdateFileContent = "/updatefilecontent"; } \ No newline at end of file From ca64c516465b083e716cf5ac58ec208210364386 Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Thu, 14 Mar 2024 16:23:26 +0200 Subject: [PATCH 11/55] Updated Get requests action --- Apps.Lionbridge/Actions/RequestActions.cs | 154 +++++++++++------- .../Requests/Request/GetRequestsAsOptional.cs | 14 ++ 2 files changed, 111 insertions(+), 57 deletions(-) create mode 100644 Apps.Lionbridge/Models/Requests/Request/GetRequestsAsOptional.cs diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index ff73cb3..5a92c6d 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -15,98 +15,138 @@ namespace Apps.Lionbridge.Actions; [ActionList] public class RequestActions(InvocationContext invocationContext) : LionbridgeInvocable(invocationContext) { + [Action("Get requests", Description = "Get translation requests.")] + public async Task GetRequests([ActionParameter] GetRequestsAsOptional jobRequest) + { + RestRequest apiRequest = null; + if (jobRequest.RequestIds != null && jobRequest.RequestIds.Any()) + { + apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests, + Method.Put) + .WithJsonBody(new + { + requestIds = jobRequest.RequestIds.ToArray() + }); + } + else + { + apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests, + Method.Get); + } + + var response = await Client.ExecuteWithErrorHandling(apiRequest); + return new GetRequestsResponse { Requests = response.Embedded.Requests.ToList() }; + } + [Action("Create requests", Description = "Create a new translation requests.")] - public async Task CreateRequest([ActionParameter] AddRequestsModel request, [ActionParameter] GetJobRequest jobRequest) + public async Task CreateRequest([ActionParameter] AddRequestsModel request, + [ActionParameter] GetJobRequest jobRequest) { - var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Add, Method.Post) - .WithJsonBody(new - { - requestName = request.RequestName, - sourceNativeId = request.SourceNativeId, - sourceNativeLanguageCode = request.SourceNativeLanguageCode, - targetNativeIds = request.TargetNativeIds, - targetNativeLanguageCodes = request.TargetNativeLanguageCodes, - wordCount = request.WordCount, - fieldNames = request.FieldNames, - fieldValues = request.FieldValues, - fieldComments = request.FieldComments - }); + var apiRequest = + new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Add, + Method.Post) + .WithJsonBody(new + { + requestName = request.RequestName, + sourceNativeId = request.SourceNativeId, + sourceNativeLanguageCode = request.SourceNativeLanguageCode, + targetNativeIds = request.TargetNativeIds, + targetNativeLanguageCodes = request.TargetNativeLanguageCodes, + wordCount = request.WordCount, + fieldNames = request.FieldNames, + fieldValues = request.FieldValues, + fieldComments = request.FieldComments + }); var response = await Client.ExecuteWithErrorHandling(apiRequest); return new GetRequestsResponse { Requests = response.Embedded.Requests.ToList() }; } - + [Action("Create request", Description = "Create a new translation request.")] - public async Task CreateSingleRequest([ActionParameter] AddRequestModel request, [ActionParameter] GetJobRequest jobRequest) + public async Task CreateSingleRequest([ActionParameter] AddRequestModel request, + [ActionParameter] GetJobRequest jobRequest) { - var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Add, Method.Post) - .WithJsonBody(new - { - requestName = request.RequestName, - sourceNativeId = request.SourceNativeId, - sourceNativeLanguageCode = request.SourceNativeLanguageCode, - targetNativeIds = request.TargetNativeIds, - targetNativeLanguageCodes = new List { request.TargetNativeLanguage }, - wordCount = request.WordCount, - fieldNames = request.FieldNames, - fieldValues = request.FieldValues, - fieldComments = request.FieldComments - }); + var apiRequest = + new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Add, + Method.Post) + .WithJsonBody(new + { + requestName = request.RequestName, + sourceNativeId = request.SourceNativeId, + sourceNativeLanguageCode = request.SourceNativeLanguageCode, + targetNativeIds = request.TargetNativeIds, + targetNativeLanguageCodes = new List { request.TargetNativeLanguage }, + wordCount = request.WordCount, + fieldNames = request.FieldNames, + fieldValues = request.FieldValues, + fieldComments = request.FieldComments + }); var response = await Client.ExecuteWithErrorHandling(apiRequest); return response.Embedded.Requests.First(); } - + [Action("Get request", Description = "Get a translation request.")] public async Task GetRequest([ActionParameter] GetRequest request) { - var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}" + $"{ApiEndpoints.Requests}/{request.RequestId}"); + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}" + + $"{ApiEndpoints.Requests}/{request.RequestId}"); return await Client.ExecuteWithErrorHandling(apiRequest); } [Action("Delete request", Description = "Delete a translation request.")] public async Task DeleteRequest([ActionParameter] GetRequest request) { - var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}" + $"{ApiEndpoints.Requests}/{request.RequestId}", Method.Delete); + var apiRequest = + new LionbridgeRequest( + $"{ApiEndpoints.Jobs}/{request.JobId}" + $"{ApiEndpoints.Requests}/{request.RequestId}", Method.Delete); await Client.ExecuteWithErrorHandling(apiRequest); } - + [Action("Approve request", Description = "Approve a translation request")] public async Task ApproveRequest([ActionParameter] GetRequests request) { - var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Approve, Method.Put) - .WithJsonBody(new - { - requestIds = request.RequestIds - }); - + var apiRequest = + new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Approve, + Method.Put) + .WithJsonBody(new + { + requestIds = request.RequestIds + }); + await Client.ExecuteWithErrorHandling(apiRequest); } - + [Action("Reject request", Description = "Reject a translation request")] public async Task RejectRequest([ActionParameter] GetRequests request) { - var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Reject, Method.Put) - .WithJsonBody(new - { - requestIds = request.RequestIds - }); - + var apiRequest = + new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Reject, + Method.Put) + .WithJsonBody(new + { + requestIds = request.RequestIds + }); + await Client.ExecuteWithErrorHandling(apiRequest); } - + [Action("Update request content", Description = "Update a translation request content")] - public async Task UpdateRequestContent([ActionParameter]GetRequests request, [ActionParameter] UpdateContentRequest updateRequestContentModel) + public async Task UpdateRequestContent([ActionParameter] GetRequests request, + [ActionParameter] UpdateContentRequest updateRequestContentModel) { - var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}" + ApiEndpoints.Requests + ApiEndpoints.UpdateContent, Method.Put) - .WithJsonBody(new - { - requestIds = request.RequestIds, - fieldNames = updateRequestContentModel.FieldNames, - fieldValues = updateRequestContentModel.FieldValues, - fieldComments = updateRequestContentModel.FieldComments - }); - + var apiRequest = + new LionbridgeRequest( + $"{ApiEndpoints.Jobs}/{request.JobId}" + ApiEndpoints.Requests + ApiEndpoints.UpdateContent, + Method.Put) + .WithJsonBody(new + { + requestIds = request.RequestIds, + fieldNames = updateRequestContentModel.FieldNames, + fieldValues = updateRequestContentModel.FieldValues, + fieldComments = updateRequestContentModel.FieldComments + }); + var response = await Client.ExecuteWithErrorHandling(apiRequest); return new GetRequestsResponse { Requests = response.Embedded.Requests.ToList() }; } diff --git a/Apps.Lionbridge/Models/Requests/Request/GetRequestsAsOptional.cs b/Apps.Lionbridge/Models/Requests/Request/GetRequestsAsOptional.cs new file mode 100644 index 0000000..6f1f696 --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/Request/GetRequestsAsOptional.cs @@ -0,0 +1,14 @@ +using Apps.Lionbridge.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Lionbridge.Models.Requests.Request; + +public class GetRequestsAsOptional +{ + [Display("Job ID"), DataSource(typeof(JobDataSourceHandler))] + public string JobId { get; set; } + + [Display("Request IDs"), DataSource(typeof(RequestDataSourceHandler))] + public IEnumerable? RequestIds { get; set; } +} \ No newline at end of file From ae3e8c949fa6d6dfe49d843f142ac233c98f343c Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Thu, 14 Mar 2024 16:27:59 +0200 Subject: [PATCH 12/55] Changes in delete request --- Apps.Lionbridge/Actions/RequestActions.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index 5a92c6d..35d4942 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -18,7 +18,7 @@ public class RequestActions(InvocationContext invocationContext) : LionbridgeInv [Action("Get requests", Description = "Get translation requests.")] public async Task GetRequests([ActionParameter] GetRequestsAsOptional jobRequest) { - RestRequest apiRequest = null; + RestRequest apiRequest; if (jobRequest.RequestIds != null && jobRequest.RequestIds.Any()) { apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests, @@ -95,12 +95,13 @@ public async Task GetRequest([ActionParameter] GetRequest request) } [Action("Delete request", Description = "Delete a translation request.")] - public async Task DeleteRequest([ActionParameter] GetRequest request) + public async Task DeleteRequest([ActionParameter] GetRequest request) { var apiRequest = new LionbridgeRequest( $"{ApiEndpoints.Jobs}/{request.JobId}" + $"{ApiEndpoints.Requests}/{request.RequestId}", Method.Delete); - await Client.ExecuteWithErrorHandling(apiRequest); + + return await Client.ExecuteWithErrorHandling(apiRequest); } [Action("Approve request", Description = "Approve a translation request")] From bc5809dceafa501126cc7a733611050c20bc1020 Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Thu, 14 Mar 2024 16:33:43 +0200 Subject: [PATCH 13/55] Updated in Get requests --- Apps.Lionbridge/Actions/RequestActions.cs | 25 ++++++++--------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index 35d4942..2449d8a 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -18,24 +18,17 @@ public class RequestActions(InvocationContext invocationContext) : LionbridgeInv [Action("Get requests", Description = "Get translation requests.")] public async Task GetRequests([ActionParameter] GetRequestsAsOptional jobRequest) { - RestRequest apiRequest; - if (jobRequest.RequestIds != null && jobRequest.RequestIds.Any()) - { - apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests, - Method.Put) - .WithJsonBody(new - { - requestIds = jobRequest.RequestIds.ToArray() - }); - } - else - { - apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests, - Method.Get); - } + RestRequest apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests, + Method.Get); var response = await Client.ExecuteWithErrorHandling(apiRequest); - return new GetRequestsResponse { Requests = response.Embedded.Requests.ToList() }; + var requests = response.Embedded.Requests.ToList(); + if(jobRequest.RequestIds != null && jobRequest.RequestIds.Any()) + { + requests = requests.Where(x => jobRequest.RequestIds.Contains(x.RequestId)).ToList(); + } + + return new GetRequestsResponse { Requests = requests }; } [Action("Create requests", Description = "Create a new translation requests.")] From 8abfd1ad04b303dd44b865d42a9badfbfd253e60 Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Thu, 14 Mar 2024 19:11:45 +0200 Subject: [PATCH 14/55] Improved Create request action --- Apps.Lionbridge/Actions/JobActions.cs | 21 +++++- Apps.Lionbridge/Actions/RequestActions.cs | 70 +++++++++---------- Apps.Lionbridge/Constants/ApiEndpoints.cs | 1 + .../Models/Dtos/UpdateJobApiRequest.cs | 3 + .../Models/Requests/Job/UpdateJobRequest.cs | 8 +++ ...RequestModel.cs => AddRequestBaseModel.cs} | 19 ++--- .../Requests/Request/AddSourceRequestModel.cs | 16 +++++ .../TranslationContentResponse.cs | 12 ++++ 8 files changed, 102 insertions(+), 48 deletions(-) rename Apps.Lionbridge/Models/Requests/Request/{AddRequestModel.cs => AddRequestBaseModel.cs} (57%) create mode 100644 Apps.Lionbridge/Models/Requests/Request/AddSourceRequestModel.cs create mode 100644 Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentResponse.cs diff --git a/Apps.Lionbridge/Actions/JobActions.cs b/Apps.Lionbridge/Actions/JobActions.cs index 793b78d..97a475e 100644 --- a/Apps.Lionbridge/Actions/JobActions.cs +++ b/Apps.Lionbridge/Actions/JobActions.cs @@ -70,6 +70,11 @@ public async Task UpdateJob([ActionParameter] GetJobRequest jobRequest, apiUpdateRequest.ExtendedMetadata = EnumerableExtensions.ToDictionary(request.MetadataKeys, request.MetadataValues); } + if(request.LabelKeys != null && request.LabelValues != null) + { + apiUpdateRequest.Labels = EnumerableExtensions.ToDictionary(request.LabelKeys, request.LabelValues); + } + if(request.DueDate != null) { apiUpdateRequest.DueDate = request.DueDate.Value.ToString("yyyy-MM-ddTHH:mm:ssZ"); @@ -100,7 +105,7 @@ public async Task UpdateJob([ActionParameter] GetJobRequest jobRequest, apiUpdateRequest.ServiceType = request.ServiceType; } - var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}", Method.Put) + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}", Method.Patch) .WithJsonBody(apiUpdateRequest); return await Client.ExecuteWithErrorHandling(apiRequest); @@ -128,4 +133,18 @@ public async Task UnarchiveJob([ActionParameter] GetJobRequest request) var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}/unarchive", Method.Put); return await Client.ExecuteWithErrorHandling(apiRequest); } + + [Action("Complete job", Description = "Complete a translation job")] + public async Task CompleteJob([ActionParameter] GetJobRequest request) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}/complete", Method.Put); + return await Client.ExecuteWithErrorHandling(apiRequest); + } + + [Action("Intranslate job", Description = "Set job status to IN_TRANSLATION. Allows further translations from being imported again. Only valid when job is currently COMPLETED")] + public async Task IntranslateJob([ActionParameter] GetJobRequest request) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}/intranslation", Method.Put); + return await Client.ExecuteWithErrorHandling(apiRequest); + } } \ No newline at end of file diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index 2449d8a..b7fb2de 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -1,9 +1,11 @@ using Apps.Lionbridge.Api; using Apps.Lionbridge.Constants; +using Apps.Lionbridge.Extensions; using Apps.Lionbridge.Models.Dtos; using Apps.Lionbridge.Models.Requests.Job; using Apps.Lionbridge.Models.Requests.Request; using Apps.Lionbridge.Models.Responses.Request; +using Apps.Lionbridge.Models.Responses.TranslationContent; using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Actions; using Blackbird.Applications.Sdk.Common.Invocation; @@ -18,47 +20,27 @@ public class RequestActions(InvocationContext invocationContext) : LionbridgeInv [Action("Get requests", Description = "Get translation requests.")] public async Task GetRequests([ActionParameter] GetRequestsAsOptional jobRequest) { - RestRequest apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests, + RestRequest apiRequest = new LionbridgeRequest( + $"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests, Method.Get); var response = await Client.ExecuteWithErrorHandling(apiRequest); var requests = response.Embedded.Requests.ToList(); - if(jobRequest.RequestIds != null && jobRequest.RequestIds.Any()) + if (jobRequest.RequestIds != null && jobRequest.RequestIds.Any()) { requests = requests.Where(x => jobRequest.RequestIds.Contains(x.RequestId)).ToList(); } - - return new GetRequestsResponse { Requests = requests }; - } - - [Action("Create requests", Description = "Create a new translation requests.")] - public async Task CreateRequest([ActionParameter] AddRequestsModel request, - [ActionParameter] GetJobRequest jobRequest) - { - var apiRequest = - new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Add, - Method.Post) - .WithJsonBody(new - { - requestName = request.RequestName, - sourceNativeId = request.SourceNativeId, - sourceNativeLanguageCode = request.SourceNativeLanguageCode, - targetNativeIds = request.TargetNativeIds, - targetNativeLanguageCodes = request.TargetNativeLanguageCodes, - wordCount = request.WordCount, - fieldNames = request.FieldNames, - fieldValues = request.FieldValues, - fieldComments = request.FieldComments - }); - var response = await Client.ExecuteWithErrorHandling(apiRequest); - return new GetRequestsResponse { Requests = response.Embedded.Requests.ToList() }; + return new GetRequestsResponse { Requests = requests }; } - [Action("Create request", Description = "Create a new translation request.")] - public async Task CreateSingleRequest([ActionParameter] AddRequestModel request, + [Action("Create source content request", Description = "Create a new translation request.")] + public async Task CreateSingleRequest([ActionParameter] AddSourceRequestModel request, [ActionParameter] GetJobRequest jobRequest) { + string sourceContentId = await CreateTranslationContent(request.FieldsKeys, request.FieldsValues); + + var metadata = EnumerableExtensions.ToDictionary(request.MetadataKeys, request.MetadataValues); var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Add, Method.Post) @@ -70,9 +52,8 @@ public async Task CreateSingleRequest([ActionParameter] AddRequestMo targetNativeIds = request.TargetNativeIds, targetNativeLanguageCodes = new List { request.TargetNativeLanguage }, wordCount = request.WordCount, - fieldNames = request.FieldNames, - fieldValues = request.FieldValues, - fieldComments = request.FieldComments + extendedMetadata = metadata, + sourcecontentId = sourceContentId }); var response = await Client.ExecuteWithErrorHandling(apiRequest); @@ -93,7 +74,7 @@ public async Task DeleteRequest([ActionParameter] GetRequest request var apiRequest = new LionbridgeRequest( $"{ApiEndpoints.Jobs}/{request.JobId}" + $"{ApiEndpoints.Requests}/{request.RequestId}", Method.Delete); - + return await Client.ExecuteWithErrorHandling(apiRequest); } @@ -129,10 +110,13 @@ public async Task RejectRequest([ActionParameter] GetRequests request) public async Task UpdateRequestContent([ActionParameter] GetRequests request, [ActionParameter] UpdateContentRequest updateRequestContentModel) { + throw new NotImplementedException("This action is not implemented yet."); + var apiRequest = new LionbridgeRequest( - $"{ApiEndpoints.Jobs}/{request.JobId}" + ApiEndpoints.Requests + ApiEndpoints.UpdateContent, - Method.Put) + $"{ApiEndpoints.Jobs}/{request.JobId}{ApiEndpoints.Requests}/{request}" + ApiEndpoints.Requests + + ApiEndpoints.UpdateContent, + Method.Patch) .WithJsonBody(new { requestIds = request.RequestIds, @@ -144,4 +128,20 @@ public async Task UpdateRequestContent([ActionParameter] Ge var response = await Client.ExecuteWithErrorHandling(apiRequest); return new GetRequestsResponse { Requests = response.Embedded.Requests.ToList() }; } + + private async Task CreateTranslationContent(IEnumerable keys, IEnumerable values) + { + var dictionary = EnumerableExtensions.ToDictionary(keys, values); + var listOfKeyValuePairs = dictionary.Select(x => new KeyValuePair(x.Key, x.Value)) + .ToList(); + + var apiRequest = new LionbridgeRequest(ApiEndpoints.SourceContent, Method.Post) + .WithJsonBody(new + { + fields = listOfKeyValuePairs + }); + + var response = await Client.ExecuteWithErrorHandling(apiRequest); + return response.SourceContentId; + } } \ No newline at end of file diff --git a/Apps.Lionbridge/Constants/ApiEndpoints.cs b/Apps.Lionbridge/Constants/ApiEndpoints.cs index 542bf44..2c05eb3 100644 --- a/Apps.Lionbridge/Constants/ApiEndpoints.cs +++ b/Apps.Lionbridge/Constants/ApiEndpoints.cs @@ -10,4 +10,5 @@ public static class ApiEndpoints public const string Reject = "/reject"; public const string UpdateContent = "/updatecontent"; public const string UpdateFileContent = "/updatefilecontent"; + public const string SourceContent = "/sourcecontent"; } \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Dtos/UpdateJobApiRequest.cs b/Apps.Lionbridge/Models/Dtos/UpdateJobApiRequest.cs index 53fb197..2b74f67 100644 --- a/Apps.Lionbridge/Models/Dtos/UpdateJobApiRequest.cs +++ b/Apps.Lionbridge/Models/Dtos/UpdateJobApiRequest.cs @@ -38,4 +38,7 @@ public class UpdateJobApiRequest [Display("Extended metadata")] public Dictionary ExtendedMetadata { get; set; } + + [Display("Labels")] + public Dictionary Labels { get; set; } } \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/Job/UpdateJobRequest.cs b/Apps.Lionbridge/Models/Requests/Job/UpdateJobRequest.cs index 99dc695..e1f0268 100644 --- a/Apps.Lionbridge/Models/Requests/Job/UpdateJobRequest.cs +++ b/Apps.Lionbridge/Models/Requests/Job/UpdateJobRequest.cs @@ -35,4 +35,12 @@ public class UpdateJobRequest [Display("Metadata values", Description = "Extended metadata values. For each specified value, a respective " + "key should be added in the 'Metadata keys' input parameter.")] public IEnumerable? MetadataValues { get; set; } + + [Display("Label keys", Description = "Label keys. For each specified key, a respective value should be added in " + + "the 'Label values' input parameter.")] + public IEnumerable? LabelKeys { get; set; } + + [Display("Label values", Description = "Label values. For each specified value, a respective key should be added " + + "in the 'Label keys' input parameter.")] + public IEnumerable? LabelValues { get; set; } } \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/Request/AddRequestModel.cs b/Apps.Lionbridge/Models/Requests/Request/AddRequestBaseModel.cs similarity index 57% rename from Apps.Lionbridge/Models/Requests/Request/AddRequestModel.cs rename to Apps.Lionbridge/Models/Requests/Request/AddRequestBaseModel.cs index 3f8bcba..86b061c 100644 --- a/Apps.Lionbridge/Models/Requests/Request/AddRequestModel.cs +++ b/Apps.Lionbridge/Models/Requests/Request/AddRequestBaseModel.cs @@ -1,8 +1,9 @@ using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Files; namespace Apps.Lionbridge.Models.Requests.Request; -public class AddRequestModel +public class AddRequestBaseModel { [Display("Request name")] public string RequestName { get; set; } @@ -16,18 +17,12 @@ public class AddRequestModel [Display("Target native IDs")] public IEnumerable? TargetNativeIds { get; set; } - [Display("Target native language")] - public string TargetNativeLanguage { get; set; } - [Display("Word count")] public int? WordCount { get; set; } = 0; + + [Display("Metadata keys", Description = "Metadata keys to be added to the request.")] + public IEnumerable? MetadataKeys { get; set; } - [Display("Field names")] - public IEnumerable? FieldNames { get; set; } - - [Display("Field values")] - public IEnumerable? FieldValues { get; set; } - - [Display("Field comments")] - public IEnumerable? FieldComments { get; set; } + [Display("Metadata values", Description = "Metadata values to be added to the request.")] + public IEnumerable? MetadataValues { get; set; } } \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/Request/AddSourceRequestModel.cs b/Apps.Lionbridge/Models/Requests/Request/AddSourceRequestModel.cs new file mode 100644 index 0000000..95b0794 --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/Request/AddSourceRequestModel.cs @@ -0,0 +1,16 @@ +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Files; + +namespace Apps.Lionbridge.Models.Requests.Request; + +public class AddSourceRequestModel : AddRequestBaseModel +{ + [Display("Target native language")] + public string TargetNativeLanguage { get; set; } + + [Display("Fields keys", Description = "Fields keys to be added to the request.")] + public IEnumerable FieldsKeys { get; set; } + + [Display("Fields values", Description = "Fields values to be added to the request.")] + public IEnumerable FieldsValues { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentResponse.cs b/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentResponse.cs new file mode 100644 index 0000000..24b0092 --- /dev/null +++ b/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentResponse.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Apps.Lionbridge.Models.Responses.TranslationContent; + +public class TranslationContentResponse +{ + [JsonProperty("sourcecontentId")] + public string SourceContentId { get; set; } + + [JsonProperty("fields")] + public List> Fields { get; set; } +} \ No newline at end of file From 30ab7cfdaa39e9292430e675e606e039454d8b6d Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Thu, 14 Mar 2024 19:19:38 +0200 Subject: [PATCH 15/55] Fixed deserialization bug --- .../TranslationContent/TranslationContentResponse.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentResponse.cs b/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentResponse.cs index 24b0092..0f4d04a 100644 --- a/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentResponse.cs +++ b/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentResponse.cs @@ -8,5 +8,14 @@ public class TranslationContentResponse public string SourceContentId { get; set; } [JsonProperty("fields")] - public List> Fields { get; set; } + public List Fields { get; set; } +} + +public class Field +{ + [JsonProperty("key")] + public string Key { get; set; } + + [JsonProperty("value")] + public string Value { get; set; } } \ No newline at end of file From 6d95ea55effefbc80758614c4b2f42ad20b28a1d Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Thu, 14 Mar 2024 22:13:13 +0200 Subject: [PATCH 16/55] Fixed 'Create source content request' action --- Apps.Lionbridge/Actions/RequestActions.cs | 8 +++---- Apps.Lionbridge/Api/LionbridgeClient.cs | 23 ++++++++++++------- Apps.Lionbridge/Models/Dtos/FieldDto.cs | 12 ++++++++++ .../TranslationContentResponse.cs | 14 +++-------- 4 files changed, 34 insertions(+), 23 deletions(-) create mode 100644 Apps.Lionbridge/Models/Dtos/FieldDto.cs diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index b7fb2de..5fb74be 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -38,7 +38,7 @@ public async Task GetRequests([ActionParameter] GetRequests public async Task CreateSingleRequest([ActionParameter] AddSourceRequestModel request, [ActionParameter] GetJobRequest jobRequest) { - string sourceContentId = await CreateTranslationContent(request.FieldsKeys, request.FieldsValues); + string sourceContentId = await CreateTranslationContent(jobRequest.JobId, request.FieldsKeys, request.FieldsValues); var metadata = EnumerableExtensions.ToDictionary(request.MetadataKeys, request.MetadataValues); var apiRequest = @@ -129,13 +129,13 @@ public async Task UpdateRequestContent([ActionParameter] Ge return new GetRequestsResponse { Requests = response.Embedded.Requests.ToList() }; } - private async Task CreateTranslationContent(IEnumerable keys, IEnumerable values) + private async Task CreateTranslationContent(string jobId, IEnumerable keys, IEnumerable values) { var dictionary = EnumerableExtensions.ToDictionary(keys, values); - var listOfKeyValuePairs = dictionary.Select(x => new KeyValuePair(x.Key, x.Value)) + var listOfKeyValuePairs = dictionary.Select(x => new FieldDto { Key = x.Key, Value = x.Value }) .ToList(); - var apiRequest = new LionbridgeRequest(ApiEndpoints.SourceContent, Method.Post) + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobId}{ApiEndpoints.SourceContent}", Method.Post) .WithJsonBody(new { fields = listOfKeyValuePairs diff --git a/Apps.Lionbridge/Api/LionbridgeClient.cs b/Apps.Lionbridge/Api/LionbridgeClient.cs index 8ebd7c3..e24009a 100644 --- a/Apps.Lionbridge/Api/LionbridgeClient.cs +++ b/Apps.Lionbridge/Api/LionbridgeClient.cs @@ -59,18 +59,25 @@ public override async Task ExecuteWithErrorHandling(RestRequest request) protected override Exception ConfigureErrorException(RestResponse response) { - if (string.IsNullOrWhiteSpace(response.Content)) - return new(response.StatusCode.ToString()); + try + { + if (string.IsNullOrWhiteSpace(response.Content)) + return new(response.StatusCode.ToString()); - if (!response.Content.IsJson()) - return new(response.Content); + if (!response.Content.IsJson()) + return new(response.Content); - var error = JsonConvert.DeserializeObject(response.Content, JsonSettings); + var error = JsonConvert.DeserializeObject(response.Content, JsonSettings); - if (error != null) - return new(error.Message); + if (error != null) + return new(error.Message); - return new(response.Content); + return new(response.Content); + } + catch (Exception e) + { + return new Exception($"Error was thrown while executing request, response content: {response.Content}; status code: {response.StatusCode}"); + } } private string GetAccessToken(IEnumerable authenticationCredentialsProviders) diff --git a/Apps.Lionbridge/Models/Dtos/FieldDto.cs b/Apps.Lionbridge/Models/Dtos/FieldDto.cs new file mode 100644 index 0000000..d50a1d7 --- /dev/null +++ b/Apps.Lionbridge/Models/Dtos/FieldDto.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Apps.Lionbridge.Models.Dtos; + +public class FieldDto +{ + [JsonProperty("key")] + public string Key { get; set; } + + [JsonProperty("value")] + public string Value { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentResponse.cs b/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentResponse.cs index 0f4d04a..0c5b4ed 100644 --- a/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentResponse.cs +++ b/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentResponse.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using Apps.Lionbridge.Models.Dtos; +using Newtonsoft.Json; namespace Apps.Lionbridge.Models.Responses.TranslationContent; @@ -8,14 +9,5 @@ public class TranslationContentResponse public string SourceContentId { get; set; } [JsonProperty("fields")] - public List Fields { get; set; } -} - -public class Field -{ - [JsonProperty("key")] - public string Key { get; set; } - - [JsonProperty("value")] - public string Value { get; set; } + public List Fields { get; set; } } \ No newline at end of file From 5c6ff1b0668c1051a36401b4072c491419ac31a7 Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Fri, 15 Mar 2024 11:45:17 +0200 Subject: [PATCH 17/55] Added file action and fixed update request action --- Apps.Lionbridge/Actions/RequestActions.cs | 105 ++++++++++++++---- Apps.Lionbridge/Constants/ApiEndpoints.cs | 1 + .../Requests/File/AddSourceFileRequest.cs | 22 ++++ .../Requests/Request/AddRequestBaseModel.cs | 1 - .../Requests/Request/UpdateRequestModel.cs | 33 ++++++ .../UploadFileAsMultipartResponse.cs | 18 +++ .../SourceFile/UploadSourceFileResponse.cs | 18 +++ 7 files changed, 177 insertions(+), 21 deletions(-) create mode 100644 Apps.Lionbridge/Models/Requests/File/AddSourceFileRequest.cs create mode 100644 Apps.Lionbridge/Models/Requests/Request/UpdateRequestModel.cs create mode 100644 Apps.Lionbridge/Models/Responses/SourceFile/UploadFileAsMultipartResponse.cs create mode 100644 Apps.Lionbridge/Models/Responses/SourceFile/UploadSourceFileResponse.cs diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index 5fb74be..e2da4d7 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -2,20 +2,25 @@ using Apps.Lionbridge.Constants; using Apps.Lionbridge.Extensions; using Apps.Lionbridge.Models.Dtos; +using Apps.Lionbridge.Models.Requests.File; using Apps.Lionbridge.Models.Requests.Job; using Apps.Lionbridge.Models.Requests.Request; using Apps.Lionbridge.Models.Responses.Request; +using Apps.Lionbridge.Models.Responses.SourceFile; using Apps.Lionbridge.Models.Responses.TranslationContent; using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Actions; +using Blackbird.Applications.Sdk.Common.Files; using Blackbird.Applications.Sdk.Common.Invocation; +using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces; using Blackbird.Applications.Sdk.Utils.Extensions.Http; using RestSharp; namespace Apps.Lionbridge.Actions; [ActionList] -public class RequestActions(InvocationContext invocationContext) : LionbridgeInvocable(invocationContext) +public class RequestActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) + : LionbridgeInvocable(invocationContext) { [Action("Get requests", Description = "Get translation requests.")] public async Task GetRequests([ActionParameter] GetRequestsAsOptional jobRequest) @@ -38,8 +43,9 @@ public async Task GetRequests([ActionParameter] GetRequests public async Task CreateSingleRequest([ActionParameter] AddSourceRequestModel request, [ActionParameter] GetJobRequest jobRequest) { - string sourceContentId = await CreateTranslationContent(jobRequest.JobId, request.FieldsKeys, request.FieldsValues); - + string sourceContentId = + await CreateTranslationContent(jobRequest.JobId, request.FieldsKeys, request.FieldsValues); + var metadata = EnumerableExtensions.ToDictionary(request.MetadataKeys, request.MetadataValues); var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Add, @@ -60,6 +66,33 @@ public async Task CreateSingleRequest([ActionParameter] AddSourceReq return response.Embedded.Requests.First(); } + [Action("Create source content request", Description = "Create a new translation request.")] + public async Task CreateFileRequest([ActionParameter] GetJobRequest jobRequest, + [ActionParameter] AddSourceFileRequest sourceFileRequest) + { + var uploadResponse = await UploadFmsFile(jobRequest.JobId, sourceFileRequest); + + var metadata = + EnumerableExtensions.ToDictionary(sourceFileRequest.MetadataKeys, sourceFileRequest.MetadataValues); + var apiRequest = + new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests + ApiEndpoints.Add, + Method.Post) + .WithJsonBody(new + { + requestName = sourceFileRequest.RequestName, + sourceNativeId = sourceFileRequest.SourceNativeId, + sourceNativeLanguageCode = sourceFileRequest.SourceNativeLanguageCode, + targetNativeIds = sourceFileRequest.TargetNativeIds, + targetNativeLanguageCodes = new List { sourceFileRequest.TargetNativeLanguage }, + wordCount = sourceFileRequest.WordCount, + extendedMetadata = metadata, + fmsFileId = uploadResponse.FmsFileId + }); + + var response = await Client.ExecuteWithErrorHandling(apiRequest); + return response.Embedded.Requests.First(); + } + [Action("Get request", Description = "Get a translation request.")] public async Task GetRequest([ActionParameter] GetRequest request) { @@ -107,41 +140,73 @@ public async Task RejectRequest([ActionParameter] GetRequests request) } [Action("Update request content", Description = "Update a translation request content")] - public async Task UpdateRequestContent([ActionParameter] GetRequests request, - [ActionParameter] UpdateContentRequest updateRequestContentModel) + public async Task UpdateRequestContent([ActionParameter] GetRequest request, + [ActionParameter] UpdateRequestModel updateRequestContentModel) { - throw new NotImplementedException("This action is not implemented yet."); - + string endpoint = $"{ApiEndpoints.Jobs}/{request.JobId}{ApiEndpoints.Requests}/{request}"; var apiRequest = - new LionbridgeRequest( - $"{ApiEndpoints.Jobs}/{request.JobId}{ApiEndpoints.Requests}/{request}" + ApiEndpoints.Requests + - ApiEndpoints.UpdateContent, - Method.Patch) + new LionbridgeRequest(endpoint, Method.Patch) .WithJsonBody(new { - requestIds = request.RequestIds, - fieldNames = updateRequestContentModel.FieldNames, - fieldValues = updateRequestContentModel.FieldValues, - fieldComments = updateRequestContentModel.FieldComments + requestName = updateRequestContentModel.RequestName, + sourceNativeId = updateRequestContentModel.SourceNativeId, + sourceNativeLanguageCode = updateRequestContentModel.SourceNativeLanguageCode, + targetNativeId = updateRequestContentModel.TargetNativeId, + targetNativeLanguageCode = updateRequestContentModel.TargetNativeLanguageCode, + extendedMetadata = EnumerableExtensions.ToDictionary(updateRequestContentModel.MetadataKeys, + updateRequestContentModel.MetadataValues), + fileId = updateRequestContentModel.FileId, + sourceContentId = updateRequestContentModel.SourceContentId }); - var response = await Client.ExecuteWithErrorHandling(apiRequest); - return new GetRequestsResponse { Requests = response.Embedded.Requests.ToList() }; + var response = await Client.ExecuteWithErrorHandling(apiRequest); + return response; } - private async Task CreateTranslationContent(string jobId, IEnumerable keys, IEnumerable values) + private async Task CreateTranslationContent(string jobId, IEnumerable keys, + IEnumerable values) { var dictionary = EnumerableExtensions.ToDictionary(keys, values); var listOfKeyValuePairs = dictionary.Select(x => new FieldDto { Key = x.Key, Value = x.Value }) .ToList(); - + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobId}{ApiEndpoints.SourceContent}", Method.Post) .WithJsonBody(new { fields = listOfKeyValuePairs }); - + var response = await Client.ExecuteWithErrorHandling(apiRequest); return response.SourceContentId; } + + private async Task UploadFmsFile(string jobId, AddSourceFileRequest fileRequest) + { + string fileName = fileRequest.FileName ?? fileRequest.File.Name; + + string endpoint = $"{ApiEndpoints.Jobs}/{jobId}{ApiEndpoints.SourceFiles}?fileName={fileName}"; + var apiRequest = new LionbridgeRequest(endpoint, Method.Post); + + var response = await Client.ExecuteWithErrorHandling(apiRequest); + + string fmsMultipartUrl = response.FmsPostMultipartUrl; + + var fileStream = await fileManagementClient.DownloadAsync(fileRequest.File); + var memoryStream = new MemoryStream(); + await fileStream.CopyToAsync(memoryStream); + + var bytes = memoryStream.ToArray(); + + var client = new RestClient(fmsMultipartUrl); + var request = new RestRequest(string.Empty, Method.Post); + request.AddFile("file", bytes, fileName); + + var uploadFileResponse = await client.ExecuteAsync(request); + if (!uploadFileResponse.IsSuccessful) + { + throw new Exception("Failed to upload file to FMS on second step"); + } + + return response; + } } \ No newline at end of file diff --git a/Apps.Lionbridge/Constants/ApiEndpoints.cs b/Apps.Lionbridge/Constants/ApiEndpoints.cs index 2c05eb3..09b38e8 100644 --- a/Apps.Lionbridge/Constants/ApiEndpoints.cs +++ b/Apps.Lionbridge/Constants/ApiEndpoints.cs @@ -11,4 +11,5 @@ public static class ApiEndpoints public const string UpdateContent = "/updatecontent"; public const string UpdateFileContent = "/updatefilecontent"; public const string SourceContent = "/sourcecontent"; + public const string SourceFiles = "/sourcefiles"; } \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/File/AddSourceFileRequest.cs b/Apps.Lionbridge/Models/Requests/File/AddSourceFileRequest.cs new file mode 100644 index 0000000..6daa751 --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/File/AddSourceFileRequest.cs @@ -0,0 +1,22 @@ +using Apps.Lionbridge.Models.Requests.Request; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Files; + +namespace Apps.Lionbridge.Models.Requests.File; + +public class AddSourceFileRequest : AddRequestBaseModel +{ + public FileReference File { get; set; } + + [Display("File name")] + public string? FileName { get; set; } + + [Display("Target native language")] + public string TargetNativeLanguage { get; set; } + + [Display("Fields keys", Description = "Fields keys to be added to the request.")] + public IEnumerable FieldsKeys { get; set; } + + [Display("Fields values", Description = "Fields values to be added to the request.")] + public IEnumerable FieldsValues { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/Request/AddRequestBaseModel.cs b/Apps.Lionbridge/Models/Requests/Request/AddRequestBaseModel.cs index 86b061c..d52cb31 100644 --- a/Apps.Lionbridge/Models/Requests/Request/AddRequestBaseModel.cs +++ b/Apps.Lionbridge/Models/Requests/Request/AddRequestBaseModel.cs @@ -1,5 +1,4 @@ using Blackbird.Applications.Sdk.Common; -using Blackbird.Applications.Sdk.Common.Files; namespace Apps.Lionbridge.Models.Requests.Request; diff --git a/Apps.Lionbridge/Models/Requests/Request/UpdateRequestModel.cs b/Apps.Lionbridge/Models/Requests/Request/UpdateRequestModel.cs new file mode 100644 index 0000000..84d510a --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/Request/UpdateRequestModel.cs @@ -0,0 +1,33 @@ +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Lionbridge.Models.Requests.Request; + +public class UpdateRequestModel +{ + [Display("Request name")] + public string RequestName { get; set; } + + [Display("Source native ID")] + public string SourceNativeId { get; set; } + + [Display("Source native language code")] + public string? SourceNativeLanguageCode { get; set; } + + [Display("Target native language code")] + public string? TargetNativeLanguageCode { get; set; } + + [Display("Target native ID")] + public string? TargetNativeId { get; set; } + + [Display("Metadata keys", Description = "Metadata keys to be added to the request.")] + public IEnumerable? MetadataKeys { get; set; } + + [Display("Metadata values", Description = "Metadata values to be added to the request.")] + public IEnumerable? MetadataValues { get; set; } + + [Display("File ID")] + public string? FileId { get; set; } + + [Display("Source content ID")] + public string? SourceContentId { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Responses/SourceFile/UploadFileAsMultipartResponse.cs b/Apps.Lionbridge/Models/Responses/SourceFile/UploadFileAsMultipartResponse.cs new file mode 100644 index 0000000..ab68768 --- /dev/null +++ b/Apps.Lionbridge/Models/Responses/SourceFile/UploadFileAsMultipartResponse.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace Apps.Lionbridge.Models.Responses.SourceFile; + +public class UploadFileAsMultipartResponse +{ + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("contentType")] + public string ContentType { get; set; } + + [JsonProperty("size")] + public long Size { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Responses/SourceFile/UploadSourceFileResponse.cs b/Apps.Lionbridge/Models/Responses/SourceFile/UploadSourceFileResponse.cs new file mode 100644 index 0000000..7ef253a --- /dev/null +++ b/Apps.Lionbridge/Models/Responses/SourceFile/UploadSourceFileResponse.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace Apps.Lionbridge.Models.Responses.SourceFile; + +public class UploadSourceFileResponse +{ + [JsonProperty("fmsFileId")] + public string FmsFileId { get; set; } + + [JsonProperty("fmsSASToken")] + public string FmsSASToken { get; set; } + + [JsonProperty("fmsUploadUrl")] + public string FmsUploadUrl { get; set; } + + [JsonProperty("fmsPostMultipartUrl")] + public string FmsPostMultipartUrl { get; set; } +} \ No newline at end of file From f194d7830e1b8a95dffe1aaa36614c7741feb836 Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Fri, 15 Mar 2024 11:49:35 +0200 Subject: [PATCH 18/55] Updated an action --- Apps.Lionbridge/Actions/RequestActions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index e2da4d7..6ed2521 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -66,7 +66,7 @@ public async Task CreateSingleRequest([ActionParameter] AddSourceReq return response.Embedded.Requests.First(); } - [Action("Create source content request", Description = "Create a new translation request.")] + [Action("Create file request", Description = "Create a new translation request.")] public async Task CreateFileRequest([ActionParameter] GetJobRequest jobRequest, [ActionParameter] AddSourceFileRequest sourceFileRequest) { From 5d18889742c865caa749e0e8db860e25eaaa2285 Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Fri, 15 Mar 2024 16:01:33 +0200 Subject: [PATCH 19/55] Added retrieve file action --- Apps.Lionbridge/Actions/RequestActions.cs | 4 +- Apps.Lionbridge/Actions/SourceFileActions.cs | 39 +++++++++++++++++++ Apps.Lionbridge/Apps.Lionbridge.csproj | 4 ++ Apps.Lionbridge/Constants/ApiEndpoints.cs | 1 + Apps.Lionbridge/LionbridgeInvocable.cs | 9 +++++ Apps.Lionbridge/Models/Dtos/RequestDto.cs | 12 ++++++ .../Requests/File/AddSourceFileRequest.cs | 6 --- .../SourceFile/RetrieveFileResponse.cs | 8 ++++ 8 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 Apps.Lionbridge/Actions/SourceFileActions.cs create mode 100644 Apps.Lionbridge/Models/Responses/SourceFile/RetrieveFileResponse.cs diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index 6ed2521..0ba8256 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -96,9 +96,7 @@ public async Task CreateFileRequest([ActionParameter] GetJobRequest [Action("Get request", Description = "Get a translation request.")] public async Task GetRequest([ActionParameter] GetRequest request) { - var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}" + - $"{ApiEndpoints.Requests}/{request.RequestId}"); - return await Client.ExecuteWithErrorHandling(apiRequest); + return await GetRequest(request.JobId, request.RequestId); } [Action("Delete request", Description = "Delete a translation request.")] diff --git a/Apps.Lionbridge/Actions/SourceFileActions.cs b/Apps.Lionbridge/Actions/SourceFileActions.cs new file mode 100644 index 0000000..1d69ba3 --- /dev/null +++ b/Apps.Lionbridge/Actions/SourceFileActions.cs @@ -0,0 +1,39 @@ +using Apps.Lionbridge.Api; +using Apps.Lionbridge.Constants; +using Apps.Lionbridge.Models.Requests.Request; +using Apps.Lionbridge.Models.Responses.SourceFile; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Actions; +using Blackbird.Applications.Sdk.Common.Invocation; +using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces; + +namespace Apps.Lionbridge.Actions; + +[ActionList] +public class SourceFileActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) + : LionbridgeInvocable(invocationContext) +{ + [Action("Retrieve file", Description = "Retrieve a file from specific request")] + public async Task RetrieveFile([ActionParameter] GetRequest request) + { + string endpoint = $"{ApiEndpoints.Jobs}/{request.JobId}{ApiEndpoints.Requests}/{request.RequestId}{ApiEndpoints.RetrieveFile}"; + var apiRequest = new LionbridgeRequest(endpoint); + + var response = await Client.ExecuteWithErrorHandling(apiRequest); + var bytes = response.RawBytes; + + if(bytes == null || bytes.Length == 0) + { + throw new Exception("Uploaded file is empty"); + } + + var requestModel = await GetRequest(request.JobId, request.RequestId); + + var memoryStream = new MemoryStream(bytes); + string fileName = requestModel.FileName; + string contentType = response.ContentType ?? MimeTypes.GetMimeType(fileName); + var fileReference = await fileManagementClient.UploadAsync(memoryStream, contentType, fileName); + + return new RetrieveFileResponse { File = fileReference }; + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Apps.Lionbridge.csproj b/Apps.Lionbridge/Apps.Lionbridge.csproj index 345119d..052717c 100644 --- a/Apps.Lionbridge/Apps.Lionbridge.csproj +++ b/Apps.Lionbridge/Apps.Lionbridge.csproj @@ -11,6 +11,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Apps.Lionbridge/Constants/ApiEndpoints.cs b/Apps.Lionbridge/Constants/ApiEndpoints.cs index 09b38e8..e809f51 100644 --- a/Apps.Lionbridge/Constants/ApiEndpoints.cs +++ b/Apps.Lionbridge/Constants/ApiEndpoints.cs @@ -12,4 +12,5 @@ public static class ApiEndpoints public const string UpdateFileContent = "/updatefilecontent"; public const string SourceContent = "/sourcecontent"; public const string SourceFiles = "/sourcefiles"; + public const string RetrieveFile = "/retrievefile"; } \ No newline at end of file diff --git a/Apps.Lionbridge/LionbridgeInvocable.cs b/Apps.Lionbridge/LionbridgeInvocable.cs index eae08cc..3715c54 100644 --- a/Apps.Lionbridge/LionbridgeInvocable.cs +++ b/Apps.Lionbridge/LionbridgeInvocable.cs @@ -1,4 +1,6 @@ using Apps.Lionbridge.Api; +using Apps.Lionbridge.Constants; +using Apps.Lionbridge.Models.Dtos; using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Invocation; @@ -12,4 +14,11 @@ protected LionbridgeInvocable(InvocationContext invocationContext) : base(invoca { Client = new(InvocationContext.AuthenticationCredentialsProviders); } + + protected async Task GetRequest(string jobId, string requestId) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobId}" + + $"{ApiEndpoints.Requests}/{requestId}"); + return await Client.ExecuteWithErrorHandling(apiRequest); + } } \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Dtos/RequestDto.cs b/Apps.Lionbridge/Models/Dtos/RequestDto.cs index 6d1b1cc..32225f2 100644 --- a/Apps.Lionbridge/Models/Dtos/RequestDto.cs +++ b/Apps.Lionbridge/Models/Dtos/RequestDto.cs @@ -36,4 +36,16 @@ public class RequestDto [Display("Source native language")] public string SourceNativeLanguageCode { get; set; } + + [Display("File name")] + public string FileName { get; set; } + + [Display("File type")] + public string FileType { get; set; } + + [Display("File ID")] + public string FileId { get; set; } + + [Display("Source content ID")] + public string SourceContentId { get; set; } } \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/File/AddSourceFileRequest.cs b/Apps.Lionbridge/Models/Requests/File/AddSourceFileRequest.cs index 6daa751..f830586 100644 --- a/Apps.Lionbridge/Models/Requests/File/AddSourceFileRequest.cs +++ b/Apps.Lionbridge/Models/Requests/File/AddSourceFileRequest.cs @@ -13,10 +13,4 @@ public class AddSourceFileRequest : AddRequestBaseModel [Display("Target native language")] public string TargetNativeLanguage { get; set; } - - [Display("Fields keys", Description = "Fields keys to be added to the request.")] - public IEnumerable FieldsKeys { get; set; } - - [Display("Fields values", Description = "Fields values to be added to the request.")] - public IEnumerable FieldsValues { get; set; } } \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Responses/SourceFile/RetrieveFileResponse.cs b/Apps.Lionbridge/Models/Responses/SourceFile/RetrieveFileResponse.cs new file mode 100644 index 0000000..e93bf0f --- /dev/null +++ b/Apps.Lionbridge/Models/Responses/SourceFile/RetrieveFileResponse.cs @@ -0,0 +1,8 @@ +using Blackbird.Applications.Sdk.Common.Files; + +namespace Apps.Lionbridge.Models.Responses.SourceFile; + +public class RetrieveFileResponse +{ + public FileReference File { get; set; } +} \ No newline at end of file From 1d557b7512a242b06f128a2098b6336335ed1a54 Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Fri, 15 Mar 2024 17:49:33 +0200 Subject: [PATCH 20/55] Improved update job action --- Apps.Lionbridge/Actions/JobActions.cs | 19 +++++++++++++------ .../Models/Requests/Job/UpdateJobRequest.cs | 2 -- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Apps.Lionbridge/Actions/JobActions.cs b/Apps.Lionbridge/Actions/JobActions.cs index 97a475e..c82145b 100644 --- a/Apps.Lionbridge/Actions/JobActions.cs +++ b/Apps.Lionbridge/Actions/JobActions.cs @@ -80,11 +80,6 @@ public async Task UpdateJob([ActionParameter] GetJobRequest jobRequest, apiUpdateRequest.DueDate = request.DueDate.Value.ToString("yyyy-MM-ddTHH:mm:ssZ"); } - if(request.CustomData != null) - { - apiUpdateRequest.CustomData = request.CustomData; - } - if(request.ShouldQuote != null) { apiUpdateRequest.ShouldQuote = request.ShouldQuote; @@ -106,7 +101,19 @@ public async Task UpdateJob([ActionParameter] GetJobRequest jobRequest, } var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobRequest.JobId}", Method.Patch) - .WithJsonBody(apiUpdateRequest); + .WithJsonBody(new + { + jobName = apiUpdateRequest.JobName, + description = apiUpdateRequest.Description, + providerId = apiUpdateRequest.ProviderId, + extendedMetadata = apiUpdateRequest.ExtendedMetadata, + labels = apiUpdateRequest.Labels, + dueDate = apiUpdateRequest.DueDate, + shouldQuote = apiUpdateRequest.ShouldQuote, + connectorName = apiUpdateRequest.ConnectorName, + connectorVersion = apiUpdateRequest.ConnectorVersion, + serviceType = apiUpdateRequest.ServiceType + }); return await Client.ExecuteWithErrorHandling(apiRequest); } diff --git a/Apps.Lionbridge/Models/Requests/Job/UpdateJobRequest.cs b/Apps.Lionbridge/Models/Requests/Job/UpdateJobRequest.cs index e1f0268..e447b07 100644 --- a/Apps.Lionbridge/Models/Requests/Job/UpdateJobRequest.cs +++ b/Apps.Lionbridge/Models/Requests/Job/UpdateJobRequest.cs @@ -14,8 +14,6 @@ public class UpdateJobRequest [Display("Due date")] public DateTime? DueDate { get; set; } - [Display("Custom data")] public string? CustomData { get; set; } - [Display("Should quote")] public bool? ShouldQuote { get; set; } [Display("Provider ID")] From 1c1186236e161bb5c25979b6eaa58a7930da7cc9 Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Mon, 18 Mar 2024 11:01:07 +0200 Subject: [PATCH 21/55] Added support assets actions --- Apps.Lionbridge/Actions/RequestActions.cs | 34 +--------- .../Actions/SupportAssetsActions.cs | 63 +++++++++++++++++++ Apps.Lionbridge/Constants/ApiEndpoints.cs | 1 + .../SupportAssetDataSourceHandler.cs | 33 ++++++++++ Apps.Lionbridge/LionbridgeInvocable.cs | 34 ++++++++++ .../Models/Dtos/SupportAssetDto.cs | 37 +++++++++++ .../SupportAssets/AddSupportAssetRequest.cs | 24 +++++++ .../SupportAssets/GetSupportAssetRequest.cs | 14 +++++ .../SupportAssets/SupportAssetResponse.cs | 56 +++++++++++++++++ .../SupportAssets/SupportAssetsResponse.cs | 10 +++ 10 files changed, 273 insertions(+), 33 deletions(-) create mode 100644 Apps.Lionbridge/Actions/SupportAssetsActions.cs create mode 100644 Apps.Lionbridge/DataSourceHandlers/SupportAssetDataSourceHandler.cs create mode 100644 Apps.Lionbridge/Models/Dtos/SupportAssetDto.cs create mode 100644 Apps.Lionbridge/Models/Requests/SupportAssets/AddSupportAssetRequest.cs create mode 100644 Apps.Lionbridge/Models/Requests/SupportAssets/GetSupportAssetRequest.cs create mode 100644 Apps.Lionbridge/Models/Responses/SupportAssets/SupportAssetResponse.cs create mode 100644 Apps.Lionbridge/Models/Responses/SupportAssets/SupportAssetsResponse.cs diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index 0ba8256..9849c7b 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -6,11 +6,9 @@ using Apps.Lionbridge.Models.Requests.Job; using Apps.Lionbridge.Models.Requests.Request; using Apps.Lionbridge.Models.Responses.Request; -using Apps.Lionbridge.Models.Responses.SourceFile; using Apps.Lionbridge.Models.Responses.TranslationContent; using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Actions; -using Blackbird.Applications.Sdk.Common.Files; using Blackbird.Applications.Sdk.Common.Invocation; using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces; using Blackbird.Applications.Sdk.Utils.Extensions.Http; @@ -70,7 +68,7 @@ public async Task CreateSingleRequest([ActionParameter] AddSourceReq public async Task CreateFileRequest([ActionParameter] GetJobRequest jobRequest, [ActionParameter] AddSourceFileRequest sourceFileRequest) { - var uploadResponse = await UploadFmsFile(jobRequest.JobId, sourceFileRequest); + var uploadResponse = await UploadFmsFile(jobRequest.JobId, sourceFileRequest, fileManagementClient); var metadata = EnumerableExtensions.ToDictionary(sourceFileRequest.MetadataKeys, sourceFileRequest.MetadataValues); @@ -177,34 +175,4 @@ private async Task CreateTranslationContent(string jobId, IEnumerable(apiRequest); return response.SourceContentId; } - - private async Task UploadFmsFile(string jobId, AddSourceFileRequest fileRequest) - { - string fileName = fileRequest.FileName ?? fileRequest.File.Name; - - string endpoint = $"{ApiEndpoints.Jobs}/{jobId}{ApiEndpoints.SourceFiles}?fileName={fileName}"; - var apiRequest = new LionbridgeRequest(endpoint, Method.Post); - - var response = await Client.ExecuteWithErrorHandling(apiRequest); - - string fmsMultipartUrl = response.FmsPostMultipartUrl; - - var fileStream = await fileManagementClient.DownloadAsync(fileRequest.File); - var memoryStream = new MemoryStream(); - await fileStream.CopyToAsync(memoryStream); - - var bytes = memoryStream.ToArray(); - - var client = new RestClient(fmsMultipartUrl); - var request = new RestRequest(string.Empty, Method.Post); - request.AddFile("file", bytes, fileName); - - var uploadFileResponse = await client.ExecuteAsync(request); - if (!uploadFileResponse.IsSuccessful) - { - throw new Exception("Failed to upload file to FMS on second step"); - } - - return response; - } } \ No newline at end of file diff --git a/Apps.Lionbridge/Actions/SupportAssetsActions.cs b/Apps.Lionbridge/Actions/SupportAssetsActions.cs new file mode 100644 index 0000000..84d8b1f --- /dev/null +++ b/Apps.Lionbridge/Actions/SupportAssetsActions.cs @@ -0,0 +1,63 @@ +using Apps.Lionbridge.Api; +using Apps.Lionbridge.Constants; +using Apps.Lionbridge.Extensions; +using Apps.Lionbridge.Models.Dtos; +using Apps.Lionbridge.Models.Requests.File; +using Apps.Lionbridge.Models.Requests.Job; +using Apps.Lionbridge.Models.Requests.SupportAssets; +using Apps.Lionbridge.Models.Responses.SupportAssets; +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 RestSharp; + +namespace Apps.Lionbridge.Actions; + +[ActionList] +public class SupportAssetsActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) + : LionbridgeInvocable(invocationContext) +{ + [Action("Get support asset", Description = "Get a support asset.")] + public async Task GetSupportAsset([ActionParameter] GetSupportAssetRequest request) + { + string endpoint = $"{ApiEndpoints.Jobs}/{request.JobId}{ApiEndpoints.SupportAssets}/{request.SupportAssetId}"; + var apiRequest = new LionbridgeRequest(endpoint); + + var dto = await Client.ExecuteWithErrorHandling(apiRequest); + return new SupportAssetResponse(dto); + } + + [Action("Delete support asset", Description = "Delete a support asset.")] + public async Task DeleteSupportAsset([ActionParameter] GetSupportAssetRequest request) + { + string endpoint = $"{ApiEndpoints.Jobs}/{request.JobId}{ApiEndpoints.SupportAssets}/{request.SupportAssetId}"; + var apiRequest = new LionbridgeRequest(endpoint, Method.Delete); + + await Client.ExecuteWithErrorHandling(apiRequest); + } + + [Action("Add support asset", Description = "Add a support asset to a job")] + public async Task AddSupportAsset([ActionParameter] GetJobRequest request, + [ActionParameter] AddSupportAssetRequest addSupportAssetRequest, + [ActionParameter] AddSourceFileRequest fileRequest) + { + var uploadResponse = await UploadFmsFile(request.JobId, fileRequest, fileManagementClient); + + string endpoint = $"{ApiEndpoints.Jobs}/{request.JobId}{ApiEndpoints.SupportAssets}"; + var metadata = EnumerableExtensions.ToDictionary(addSupportAssetRequest.ExtendedMetadataKeys, addSupportAssetRequest.ExtendedMetadataValues); + var apiRequest = new LionbridgeRequest(endpoint, Method.Post) + .AddJsonBody(new + { + fmsFileId = uploadResponse.FmsFileId, + description = addSupportAssetRequest.Description, + sourceNativeIds = addSupportAssetRequest.SourceNativeIds, + sourceNativeLanguageCode = addSupportAssetRequest.SourceNativeLanguageCode, + targetNativeLanguageCodes = addSupportAssetRequest.TargetNativeLanguageCodes ?? [fileRequest.TargetNativeLanguage], + extendedMetadata = metadata + }); + + var dto = await Client.ExecuteWithErrorHandling(apiRequest); + return new SupportAssetResponse(dto); + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Constants/ApiEndpoints.cs b/Apps.Lionbridge/Constants/ApiEndpoints.cs index e809f51..e2b35d9 100644 --- a/Apps.Lionbridge/Constants/ApiEndpoints.cs +++ b/Apps.Lionbridge/Constants/ApiEndpoints.cs @@ -13,4 +13,5 @@ public static class ApiEndpoints public const string SourceContent = "/sourcecontent"; public const string SourceFiles = "/sourcefiles"; public const string RetrieveFile = "/retrievefile"; + public const string SupportAssets = "/supportassets"; } \ No newline at end of file diff --git a/Apps.Lionbridge/DataSourceHandlers/SupportAssetDataSourceHandler.cs b/Apps.Lionbridge/DataSourceHandlers/SupportAssetDataSourceHandler.cs new file mode 100644 index 0000000..2849e11 --- /dev/null +++ b/Apps.Lionbridge/DataSourceHandlers/SupportAssetDataSourceHandler.cs @@ -0,0 +1,33 @@ +using Apps.Lionbridge.Api; +using Apps.Lionbridge.Constants; +using Apps.Lionbridge.Models.Requests.SupportAssets; +using Apps.Lionbridge.Models.Responses.SupportAssets; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; +using Blackbird.Applications.Sdk.Common.Invocation; + +namespace Apps.Lionbridge.DataSourceHandlers; + +public class SupportAssetDataSourceHandler : LionbridgeInvocable, IAsyncDataSourceHandler +{ + private readonly string _jobId; + + public SupportAssetDataSourceHandler(InvocationContext invocationContext, [ActionParameter] GetSupportAssetRequest request) : base(invocationContext) + { + _jobId = request.JobId; + } + + public async Task> GetDataAsync(DataSourceContext context, + CancellationToken cancellationToken) + { + var endpoint = $"{ApiEndpoints.Jobs}/{_jobId}{ApiEndpoints.SupportAssets}"; + var request = new LionbridgeRequest(endpoint); + + var response = await Client.ExecuteWithErrorHandling(request); + + return response.Embedded.SupportAssets.Where(job => + context.SearchString == null || + job.Filename.Contains(context.SearchString, StringComparison.OrdinalIgnoreCase)) + .ToDictionary(job => job.SupportAssetId, job => job.Filename); + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/LionbridgeInvocable.cs b/Apps.Lionbridge/LionbridgeInvocable.cs index 3715c54..fb71c15 100644 --- a/Apps.Lionbridge/LionbridgeInvocable.cs +++ b/Apps.Lionbridge/LionbridgeInvocable.cs @@ -1,8 +1,12 @@ using Apps.Lionbridge.Api; using Apps.Lionbridge.Constants; using Apps.Lionbridge.Models.Dtos; +using Apps.Lionbridge.Models.Requests.File; +using Apps.Lionbridge.Models.Responses.SourceFile; using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Invocation; +using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces; +using RestSharp; namespace Apps.Lionbridge; @@ -21,4 +25,34 @@ protected async Task GetRequest(string jobId, string requestId) $"{ApiEndpoints.Requests}/{requestId}"); return await Client.ExecuteWithErrorHandling(apiRequest); } + + protected async Task UploadFmsFile(string jobId, AddSourceFileRequest fileRequest, IFileManagementClient fileManagementClient) + { + string fileName = fileRequest.FileName ?? fileRequest.File.Name; + + string endpoint = $"{ApiEndpoints.Jobs}/{jobId}{ApiEndpoints.SourceFiles}?fileName={fileName}"; + var apiRequest = new LionbridgeRequest(endpoint, Method.Post); + + var response = await Client.ExecuteWithErrorHandling(apiRequest); + + string fmsMultipartUrl = response.FmsPostMultipartUrl; + + var fileStream = await fileManagementClient.DownloadAsync(fileRequest.File); + var memoryStream = new MemoryStream(); + await fileStream.CopyToAsync(memoryStream); + + var bytes = memoryStream.ToArray(); + + var client = new RestClient(fmsMultipartUrl); + var request = new RestRequest(string.Empty, Method.Post); + request.AddFile("file", bytes, fileName); + + var uploadFileResponse = await client.ExecuteAsync(request); + if (!uploadFileResponse.IsSuccessful) + { + throw new Exception("Failed to upload file to FMS on second step"); + } + + return response; + } } \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Dtos/SupportAssetDto.cs b/Apps.Lionbridge/Models/Dtos/SupportAssetDto.cs new file mode 100644 index 0000000..02be763 --- /dev/null +++ b/Apps.Lionbridge/Models/Dtos/SupportAssetDto.cs @@ -0,0 +1,37 @@ +using Blackbird.Applications.Sdk.Common; +using Newtonsoft.Json; + +namespace Apps.Lionbridge.Models.Dtos; + +public class SupportAssetDto +{ + [JsonProperty("supportassetId")] + public string SupportAssetId { get; set; } + + [JsonProperty("fileId")] + public string FileId { get; set; } + + [JsonProperty("jobId")] + public string JobId { get; set; } + + [JsonProperty("filename")] + public string Filename { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("sourceNativeIds")] + public string[] SourceNativeIds { get; set; } + + [JsonProperty("sourceNativeLanguageCode")] + public string SourceNativeLanguageCode { get; set; } + + [JsonProperty("targetNativeLanguageCodes")] + public string[] TargetNativeLanguageCodes { get; set; } + + [JsonProperty("createdDate")] + public DateTime CreatedDate { get; set; } + + [JsonProperty("extendedMetadata")] + public Dictionary ExtendedMetadata { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/SupportAssets/AddSupportAssetRequest.cs b/Apps.Lionbridge/Models/Requests/SupportAssets/AddSupportAssetRequest.cs new file mode 100644 index 0000000..6d8a201 --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/SupportAssets/AddSupportAssetRequest.cs @@ -0,0 +1,24 @@ +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Files; + +namespace Apps.Lionbridge.Models.Requests.SupportAssets; + +public class AddSupportAssetRequest +{ + public string? Description { get; set; } + + [Display("Source native language")] + public IEnumerable SourceNativeIds { get; set; } + + [Display("Source native language code")] + public string SourceNativeLanguageCode { get; set; } + + [Display("Target native language codes")] + public IEnumerable? TargetNativeLanguageCodes { get; set; } + + [Display("Extended metadata keys")] + public IEnumerable? ExtendedMetadataKeys { get; set; } + + [Display("Extended metadata values")] + public IEnumerable? ExtendedMetadataValues { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/SupportAssets/GetSupportAssetRequest.cs b/Apps.Lionbridge/Models/Requests/SupportAssets/GetSupportAssetRequest.cs new file mode 100644 index 0000000..d29d3b6 --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/SupportAssets/GetSupportAssetRequest.cs @@ -0,0 +1,14 @@ +using Apps.Lionbridge.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Lionbridge.Models.Requests.SupportAssets; + +public class GetSupportAssetRequest +{ + [Display("Job ID"), DataSource(typeof(JobDataSourceHandler))] + public string JobId { get; set; } + + [Display("Support asset ID"), DataSource(typeof(SupportAssetDataSourceHandler))] + public string SupportAssetId { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Responses/SupportAssets/SupportAssetResponse.cs b/Apps.Lionbridge/Models/Responses/SupportAssets/SupportAssetResponse.cs new file mode 100644 index 0000000..e928377 --- /dev/null +++ b/Apps.Lionbridge/Models/Responses/SupportAssets/SupportAssetResponse.cs @@ -0,0 +1,56 @@ +using Apps.Lionbridge.Models.Dtos; +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Lionbridge.Models.Responses.SupportAssets; + +public class SupportAssetResponse +{ + [Display("Support asset ID")] + public string SupportAssetId { get; set; } + + [Display("File ID")] + public string FileId { get; set; } + + [Display("Job ID")] + public string JobId { get; set; } + + [Display("Filename")] + public string Filename { get; set; } + + [Display("Description")] + public string Description { get; set; } + + [Display("Source native IDs")] + public string[] SourceNativeIds { get; set; } + + [Display("Source native language code")] + public string SourceNativeLanguageCode { get; set; } + + [Display("Target native language codes")] + public string[] TargetNativeLanguageCodes { get; set; } + + [Display("Created date")] + public DateTime CreatedDate { get; set; } + + [Display("Extended metadata keys")] + public string[] ExtendedMetadataKeys { get; set; } + + [Display("Extended metadata values")] + public string[] ExtendedMetadataValues { get; set; } + + public SupportAssetResponse(SupportAssetDto supportAssetDto) + { + SupportAssetId = supportAssetDto.SupportAssetId; + FileId = supportAssetDto.FileId; + JobId = supportAssetDto.JobId; + Filename = supportAssetDto.Filename; + Description = supportAssetDto.Description; + SourceNativeIds = supportAssetDto.SourceNativeIds; + SourceNativeLanguageCode = supportAssetDto.SourceNativeLanguageCode; + TargetNativeLanguageCodes = supportAssetDto.TargetNativeLanguageCodes; + CreatedDate = supportAssetDto.CreatedDate; + ExtendedMetadataKeys = supportAssetDto.ExtendedMetadata.Keys.ToArray(); + ExtendedMetadataValues = supportAssetDto.ExtendedMetadata.Values.ToArray(); + } +} + diff --git a/Apps.Lionbridge/Models/Responses/SupportAssets/SupportAssetsResponse.cs b/Apps.Lionbridge/Models/Responses/SupportAssets/SupportAssetsResponse.cs new file mode 100644 index 0000000..8922e03 --- /dev/null +++ b/Apps.Lionbridge/Models/Responses/SupportAssets/SupportAssetsResponse.cs @@ -0,0 +1,10 @@ +using Apps.Lionbridge.Models.Dtos; + +namespace Apps.Lionbridge.Models.Responses.SupportAssets; + +public class SupportAssetsResponse : EmbeddedItemsWrapper +{ + +} + +public record SupportAssetsWrapper(IEnumerable SupportAssets); \ No newline at end of file From c347745d17269557388cc50bec0515949362ee4e Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Mon, 18 Mar 2024 11:27:26 +0200 Subject: [PATCH 22/55] Rename jobId property --- .../DataSourceHandlers/SupportAssetDataSourceHandler.cs | 2 +- .../Models/Requests/SupportAssets/GetSupportAssetRequest.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Apps.Lionbridge/DataSourceHandlers/SupportAssetDataSourceHandler.cs b/Apps.Lionbridge/DataSourceHandlers/SupportAssetDataSourceHandler.cs index 2849e11..406d09b 100644 --- a/Apps.Lionbridge/DataSourceHandlers/SupportAssetDataSourceHandler.cs +++ b/Apps.Lionbridge/DataSourceHandlers/SupportAssetDataSourceHandler.cs @@ -14,7 +14,7 @@ public class SupportAssetDataSourceHandler : LionbridgeInvocable, IAsyncDataSour public SupportAssetDataSourceHandler(InvocationContext invocationContext, [ActionParameter] GetSupportAssetRequest request) : base(invocationContext) { - _jobId = request.JobId; + _jobId = request.LionBridgeJobId; } public async Task> GetDataAsync(DataSourceContext context, diff --git a/Apps.Lionbridge/Models/Requests/SupportAssets/GetSupportAssetRequest.cs b/Apps.Lionbridge/Models/Requests/SupportAssets/GetSupportAssetRequest.cs index d29d3b6..6314837 100644 --- a/Apps.Lionbridge/Models/Requests/SupportAssets/GetSupportAssetRequest.cs +++ b/Apps.Lionbridge/Models/Requests/SupportAssets/GetSupportAssetRequest.cs @@ -7,7 +7,7 @@ namespace Apps.Lionbridge.Models.Requests.SupportAssets; public class GetSupportAssetRequest { [Display("Job ID"), DataSource(typeof(JobDataSourceHandler))] - public string JobId { get; set; } + public string LionBridgeJobId { get; set; } [Display("Support asset ID"), DataSource(typeof(SupportAssetDataSourceHandler))] public string SupportAssetId { get; set; } From d9bfe88b21f503aaa4ca528c1ef8350bf344c7ea Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Mon, 18 Mar 2024 11:27:57 +0200 Subject: [PATCH 23/55] Rename jobId property --- Apps.Lionbridge/Actions/SupportAssetsActions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Apps.Lionbridge/Actions/SupportAssetsActions.cs b/Apps.Lionbridge/Actions/SupportAssetsActions.cs index 84d8b1f..f2f38f5 100644 --- a/Apps.Lionbridge/Actions/SupportAssetsActions.cs +++ b/Apps.Lionbridge/Actions/SupportAssetsActions.cs @@ -21,7 +21,7 @@ public class SupportAssetsActions(InvocationContext invocationContext, IFileMana [Action("Get support asset", Description = "Get a support asset.")] public async Task GetSupportAsset([ActionParameter] GetSupportAssetRequest request) { - string endpoint = $"{ApiEndpoints.Jobs}/{request.JobId}{ApiEndpoints.SupportAssets}/{request.SupportAssetId}"; + string endpoint = $"{ApiEndpoints.Jobs}/{request.LionBridgeJobId}{ApiEndpoints.SupportAssets}/{request.SupportAssetId}"; var apiRequest = new LionbridgeRequest(endpoint); var dto = await Client.ExecuteWithErrorHandling(apiRequest); @@ -31,7 +31,7 @@ public async Task GetSupportAsset([ActionParameter] GetSup [Action("Delete support asset", Description = "Delete a support asset.")] public async Task DeleteSupportAsset([ActionParameter] GetSupportAssetRequest request) { - string endpoint = $"{ApiEndpoints.Jobs}/{request.JobId}{ApiEndpoints.SupportAssets}/{request.SupportAssetId}"; + string endpoint = $"{ApiEndpoints.Jobs}/{request.LionBridgeJobId}{ApiEndpoints.SupportAssets}/{request.SupportAssetId}"; var apiRequest = new LionbridgeRequest(endpoint, Method.Delete); await Client.ExecuteWithErrorHandling(apiRequest); From 5d3e515cf16d7cfabae2c77eb96953c36d3f0d4c Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Mon, 18 Mar 2024 11:34:55 +0200 Subject: [PATCH 24/55] Updated collection type --- .../Models/Responses/SupportAssets/SupportAssetResponse.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Apps.Lionbridge/Models/Responses/SupportAssets/SupportAssetResponse.cs b/Apps.Lionbridge/Models/Responses/SupportAssets/SupportAssetResponse.cs index e928377..fd683d2 100644 --- a/Apps.Lionbridge/Models/Responses/SupportAssets/SupportAssetResponse.cs +++ b/Apps.Lionbridge/Models/Responses/SupportAssets/SupportAssetResponse.cs @@ -33,10 +33,10 @@ public class SupportAssetResponse public DateTime CreatedDate { get; set; } [Display("Extended metadata keys")] - public string[] ExtendedMetadataKeys { get; set; } + public IEnumerable ExtendedMetadataKeys { get; set; } [Display("Extended metadata values")] - public string[] ExtendedMetadataValues { get; set; } + public IEnumerable ExtendedMetadataValues { get; set; } public SupportAssetResponse(SupportAssetDto supportAssetDto) { From c1101bc144bdf4a9b8dab4e7d75f1a6a690b597b Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Mon, 18 Mar 2024 12:03:09 +0200 Subject: [PATCH 25/55] Translation memory actions --- .../Actions/TranslationMemoryActions.cs | 52 +++++++++++++++++++ Apps.Lionbridge/Constants/ApiEndpoints.cs | 1 + .../SupportAssetDataSourceHandler.cs | 5 ++ .../TranslationMemoryDataSourceHandler.cs | 39 ++++++++++++++ .../Models/Dtos/TranslationMemoryDto.cs | 30 +++++++++++ .../AddTranslationMemoryRequest.cs | 18 +++++++ .../GetTranslationMemoryRequest.cs | 14 +++++ .../TranslationMemoriesResponse.cs | 10 ++++ .../TranslationMemoryResponse.cs | 47 +++++++++++++++++ 9 files changed, 216 insertions(+) create mode 100644 Apps.Lionbridge/Actions/TranslationMemoryActions.cs create mode 100644 Apps.Lionbridge/DataSourceHandlers/TranslationMemoryDataSourceHandler.cs create mode 100644 Apps.Lionbridge/Models/Dtos/TranslationMemoryDto.cs create mode 100644 Apps.Lionbridge/Models/Requests/TranslationMemory/AddTranslationMemoryRequest.cs create mode 100644 Apps.Lionbridge/Models/Requests/TranslationMemory/GetTranslationMemoryRequest.cs create mode 100644 Apps.Lionbridge/Models/Responses/TranslationMemory/TranslationMemoriesResponse.cs create mode 100644 Apps.Lionbridge/Models/Responses/TranslationMemory/TranslationMemoryResponse.cs diff --git a/Apps.Lionbridge/Actions/TranslationMemoryActions.cs b/Apps.Lionbridge/Actions/TranslationMemoryActions.cs new file mode 100644 index 0000000..18cd4db --- /dev/null +++ b/Apps.Lionbridge/Actions/TranslationMemoryActions.cs @@ -0,0 +1,52 @@ +using Apps.Lionbridge.Api; +using Apps.Lionbridge.Constants; +using Apps.Lionbridge.Extensions; +using Apps.Lionbridge.Models.Dtos; +using Apps.Lionbridge.Models.Requests.File; +using Apps.Lionbridge.Models.Requests.Job; +using Apps.Lionbridge.Models.Requests.TranslationMemory; +using Apps.Lionbridge.Models.Responses.TranslationMemory; +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 RestSharp; + +namespace Apps.Lionbridge.Actions; + +[ActionList] +public class TranslationMemoryActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) + : LionbridgeInvocable(invocationContext) +{ + [Action("Add translation memory", Description = "Add a translation memory to a job")] + public async Task AddTranslationMemory([ActionParameter] GetJobRequest request, + [ActionParameter] AddTranslationMemoryRequest addTranslationMemoryRequest, + [ActionParameter] AddSourceFileRequest fileRequest) + { + var uploadResponse = await UploadFmsFile(request.JobId, fileRequest, fileManagementClient); + string endpoint = $"{ApiEndpoints.Jobs}/{request.JobId}{ApiEndpoints.TranslationMemories}"; + var extendedMetadata = EnumerableExtensions.ToDictionary(addTranslationMemoryRequest.ExtendedMetadataKeys, addTranslationMemoryRequest.ExtendedMetadataValues); + + var apiRequest = new LionbridgeRequest(endpoint, Method.Post) + .AddJsonBody(new + { + fmsFileId = uploadResponse.FmsFileId, + sourceNativeLanguageCode = addTranslationMemoryRequest.SourceNativeLanguageCode, + targetNativeLanguageCode = addTranslationMemoryRequest.TargetNativeLanguageCode, + extendedMetadata = extendedMetadata + }); + + var dto = await Client.ExecuteWithErrorHandling(apiRequest); + return new TranslationMemoryResponse(dto); + } + + [Action("Get translation memory", Description = "Get a translation memory.")] + public async Task GetTranslationMemory( + [ActionParameter] GetTranslationMemoryRequest request) + { + string endpoint = $"{ApiEndpoints.Jobs}/{request.LionBridgeJobId}{ApiEndpoints.TranslationMemories}/{request.TmupdateId}"; + var apiRequest = new LionbridgeRequest(endpoint); + var dto = await Client.ExecuteWithErrorHandling(apiRequest); + return new TranslationMemoryResponse(dto); + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Constants/ApiEndpoints.cs b/Apps.Lionbridge/Constants/ApiEndpoints.cs index e2b35d9..3a0c932 100644 --- a/Apps.Lionbridge/Constants/ApiEndpoints.cs +++ b/Apps.Lionbridge/Constants/ApiEndpoints.cs @@ -14,4 +14,5 @@ public static class ApiEndpoints public const string SourceFiles = "/sourcefiles"; public const string RetrieveFile = "/retrievefile"; public const string SupportAssets = "/supportassets"; + public const string TranslationMemories = "/tmupdates"; } \ No newline at end of file diff --git a/Apps.Lionbridge/DataSourceHandlers/SupportAssetDataSourceHandler.cs b/Apps.Lionbridge/DataSourceHandlers/SupportAssetDataSourceHandler.cs index 406d09b..8b1806c 100644 --- a/Apps.Lionbridge/DataSourceHandlers/SupportAssetDataSourceHandler.cs +++ b/Apps.Lionbridge/DataSourceHandlers/SupportAssetDataSourceHandler.cs @@ -20,6 +20,11 @@ public SupportAssetDataSourceHandler(InvocationContext invocationContext, [Actio public async Task> GetDataAsync(DataSourceContext context, CancellationToken cancellationToken) { + if(string.IsNullOrEmpty(_jobId)) + { + throw new InvalidOperationException("You should provide a Job ID first"); + } + var endpoint = $"{ApiEndpoints.Jobs}/{_jobId}{ApiEndpoints.SupportAssets}"; var request = new LionbridgeRequest(endpoint); diff --git a/Apps.Lionbridge/DataSourceHandlers/TranslationMemoryDataSourceHandler.cs b/Apps.Lionbridge/DataSourceHandlers/TranslationMemoryDataSourceHandler.cs new file mode 100644 index 0000000..409a4d8 --- /dev/null +++ b/Apps.Lionbridge/DataSourceHandlers/TranslationMemoryDataSourceHandler.cs @@ -0,0 +1,39 @@ +using Apps.Lionbridge.Api; +using Apps.Lionbridge.Constants; +using Apps.Lionbridge.Models.Requests.SupportAssets; +using Apps.Lionbridge.Models.Responses.TranslationMemory; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; +using Blackbird.Applications.Sdk.Common.Invocation; + +namespace Apps.Lionbridge.DataSourceHandlers; + +public class TranslationMemoryDataSourceHandler : LionbridgeInvocable, IAsyncDataSourceHandler +{ + private readonly string _jobId; + + public TranslationMemoryDataSourceHandler(InvocationContext invocationContext, + [ActionParameter] GetSupportAssetRequest request) : base(invocationContext) + { + _jobId = request.LionBridgeJobId; + } + + public async Task> GetDataAsync(DataSourceContext context, + CancellationToken cancellationToken) + { + if(string.IsNullOrEmpty(_jobId)) + { + throw new InvalidOperationException("You should provide a Job ID first"); + } + + var endpoint = $"{ApiEndpoints.Jobs}/{_jobId}{ApiEndpoints.TranslationMemories}"; + var request = new LionbridgeRequest(endpoint); + + var response = await Client.ExecuteWithErrorHandling(request); + + return response.Embedded.tmupdates.Where(job => + context.SearchString == null || + job.FileName.Contains(context.SearchString, StringComparison.OrdinalIgnoreCase)) + .ToDictionary(job => job.TmupdateId, job => job.FileName); + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Dtos/TranslationMemoryDto.cs b/Apps.Lionbridge/Models/Dtos/TranslationMemoryDto.cs new file mode 100644 index 0000000..56cd41b --- /dev/null +++ b/Apps.Lionbridge/Models/Dtos/TranslationMemoryDto.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; + +namespace Apps.Lionbridge.Models.Dtos; + +public class TranslationMemoryDto +{ + [JsonProperty("tmupdateId")] + public string TmupdateId { get; set; } + + [JsonProperty("jobId")] + public string JobId { get; set; } + + [JsonProperty("fileId")] + public string FileId { get; set; } + + [JsonProperty("fileType")] + public string FileType { get; set; } + + [JsonProperty("fileName")] + public string FileName { get; set; } + + [JsonProperty("sourceNativeLanguageCode")] + public string SourceNativeLanguageCode { get; set; } + + [JsonProperty("targetNativeLanguageCode")] + public string TargetNativeLanguageCode { get; set; } + + [JsonProperty("extendedMetadata")] + public Dictionary ExtendedMetadata { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/TranslationMemory/AddTranslationMemoryRequest.cs b/Apps.Lionbridge/Models/Requests/TranslationMemory/AddTranslationMemoryRequest.cs new file mode 100644 index 0000000..06835db --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/TranslationMemory/AddTranslationMemoryRequest.cs @@ -0,0 +1,18 @@ +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Lionbridge.Models.Requests.TranslationMemory; + +public class AddTranslationMemoryRequest +{ + [Display("Source native language code")] + public string SourceNativeLanguageCode { get; set; } + + [Display("Target native language code")] + public string TargetNativeLanguageCode { get; set; } + + [Display("Extended metadata keys")] + public IEnumerable? ExtendedMetadataKeys { get; set; } + + [Display("Extended metadata values")] + public IEnumerable? ExtendedMetadataValues { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/TranslationMemory/GetTranslationMemoryRequest.cs b/Apps.Lionbridge/Models/Requests/TranslationMemory/GetTranslationMemoryRequest.cs new file mode 100644 index 0000000..8ac249c --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/TranslationMemory/GetTranslationMemoryRequest.cs @@ -0,0 +1,14 @@ +using Apps.Lionbridge.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Lionbridge.Models.Requests.TranslationMemory; + +public class GetTranslationMemoryRequest +{ + [Display("Job ID"), DataSource(typeof(JobDataSourceHandler))] + public string LionBridgeJobId { get; set; } + + [Display("TM update ID"), DataSource(typeof(TranslationMemoryDataSourceHandler))] + public string TmupdateId { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Responses/TranslationMemory/TranslationMemoriesResponse.cs b/Apps.Lionbridge/Models/Responses/TranslationMemory/TranslationMemoriesResponse.cs new file mode 100644 index 0000000..c6fed2c --- /dev/null +++ b/Apps.Lionbridge/Models/Responses/TranslationMemory/TranslationMemoriesResponse.cs @@ -0,0 +1,10 @@ +using Apps.Lionbridge.Models.Dtos; + +namespace Apps.Lionbridge.Models.Responses.TranslationMemory; + +public class TranslationMemoriesResponse : EmbeddedItemsWrapper +{ + +} + +public record TranslationMemoriesWrapper(IEnumerable tmupdates); \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Responses/TranslationMemory/TranslationMemoryResponse.cs b/Apps.Lionbridge/Models/Responses/TranslationMemory/TranslationMemoryResponse.cs new file mode 100644 index 0000000..bb3e2ee --- /dev/null +++ b/Apps.Lionbridge/Models/Responses/TranslationMemory/TranslationMemoryResponse.cs @@ -0,0 +1,47 @@ +using Apps.Lionbridge.Models.Dtos; +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Lionbridge.Models.Responses.TranslationMemory; + +public class TranslationMemoryResponse +{ + [Display("TM update ID")] + public string TmupdateId { get; set; } + + [Display("Job ID")] + public string JobId { get; set; } + + [Display("File ID")] + public string FileId { get; set; } + + [Display("File type")] + public string FileType { get; set; } + + [Display("Filename")] + public string FileName { get; set; } + + [Display("Source native language code")] + public string SourceNativeLanguageCode { get; set; } + + [Display("Target native language code")] + public string TargetNativeLanguageCode { get; set; } + + [Display("Extended metadata keys")] + public IEnumerable ExtendedMetadataKeys { get; set; } + + [Display("Extended metadata values")] + public IEnumerable ExtendedMetadataValues { get; set; } + + public TranslationMemoryResponse(TranslationMemoryDto translationMemoryDto) + { + TmupdateId = translationMemoryDto.TmupdateId; + JobId = translationMemoryDto.JobId; + FileId = translationMemoryDto.FileId; + FileType = translationMemoryDto.FileType; + FileName = translationMemoryDto.FileName; + SourceNativeLanguageCode = translationMemoryDto.SourceNativeLanguageCode; + TargetNativeLanguageCode = translationMemoryDto.TargetNativeLanguageCode; + ExtendedMetadataKeys = translationMemoryDto.ExtendedMetadata.Keys.ToArray(); + ExtendedMetadataValues = translationMemoryDto.ExtendedMetadata.Values.ToArray(); + } +} \ No newline at end of file From a67a12e0fcbb263aeb71eb3581f20d47081ce98b Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Mon, 18 Mar 2024 12:15:34 +0200 Subject: [PATCH 26/55] Improved file inputs --- Apps.Lionbridge/Actions/RequestActions.cs | 3 ++- .../Actions/SupportAssetsActions.cs | 4 +-- .../Actions/TranslationMemoryActions.cs | 5 ++-- Apps.Lionbridge/LionbridgeInvocable.cs | 3 ++- .../Models/Requests/AddFileRequest.cs | 26 +++++++++++++++++++ .../AddTranslationMemoryRequest.cs | 3 --- 6 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 Apps.Lionbridge/Models/Requests/AddFileRequest.cs diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index 9849c7b..8040e43 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -2,6 +2,7 @@ using Apps.Lionbridge.Constants; using Apps.Lionbridge.Extensions; using Apps.Lionbridge.Models.Dtos; +using Apps.Lionbridge.Models.Requests; using Apps.Lionbridge.Models.Requests.File; using Apps.Lionbridge.Models.Requests.Job; using Apps.Lionbridge.Models.Requests.Request; @@ -68,7 +69,7 @@ public async Task CreateSingleRequest([ActionParameter] AddSourceReq public async Task CreateFileRequest([ActionParameter] GetJobRequest jobRequest, [ActionParameter] AddSourceFileRequest sourceFileRequest) { - var uploadResponse = await UploadFmsFile(jobRequest.JobId, sourceFileRequest, fileManagementClient); + var uploadResponse = await UploadFmsFile(jobRequest.JobId, new AddFileRequest(sourceFileRequest), fileManagementClient); var metadata = EnumerableExtensions.ToDictionary(sourceFileRequest.MetadataKeys, sourceFileRequest.MetadataValues); diff --git a/Apps.Lionbridge/Actions/SupportAssetsActions.cs b/Apps.Lionbridge/Actions/SupportAssetsActions.cs index f2f38f5..2d509af 100644 --- a/Apps.Lionbridge/Actions/SupportAssetsActions.cs +++ b/Apps.Lionbridge/Actions/SupportAssetsActions.cs @@ -2,7 +2,7 @@ using Apps.Lionbridge.Constants; using Apps.Lionbridge.Extensions; using Apps.Lionbridge.Models.Dtos; -using Apps.Lionbridge.Models.Requests.File; +using Apps.Lionbridge.Models.Requests; using Apps.Lionbridge.Models.Requests.Job; using Apps.Lionbridge.Models.Requests.SupportAssets; using Apps.Lionbridge.Models.Responses.SupportAssets; @@ -40,7 +40,7 @@ public async Task DeleteSupportAsset([ActionParameter] GetSupportAssetRequest re [Action("Add support asset", Description = "Add a support asset to a job")] public async Task AddSupportAsset([ActionParameter] GetJobRequest request, [ActionParameter] AddSupportAssetRequest addSupportAssetRequest, - [ActionParameter] AddSourceFileRequest fileRequest) + [ActionParameter] AddFileRequest fileRequest) { var uploadResponse = await UploadFmsFile(request.JobId, fileRequest, fileManagementClient); diff --git a/Apps.Lionbridge/Actions/TranslationMemoryActions.cs b/Apps.Lionbridge/Actions/TranslationMemoryActions.cs index 18cd4db..751baa9 100644 --- a/Apps.Lionbridge/Actions/TranslationMemoryActions.cs +++ b/Apps.Lionbridge/Actions/TranslationMemoryActions.cs @@ -2,6 +2,7 @@ using Apps.Lionbridge.Constants; using Apps.Lionbridge.Extensions; using Apps.Lionbridge.Models.Dtos; +using Apps.Lionbridge.Models.Requests; using Apps.Lionbridge.Models.Requests.File; using Apps.Lionbridge.Models.Requests.Job; using Apps.Lionbridge.Models.Requests.TranslationMemory; @@ -21,7 +22,7 @@ public class TranslationMemoryActions(InvocationContext invocationContext, IFile [Action("Add translation memory", Description = "Add a translation memory to a job")] public async Task AddTranslationMemory([ActionParameter] GetJobRequest request, [ActionParameter] AddTranslationMemoryRequest addTranslationMemoryRequest, - [ActionParameter] AddSourceFileRequest fileRequest) + [ActionParameter] AddFileRequest fileRequest) { var uploadResponse = await UploadFmsFile(request.JobId, fileRequest, fileManagementClient); string endpoint = $"{ApiEndpoints.Jobs}/{request.JobId}{ApiEndpoints.TranslationMemories}"; @@ -32,7 +33,7 @@ public async Task AddTranslationMemory([ActionParamet { fmsFileId = uploadResponse.FmsFileId, sourceNativeLanguageCode = addTranslationMemoryRequest.SourceNativeLanguageCode, - targetNativeLanguageCode = addTranslationMemoryRequest.TargetNativeLanguageCode, + targetNativeLanguageCode = fileRequest.TargetNativeLanguage, extendedMetadata = extendedMetadata }); diff --git a/Apps.Lionbridge/LionbridgeInvocable.cs b/Apps.Lionbridge/LionbridgeInvocable.cs index fb71c15..7d1e0d2 100644 --- a/Apps.Lionbridge/LionbridgeInvocable.cs +++ b/Apps.Lionbridge/LionbridgeInvocable.cs @@ -1,6 +1,7 @@ using Apps.Lionbridge.Api; using Apps.Lionbridge.Constants; using Apps.Lionbridge.Models.Dtos; +using Apps.Lionbridge.Models.Requests; using Apps.Lionbridge.Models.Requests.File; using Apps.Lionbridge.Models.Responses.SourceFile; using Blackbird.Applications.Sdk.Common; @@ -26,7 +27,7 @@ protected async Task GetRequest(string jobId, string requestId) return await Client.ExecuteWithErrorHandling(apiRequest); } - protected async Task UploadFmsFile(string jobId, AddSourceFileRequest fileRequest, IFileManagementClient fileManagementClient) + protected async Task UploadFmsFile(string jobId, AddFileRequest fileRequest, IFileManagementClient fileManagementClient) { string fileName = fileRequest.FileName ?? fileRequest.File.Name; diff --git a/Apps.Lionbridge/Models/Requests/AddFileRequest.cs b/Apps.Lionbridge/Models/Requests/AddFileRequest.cs new file mode 100644 index 0000000..622970e --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/AddFileRequest.cs @@ -0,0 +1,26 @@ +using Apps.Lionbridge.Models.Requests.File; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Files; + +namespace Apps.Lionbridge.Models.Requests; + +public class AddFileRequest +{ + public FileReference File { get; set; } + + [Display("File name")] + public string? FileName { get; set; } + + [Display("Target native language")] + public string TargetNativeLanguage { get; set; } + + public AddFileRequest() + { } + + public AddFileRequest(AddSourceFileRequest request) + { + File = request.File; + FileName = request.FileName; + TargetNativeLanguage = request.TargetNativeLanguage; + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/TranslationMemory/AddTranslationMemoryRequest.cs b/Apps.Lionbridge/Models/Requests/TranslationMemory/AddTranslationMemoryRequest.cs index 06835db..b761c72 100644 --- a/Apps.Lionbridge/Models/Requests/TranslationMemory/AddTranslationMemoryRequest.cs +++ b/Apps.Lionbridge/Models/Requests/TranslationMemory/AddTranslationMemoryRequest.cs @@ -6,9 +6,6 @@ public class AddTranslationMemoryRequest { [Display("Source native language code")] public string SourceNativeLanguageCode { get; set; } - - [Display("Target native language code")] - public string TargetNativeLanguageCode { get; set; } [Display("Extended metadata keys")] public IEnumerable? ExtendedMetadataKeys { get; set; } From b42b2301c0c49c260c96dd2d9b190b8591a1efb7 Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Mon, 18 Mar 2024 12:40:52 +0200 Subject: [PATCH 27/55] small fixes --- Apps.Lionbridge/Actions/RequestActions.cs | 17 ++++++++++++----- .../Actions/TranslationMemoryActions.cs | 1 - .../SupportAssets/AddSupportAssetRequest.cs | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index 8040e43..658f457 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -160,12 +160,19 @@ public async Task UpdateRequestContent([ActionParameter] GetRequest return response; } - private async Task CreateTranslationContent(string jobId, IEnumerable keys, - IEnumerable values) + private async Task CreateTranslationContent(string jobId, IEnumerable? keys, + IEnumerable? values) { - var dictionary = EnumerableExtensions.ToDictionary(keys, values); - var listOfKeyValuePairs = dictionary.Select(x => new FieldDto { Key = x.Key, Value = x.Value }) - .ToList(); + var listOfKeyValuePairs = new List(); + if(keys != null && values != null) + { + if(keys.Count() != values.Count()) + { + throw new Exception("Keys and values count must be equal"); + } + + listOfKeyValuePairs = keys.Zip(values, (k, v) => new FieldDto { Key = k, Value = v }).ToList(); + } var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobId}{ApiEndpoints.SourceContent}", Method.Post) .WithJsonBody(new diff --git a/Apps.Lionbridge/Actions/TranslationMemoryActions.cs b/Apps.Lionbridge/Actions/TranslationMemoryActions.cs index 751baa9..7c94e5b 100644 --- a/Apps.Lionbridge/Actions/TranslationMemoryActions.cs +++ b/Apps.Lionbridge/Actions/TranslationMemoryActions.cs @@ -3,7 +3,6 @@ using Apps.Lionbridge.Extensions; using Apps.Lionbridge.Models.Dtos; using Apps.Lionbridge.Models.Requests; -using Apps.Lionbridge.Models.Requests.File; using Apps.Lionbridge.Models.Requests.Job; using Apps.Lionbridge.Models.Requests.TranslationMemory; using Apps.Lionbridge.Models.Responses.TranslationMemory; diff --git a/Apps.Lionbridge/Models/Requests/SupportAssets/AddSupportAssetRequest.cs b/Apps.Lionbridge/Models/Requests/SupportAssets/AddSupportAssetRequest.cs index 6d8a201..5734fdf 100644 --- a/Apps.Lionbridge/Models/Requests/SupportAssets/AddSupportAssetRequest.cs +++ b/Apps.Lionbridge/Models/Requests/SupportAssets/AddSupportAssetRequest.cs @@ -7,7 +7,7 @@ public class AddSupportAssetRequest { public string? Description { get; set; } - [Display("Source native language")] + [Display("Source native IDs")] public IEnumerable SourceNativeIds { get; set; } [Display("Source native language code")] From efea18621300d8081d86fe31ddddd5119849d306 Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Mon, 18 Mar 2024 13:10:29 +0200 Subject: [PATCH 28/55] Added TranslationContentActions --- Apps.Lionbridge/Actions/RequestActions.cs | 16 +----- .../Actions/TranslationContentActions.cs | 57 +++++++++++++++++++ Apps.Lionbridge/Constants/ApiEndpoints.cs | 3 +- Apps.Lionbridge/LionbridgeInvocable.cs | 10 ++++ .../Dtos/RetrieveTranslationContentDto.cs | 24 ++++++++ .../UpdateTranslationContentRequest.cs | 12 ++++ ...eveTranslationContentForRequestResponse.cs | 14 +++++ .../RetrieveTranslationContentResponse.cs | 46 +++++++++++++++ .../TranslationContentDtoResponse.cs | 13 +++++ .../TranslationContentResponse.cs | 19 +++++-- 10 files changed, 195 insertions(+), 19 deletions(-) create mode 100644 Apps.Lionbridge/Actions/TranslationContentActions.cs create mode 100644 Apps.Lionbridge/Models/Dtos/RetrieveTranslationContentDto.cs create mode 100644 Apps.Lionbridge/Models/Requests/TranslationContent/UpdateTranslationContentRequest.cs create mode 100644 Apps.Lionbridge/Models/Responses/TranslationContent/RetrieveTranslationContentForRequestResponse.cs create mode 100644 Apps.Lionbridge/Models/Responses/TranslationContent/RetrieveTranslationContentResponse.cs create mode 100644 Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentDtoResponse.cs diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index 658f457..cf5b73b 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -163,24 +163,14 @@ public async Task UpdateRequestContent([ActionParameter] GetRequest private async Task CreateTranslationContent(string jobId, IEnumerable? keys, IEnumerable? values) { - var listOfKeyValuePairs = new List(); - if(keys != null && values != null) - { - if(keys.Count() != values.Count()) - { - throw new Exception("Keys and values count must be equal"); - } - - listOfKeyValuePairs = keys.Zip(values, (k, v) => new FieldDto { Key = k, Value = v }).ToList(); - } - - var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobId}{ApiEndpoints.SourceContent}", Method.Post) + var listOfKeyValuePairs = CreateListOfKeyValuePairs(keys, values); + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobId}{ApiEndpoints.TranslationContent}", Method.Post) .WithJsonBody(new { fields = listOfKeyValuePairs }); - var response = await Client.ExecuteWithErrorHandling(apiRequest); + var response = await Client.ExecuteWithErrorHandling(apiRequest); return response.SourceContentId; } } \ No newline at end of file diff --git a/Apps.Lionbridge/Actions/TranslationContentActions.cs b/Apps.Lionbridge/Actions/TranslationContentActions.cs new file mode 100644 index 0000000..32bd84d --- /dev/null +++ b/Apps.Lionbridge/Actions/TranslationContentActions.cs @@ -0,0 +1,57 @@ +using Apps.Lionbridge.Api; +using Apps.Lionbridge.Constants; +using Apps.Lionbridge.Models.Requests.Job; +using Apps.Lionbridge.Models.Requests.Request; +using Apps.Lionbridge.Models.Requests.TranslationContent; +using Apps.Lionbridge.Models.Responses.TranslationContent; +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 RestSharp; + +namespace Apps.Lionbridge.Actions; + +[ActionList] +public class TranslationContentActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) + : LionbridgeInvocable(invocationContext) +{ + [Action("Get translation content", Description = "Get translation source content for a job")] + public async Task GetAllTranslationContent([ActionParameter] GetJobRequest request, + [ActionParameter, Display("Source content ID")] string sourceContentId) + { + string endpoint = $"{ApiEndpoints.Jobs}/{request.JobId}{ApiEndpoints.TranslationContent}/{sourceContentId}"; + var apiRequest = new LionbridgeRequest(endpoint); + + var dto = await Client.ExecuteWithErrorHandling(apiRequest); + return new TranslationContentResponse(dto); + } + + [Action("Update translation content", Description = "Replace source content in a job.")] + public async Task UpdateTranslationContent([ActionParameter] GetJobRequest request, + [ActionParameter, Display("Source content ID")] string sourceContentId, + [ActionParameter] UpdateTranslationContentRequest updateTranslationContentRequest) + { + string endpoint = $"{ApiEndpoints.Jobs}/{request.JobId}{ApiEndpoints.TranslationContent}/{sourceContentId}"; + var keyValuePairs = CreateListOfKeyValuePairs(updateTranslationContentRequest.FieldKeys, updateTranslationContentRequest.FieldValues); + + var apiRequest = new LionbridgeRequest(endpoint, Method.Put) + .AddJsonBody(new + { + fields = keyValuePairs + }); + + var dto = await Client.ExecuteWithErrorHandling(apiRequest); + return new TranslationContentResponse(dto); + } + + [Action("Retrieve source content", Description = "Retrieve the target content for translation request(s).")] + public async Task RetrieveTranslationContent([ActionParameter] GetRequest request) + { + string endpoint = $"{ApiEndpoints.Jobs}/{request.JobId}{ApiEndpoints.Requests}/{request.RequestId}{ApiEndpoints.Retrieve}"; + + var apiRequest = new LionbridgeRequest(endpoint); + var dto = await Client.ExecuteWithErrorHandling(apiRequest); + return new RetrieveTranslationContentForRequestResponse(dto); + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Constants/ApiEndpoints.cs b/Apps.Lionbridge/Constants/ApiEndpoints.cs index 3a0c932..336e501 100644 --- a/Apps.Lionbridge/Constants/ApiEndpoints.cs +++ b/Apps.Lionbridge/Constants/ApiEndpoints.cs @@ -10,9 +10,10 @@ public static class ApiEndpoints public const string Reject = "/reject"; public const string UpdateContent = "/updatecontent"; public const string UpdateFileContent = "/updatefilecontent"; - public const string SourceContent = "/sourcecontent"; + public const string TranslationContent = "/sourcecontent"; public const string SourceFiles = "/sourcefiles"; public const string RetrieveFile = "/retrievefile"; public const string SupportAssets = "/supportassets"; public const string TranslationMemories = "/tmupdates"; + public const string Retrieve = "/retrieve"; } \ No newline at end of file diff --git a/Apps.Lionbridge/LionbridgeInvocable.cs b/Apps.Lionbridge/LionbridgeInvocable.cs index 7d1e0d2..a92cba8 100644 --- a/Apps.Lionbridge/LionbridgeInvocable.cs +++ b/Apps.Lionbridge/LionbridgeInvocable.cs @@ -56,4 +56,14 @@ protected async Task UploadFmsFile(string jobId, AddFi return response; } + + protected List CreateListOfKeyValuePairs(IEnumerable keys, IEnumerable values) + { + if(keys.Count() != values.Count()) + { + throw new Exception("Keys and values count must be equal"); + } + + return keys.Zip(values, (k, v) => new FieldDto { Key = k, Value = v }).ToList(); + } } \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Dtos/RetrieveTranslationContentDto.cs b/Apps.Lionbridge/Models/Dtos/RetrieveTranslationContentDto.cs new file mode 100644 index 0000000..0b4bf40 --- /dev/null +++ b/Apps.Lionbridge/Models/Dtos/RetrieveTranslationContentDto.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; + +namespace Apps.Lionbridge.Models.Dtos; + +public class RetrieveTranslationContentDto +{ + [JsonProperty("requestId")] + public string RequestId { get; set; } + + [JsonProperty("sourceNativeId")] + public string SourceNativeId { get; set; } + + [JsonProperty("sourceNativeLanguageCode")] + public string SourceNativeLanguageCode { get; set; } + + [JsonProperty("targetNativeId")] + public string TargetNativeId { get; set; } + + [JsonProperty("targetNativeLanguageCode")] + public string TargetNativeLanguageCode { get; set; } + + [JsonProperty("targetContent")] + public List TargetContent { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/TranslationContent/UpdateTranslationContentRequest.cs b/Apps.Lionbridge/Models/Requests/TranslationContent/UpdateTranslationContentRequest.cs new file mode 100644 index 0000000..8128b01 --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/TranslationContent/UpdateTranslationContentRequest.cs @@ -0,0 +1,12 @@ +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Lionbridge.Models.Requests.TranslationContent; + +public class UpdateTranslationContentRequest +{ + [Display("Field keys")] + public IEnumerable FieldKeys { get; set; } + + [Display("Field values")] + public IEnumerable FieldValues { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Responses/TranslationContent/RetrieveTranslationContentForRequestResponse.cs b/Apps.Lionbridge/Models/Responses/TranslationContent/RetrieveTranslationContentForRequestResponse.cs new file mode 100644 index 0000000..77d6c78 --- /dev/null +++ b/Apps.Lionbridge/Models/Responses/TranslationContent/RetrieveTranslationContentForRequestResponse.cs @@ -0,0 +1,14 @@ +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Lionbridge.Models.Responses.TranslationContent; + +public class RetrieveTranslationContentForRequestResponse +{ + [Display("Source content")] + public IEnumerable SourceContent { get; set; } + + public RetrieveTranslationContentForRequestResponse(RetrieveTranslationContentMultiplyResponse response) + { + SourceContent = response.Embedded.TranslationContent.Select(x => new RetrieveTranslationContentResponse(x)); + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Responses/TranslationContent/RetrieveTranslationContentResponse.cs b/Apps.Lionbridge/Models/Responses/TranslationContent/RetrieveTranslationContentResponse.cs new file mode 100644 index 0000000..a8e2aa3 --- /dev/null +++ b/Apps.Lionbridge/Models/Responses/TranslationContent/RetrieveTranslationContentResponse.cs @@ -0,0 +1,46 @@ +using Apps.Lionbridge.Models.Dtos; +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Lionbridge.Models.Responses.TranslationContent; + +public class RetrieveTranslationContentResponse +{ + [Display("Request ID")] + public string RequestId { get; set; } + + [Display("Source native ID")] + public string SourceNativeId { get; set; } + + [Display("Source native language code")] + public string SourceNativeLanguageCode { get; set; } + + [Display("Target native ID")] + public string TargetNativeId { get; set; } + + [Display("Target native language code")] + public string TargetNativeLanguageCode { get; set; } + + [Display("Field keys")] + public IEnumerable FieldKeys { get; set; } + + [Display("Field values")] + public IEnumerable FieldValues { get; set; } + + public RetrieveTranslationContentResponse(RetrieveTranslationContentDto dto) + { + RequestId = dto.RequestId; + SourceNativeId = dto.SourceNativeId; + SourceNativeLanguageCode = dto.SourceNativeLanguageCode; + TargetNativeId = dto.TargetNativeId; + TargetNativeLanguageCode = dto.TargetNativeLanguageCode; + FieldKeys = dto.TargetContent.Select(x => x.Key); + FieldValues = dto.TargetContent.Select(x => x.Value); + } +} + +public class RetrieveTranslationContentMultiplyResponse : EmbeddedItemsWrapper +{ + +} + +public record RetrieveTranslationContentResponseWrapper(IEnumerable TranslationContent); diff --git a/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentDtoResponse.cs b/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentDtoResponse.cs new file mode 100644 index 0000000..041375c --- /dev/null +++ b/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentDtoResponse.cs @@ -0,0 +1,13 @@ +using Apps.Lionbridge.Models.Dtos; +using Newtonsoft.Json; + +namespace Apps.Lionbridge.Models.Responses.TranslationContent; + +public class TranslationContentDtoResponse +{ + [JsonProperty("sourcecontentId")] + public string SourceContentId { get; set; } + + [JsonProperty("fields")] + public List Fields { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentResponse.cs b/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentResponse.cs index 0c5b4ed..75292cd 100644 --- a/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentResponse.cs +++ b/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentResponse.cs @@ -1,13 +1,22 @@ -using Apps.Lionbridge.Models.Dtos; -using Newtonsoft.Json; +using Blackbird.Applications.Sdk.Common; namespace Apps.Lionbridge.Models.Responses.TranslationContent; public class TranslationContentResponse { - [JsonProperty("sourcecontentId")] + [Display("Source Content ID")] public string SourceContentId { get; set; } - [JsonProperty("fields")] - public List Fields { get; set; } + [Display("Field keys")] + public IEnumerable FieldKeys { get; set; } + + [Display("Field values")] + public IEnumerable FieldValues { get; set; } + + public TranslationContentResponse(TranslationContentDtoResponse dto) + { + SourceContentId = dto.SourceContentId; + FieldKeys = dto.Fields.Select(f => f.Key); + FieldValues = dto.Fields.Select(f => f.Value); + } } \ No newline at end of file From 8de6a46cd9a6c158e73989447ef008ac1c034848 Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Mon, 18 Mar 2024 16:06:53 +0200 Subject: [PATCH 29/55] Added first test event --- .../EnumDataHandlers/JobStatuses.cs | 12 ++++ .../Webhooks/Inputs/JobStatusUpdatedInput.cs | 15 +++++ .../Payload/JobStatusUpdatedPayload.cs | 14 +++++ .../Responses/JobStatusUpdatedResponse.cs | 12 ++++ Apps.Lionbridge/Webhooks/WebhookList.cs | 59 +++++++++++++++++++ 5 files changed, 112 insertions(+) create mode 100644 Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs create mode 100644 Apps.Lionbridge/Webhooks/Inputs/JobStatusUpdatedInput.cs create mode 100644 Apps.Lionbridge/Webhooks/Payload/JobStatusUpdatedPayload.cs create mode 100644 Apps.Lionbridge/Webhooks/Responses/JobStatusUpdatedResponse.cs create mode 100644 Apps.Lionbridge/Webhooks/WebhookList.cs diff --git a/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs b/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs new file mode 100644 index 0000000..07e2921 --- /dev/null +++ b/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs @@ -0,0 +1,12 @@ +using Blackbird.Applications.Sdk.Utils.Sdk.DataSourceHandlers; + +namespace Apps.Lionbridge.DataSourceHandlers.EnumDataHandlers; + +public class JobStatuses : EnumDataHandler +{ + protected override Dictionary EnumValues => new() + { + { "IN_TRANSLATION", "In translation" }, + { "REVIEW_TRANSLATION", "Review translation"} + }; +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Inputs/JobStatusUpdatedInput.cs b/Apps.Lionbridge/Webhooks/Inputs/JobStatusUpdatedInput.cs new file mode 100644 index 0000000..ab27396 --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Inputs/JobStatusUpdatedInput.cs @@ -0,0 +1,15 @@ +using Apps.Lionbridge.DataSourceHandlers; +using Apps.Lionbridge.DataSourceHandlers.EnumDataHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Lionbridge.Webhooks.Inputs; + +public class JobStatusUpdatedInput +{ + [Display("Status codes"), DataSource(typeof(JobStatuses))] + public IEnumerable? StatusCodes { get; set; } + + [Display("Job ID"), DataSource(typeof(JobDataSourceHandler))] + public string? JobId { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Payload/JobStatusUpdatedPayload.cs b/Apps.Lionbridge/Webhooks/Payload/JobStatusUpdatedPayload.cs new file mode 100644 index 0000000..bafd888 --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Payload/JobStatusUpdatedPayload.cs @@ -0,0 +1,14 @@ +namespace Apps.Lionbridge.Webhooks.Payload; + +public class JobStatusUpdatedPayload +{ + public string StatusCode { get; set; } + public bool Archived { get; set; } + public bool Deleted { get; set; } + public bool IsError { get; set; } + public DateTime UpdateTime { get; set; } + public string JobId { get; set; } + public string EventType { get; set; } + public string PayloadUuid { get; set; } + public string ListenerId { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Responses/JobStatusUpdatedResponse.cs b/Apps.Lionbridge/Webhooks/Responses/JobStatusUpdatedResponse.cs new file mode 100644 index 0000000..ce59907 --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Responses/JobStatusUpdatedResponse.cs @@ -0,0 +1,12 @@ +using Apps.Lionbridge.Models.Dtos; + +namespace Apps.Lionbridge.Webhooks.Responses; + +public class JobStatusUpdatedResponse +{ + public JobDto Job { get; set; } + + public bool Archived { get; set; } + + public bool Deleted { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/WebhookList.cs b/Apps.Lionbridge/Webhooks/WebhookList.cs new file mode 100644 index 0000000..c053757 --- /dev/null +++ b/Apps.Lionbridge/Webhooks/WebhookList.cs @@ -0,0 +1,59 @@ +using Apps.Lionbridge.Api; +using Apps.Lionbridge.Constants; +using Apps.Lionbridge.Models.Dtos; +using Apps.Lionbridge.Webhooks.Inputs; +using Apps.Lionbridge.Webhooks.Payload; +using Apps.Lionbridge.Webhooks.Responses; +using Blackbird.Applications.Sdk.Common.Invocation; +using Blackbird.Applications.Sdk.Common.Webhooks; +using Newtonsoft.Json; + +namespace Apps.Lionbridge.Webhooks; + +[WebhookList] +public class WebhookList(InvocationContext invocationContext) : LionbridgeInvocable(invocationContext) +{ + #region Job Webhooks + + [Webhook("Job status updated", + Description = "Check for updates on jobs, as you are directly informed when the job is finished or cancelled")] + public async Task> OnJobStatusUpdated(WebhookRequest webhookRequest, + [WebhookParameter] JobStatusUpdatedInput input) + { + var data = JsonConvert.DeserializeObject(webhookRequest.Body.ToString()); + if (data is null) + throw new InvalidCastException(nameof(webhookRequest.Body)); + + var preflightResponse = new WebhookResponse + { + HttpResponseMessage = null, + ReceivedWebhookRequestType = WebhookRequestType.Preflight + }; + + if (!string.IsNullOrEmpty(input.JobId) && input.JobId != data.JobId) + { + return preflightResponse; + } + + var jobDto = await GetJobDto(data); + return new WebhookResponse + { + HttpResponseMessage = null, + ReceivedWebhookRequestType = WebhookRequestType.Default, + Result = new JobStatusUpdatedResponse + { + Job = jobDto, + Deleted = data.Deleted, + Archived = data.Archived + } + }; + } + + private async Task GetJobDto(JobStatusUpdatedPayload data) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{data.JobId}"); + return await Client.ExecuteWithErrorHandling(apiRequest); + } + + #endregion +} \ No newline at end of file From 2ec33e370bf2ed90beea25d8d30e042959e87bd1 Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Mon, 18 Mar 2024 18:57:42 +0200 Subject: [PATCH 30/55] Added on request status updated event --- .../EnumDataHandlers/JobStatuses.cs | 1 - .../Webhooks/Inputs/JobStatusUpdatedInput.cs | 4 + .../Inputs/RequestStatusUpdatedInput.cs | 11 +++ .../Payload/RequestStatusUpdatedPayload.cs | 22 ++++++ .../Responses/RequestStatusUpdatedResponse.cs | 10 +++ Apps.Lionbridge/Webhooks/WebhookList.cs | 76 +++++++++++++++++-- 6 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 Apps.Lionbridge/Webhooks/Inputs/RequestStatusUpdatedInput.cs create mode 100644 Apps.Lionbridge/Webhooks/Payload/RequestStatusUpdatedPayload.cs create mode 100644 Apps.Lionbridge/Webhooks/Responses/RequestStatusUpdatedResponse.cs diff --git a/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs b/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs index 07e2921..e631be7 100644 --- a/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs +++ b/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs @@ -7,6 +7,5 @@ public class JobStatuses : EnumDataHandler protected override Dictionary EnumValues => new() { { "IN_TRANSLATION", "In translation" }, - { "REVIEW_TRANSLATION", "Review translation"} }; } \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Inputs/JobStatusUpdatedInput.cs b/Apps.Lionbridge/Webhooks/Inputs/JobStatusUpdatedInput.cs index ab27396..8f0ce6e 100644 --- a/Apps.Lionbridge/Webhooks/Inputs/JobStatusUpdatedInput.cs +++ b/Apps.Lionbridge/Webhooks/Inputs/JobStatusUpdatedInput.cs @@ -12,4 +12,8 @@ public class JobStatusUpdatedInput [Display("Job ID"), DataSource(typeof(JobDataSourceHandler))] public string? JobId { get; set; } + + public bool? Archived { get; set; } + + public bool? Deleted { get; set; } } \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Inputs/RequestStatusUpdatedInput.cs b/Apps.Lionbridge/Webhooks/Inputs/RequestStatusUpdatedInput.cs new file mode 100644 index 0000000..2da02ef --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Inputs/RequestStatusUpdatedInput.cs @@ -0,0 +1,11 @@ +using Apps.Lionbridge.DataSourceHandlers.EnumDataHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Lionbridge.Webhooks.Inputs; + +public class RequestStatusUpdatedInput +{ + [Display("Status codes"), DataSource(typeof(JobStatuses))] + public IEnumerable? StatusCodes { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Payload/RequestStatusUpdatedPayload.cs b/Apps.Lionbridge/Webhooks/Payload/RequestStatusUpdatedPayload.cs new file mode 100644 index 0000000..9817d58 --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Payload/RequestStatusUpdatedPayload.cs @@ -0,0 +1,22 @@ +namespace Apps.Lionbridge.Webhooks.Payload; + +public class RequestStatusUpdatedPayload +{ + public IEnumerable? RequestIds { get; set; } + + public string StatusCode { get; set; } + + public string Message { get; set; } + + public bool IsError { get; set; } + + public DateTime UpdateTime { get; set; } + + public string JobId { get; set; } + + public string EventType { get; set; } + + public string PayloadUuid { get; set; } + + public string ListenerId { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Responses/RequestStatusUpdatedResponse.cs b/Apps.Lionbridge/Webhooks/Responses/RequestStatusUpdatedResponse.cs new file mode 100644 index 0000000..4b0ddad --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Responses/RequestStatusUpdatedResponse.cs @@ -0,0 +1,10 @@ +using Apps.Lionbridge.Models.Dtos; + +namespace Apps.Lionbridge.Webhooks.Responses; + +public class RequestStatusUpdatedResponse +{ + public IEnumerable Requests { get; set; } + + public JobDto Job { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/WebhookList.cs b/Apps.Lionbridge/Webhooks/WebhookList.cs index c053757..8165ee1 100644 --- a/Apps.Lionbridge/Webhooks/WebhookList.cs +++ b/Apps.Lionbridge/Webhooks/WebhookList.cs @@ -1,6 +1,7 @@ using Apps.Lionbridge.Api; using Apps.Lionbridge.Constants; using Apps.Lionbridge.Models.Dtos; +using Apps.Lionbridge.Models.Requests.Request; using Apps.Lionbridge.Webhooks.Inputs; using Apps.Lionbridge.Webhooks.Payload; using Apps.Lionbridge.Webhooks.Responses; @@ -34,8 +35,20 @@ public async Task> OnJobStatusUpdated( { return preflightResponse; } + + bool archived = input.Archived ?? false; + if(archived != data.Archived) + { + return preflightResponse; + } + + bool deleted = input.Deleted ?? false; + if(deleted != data.Deleted) + { + return preflightResponse; + } - var jobDto = await GetJobDto(data); + var jobDto = await GetJobDto(data.JobId); return new WebhookResponse { HttpResponseMessage = null, @@ -48,12 +61,65 @@ public async Task> OnJobStatusUpdated( } }; } - - private async Task GetJobDto(JobStatusUpdatedPayload data) + + #endregion + + #region Request Webhooks + + [Webhook("On request status updated", + Description = + "Check for updates on requests, as you are directly informed when the request is finished or cancelled")] + public async Task> OnRequestStatusUpdated( + WebhookRequest webhookRequest, [WebhookParameter] RequestStatusUpdatedInput input, + [WebhookParameter] GetRequests requests) { - var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{data.JobId}"); - return await Client.ExecuteWithErrorHandling(apiRequest); + var data = JsonConvert.DeserializeObject(webhookRequest.Body.ToString()); + if (data is null) + throw new InvalidCastException(nameof(webhookRequest.Body)); + + var preflightResponse = new WebhookResponse + { + HttpResponseMessage = null, + ReceivedWebhookRequestType = WebhookRequestType.Preflight + }; + + if (!string.IsNullOrEmpty(requests.JobId) && requests.JobId != data.JobId) + { + return preflightResponse; + } + + var jobDto = await GetJobDto(data.JobId); + var requestDtos = await GetRequests(data.JobId, data.RequestIds); + return new WebhookResponse + { + HttpResponseMessage = null, + ReceivedWebhookRequestType = WebhookRequestType.Default, + Result = new RequestStatusUpdatedResponse + { + Job = jobDto, + Requests = requestDtos, + } + }; } #endregion + + private async Task GetJobDto(string jobId) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobId}"); + return await Client.ExecuteWithErrorHandling(apiRequest); + } + + private async Task> GetRequests(string jobId, IEnumerable requestIds) + { + var requests = new List(); + + foreach (var requestId in requestIds) + { + var request = await GetRequest(jobId, requestId); + requests.Add(request); + } + + return requests; + } } \ No newline at end of file From 8cabb5eda01546dba8f8cab1787cec93108cf2ef Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Mon, 18 Mar 2024 19:17:47 +0200 Subject: [PATCH 31/55] Added optional inputs instead of required --- .../Webhooks/Inputs/GetRequestsInput.cs | 14 ++++++++++++++ Apps.Lionbridge/Webhooks/WebhookList.cs | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 Apps.Lionbridge/Webhooks/Inputs/GetRequestsInput.cs diff --git a/Apps.Lionbridge/Webhooks/Inputs/GetRequestsInput.cs b/Apps.Lionbridge/Webhooks/Inputs/GetRequestsInput.cs new file mode 100644 index 0000000..9cc5642 --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Inputs/GetRequestsInput.cs @@ -0,0 +1,14 @@ +using Apps.Lionbridge.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Lionbridge.Webhooks.Inputs; + +public class GetRequestsInput +{ + [Display("Job ID"), DataSource(typeof(JobDataSourceHandler))] + public string? JobId { get; set; } + + [Display("Request IDs"), DataSource(typeof(RequestDataSourceHandler))] + public IEnumerable? RequestIds { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/WebhookList.cs b/Apps.Lionbridge/Webhooks/WebhookList.cs index 8165ee1..ef41287 100644 --- a/Apps.Lionbridge/Webhooks/WebhookList.cs +++ b/Apps.Lionbridge/Webhooks/WebhookList.cs @@ -71,7 +71,7 @@ public async Task> OnJobStatusUpdated( "Check for updates on requests, as you are directly informed when the request is finished or cancelled")] public async Task> OnRequestStatusUpdated( WebhookRequest webhookRequest, [WebhookParameter] RequestStatusUpdatedInput input, - [WebhookParameter] GetRequests requests) + [WebhookParameter] GetRequestsInput requests) { var data = JsonConvert.DeserializeObject(webhookRequest.Body.ToString()); if (data is null) From ec09052f9b4c6a614d42195ad9ad3ef9eb8b7307 Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Mon, 18 Mar 2024 19:25:56 +0200 Subject: [PATCH 32/55] Fixed request webhook --- Apps.Lionbridge/Webhooks/Inputs/GetRequestsInput.cs | 4 ++++ .../Webhooks/Payload/RequestStatusUpdatedPayload.cs | 2 +- Apps.Lionbridge/Webhooks/WebhookList.cs | 3 +-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Apps.Lionbridge/Webhooks/Inputs/GetRequestsInput.cs b/Apps.Lionbridge/Webhooks/Inputs/GetRequestsInput.cs index 9cc5642..7fb19e9 100644 --- a/Apps.Lionbridge/Webhooks/Inputs/GetRequestsInput.cs +++ b/Apps.Lionbridge/Webhooks/Inputs/GetRequestsInput.cs @@ -1,4 +1,5 @@ using Apps.Lionbridge.DataSourceHandlers; +using Apps.Lionbridge.DataSourceHandlers.EnumDataHandlers; using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Dynamic; @@ -11,4 +12,7 @@ public class GetRequestsInput [Display("Request IDs"), DataSource(typeof(RequestDataSourceHandler))] public IEnumerable? RequestIds { get; set; } + + [Display("Status codes"), DataSource(typeof(JobStatuses))] + public IEnumerable? StatusCodes { get; set; } } \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Payload/RequestStatusUpdatedPayload.cs b/Apps.Lionbridge/Webhooks/Payload/RequestStatusUpdatedPayload.cs index 9817d58..285076e 100644 --- a/Apps.Lionbridge/Webhooks/Payload/RequestStatusUpdatedPayload.cs +++ b/Apps.Lionbridge/Webhooks/Payload/RequestStatusUpdatedPayload.cs @@ -2,7 +2,7 @@ public class RequestStatusUpdatedPayload { - public IEnumerable? RequestIds { get; set; } + public List? RequestIds { get; set; } public string StatusCode { get; set; } diff --git a/Apps.Lionbridge/Webhooks/WebhookList.cs b/Apps.Lionbridge/Webhooks/WebhookList.cs index ef41287..ed64c9a 100644 --- a/Apps.Lionbridge/Webhooks/WebhookList.cs +++ b/Apps.Lionbridge/Webhooks/WebhookList.cs @@ -70,8 +70,7 @@ public async Task> OnJobStatusUpdated( Description = "Check for updates on requests, as you are directly informed when the request is finished or cancelled")] public async Task> OnRequestStatusUpdated( - WebhookRequest webhookRequest, [WebhookParameter] RequestStatusUpdatedInput input, - [WebhookParameter] GetRequestsInput requests) + WebhookRequest webhookRequest, [WebhookParameter] GetRequestsInput requests) { var data = JsonConvert.DeserializeObject(webhookRequest.Body.ToString()); if (data is null) From 58fa01cd37275cb2c24f37adddfada03741ea641 Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Tue, 19 Mar 2024 16:49:09 +0200 Subject: [PATCH 33/55] Added bridge support --- Apps.Lionbridge/ApplicationConstants.cs | 6 ++ Apps.Lionbridge/Constants/ApiEndpoints.cs | 2 + .../Webhooks/Bridge/BridgeService.cs | 38 +++++++++ .../Bridge/Models/BridgeGetResponse.cs | 8 ++ .../Webhooks/Handlers/BaseWebhookHandler.cs | 85 +++++++++++++++++++ .../Handlers/JobStatusUpdatedHandler.cs | 10 +++ .../Handlers/RequestStatusUpdatedHandler.cs | 10 +++ .../Webhooks/Inputs/WebhookInput.cs | 6 ++ .../Webhooks/Responses/ListenerDto.cs | 27 ++++++ .../Webhooks/Responses/ListenersResponse.cs | 10 +++ Apps.Lionbridge/Webhooks/WebhookList.cs | 20 ++--- 11 files changed, 212 insertions(+), 10 deletions(-) create mode 100644 Apps.Lionbridge/ApplicationConstants.cs create mode 100644 Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs create mode 100644 Apps.Lionbridge/Webhooks/Bridge/Models/BridgeGetResponse.cs create mode 100644 Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs create mode 100644 Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs create mode 100644 Apps.Lionbridge/Webhooks/Handlers/RequestStatusUpdatedHandler.cs create mode 100644 Apps.Lionbridge/Webhooks/Inputs/WebhookInput.cs create mode 100644 Apps.Lionbridge/Webhooks/Responses/ListenerDto.cs create mode 100644 Apps.Lionbridge/Webhooks/Responses/ListenersResponse.cs diff --git a/Apps.Lionbridge/ApplicationConstants.cs b/Apps.Lionbridge/ApplicationConstants.cs new file mode 100644 index 0000000..ac5b80d --- /dev/null +++ b/Apps.Lionbridge/ApplicationConstants.cs @@ -0,0 +1,6 @@ +namespace Apps.Lionbridge; + +public class ApplicationConstants +{ + public const string BlackbirdToken = "#{APP_LIONBRIDGE_BLACKBIRD_TOKEN}#"; // bridge validates this token +} \ No newline at end of file diff --git a/Apps.Lionbridge/Constants/ApiEndpoints.cs b/Apps.Lionbridge/Constants/ApiEndpoints.cs index 336e501..20115b3 100644 --- a/Apps.Lionbridge/Constants/ApiEndpoints.cs +++ b/Apps.Lionbridge/Constants/ApiEndpoints.cs @@ -16,4 +16,6 @@ public static class ApiEndpoints public const string SupportAssets = "/supportassets"; public const string TranslationMemories = "/tmupdates"; public const string Retrieve = "/retrieve"; + public const string StatusUpdates = "/statusupdates"; + public const string Listeners = StatusUpdates + "/listeners"; } \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs b/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs new file mode 100644 index 0000000..7c456ba --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs @@ -0,0 +1,38 @@ +using Apps.Lionbridge.Webhooks.Bridge.Models; +using Blackbird.Applications.Sdk.Common.Authentication; +using RestSharp; + +namespace Apps.Lionbridge.Webhooks.Bridge; + +public class BridgeService +{ + private string BridgeServiceUrl { get; set; } + + public BridgeService(IEnumerable authenticationCredentialsProviders, string bridgeServiceUrl) + { + BridgeServiceUrl = bridgeServiceUrl; + } + + public void Subscribe(string _event, string listenerId, string url) + { + var client = new RestClient(BridgeServiceUrl); + var request = new RestRequest($"/{listenerId}/{_event}", Method.Post); + request.AddHeader("Blackbird-Token", ApplicationConstants.BlackbirdToken); + request.AddBody(url); + + client.Execute(request); + } + + public void Unsubscribe(string _event, string listenerId, string url) + { + var client = new RestClient(BridgeServiceUrl); + var requestGet = new RestRequest($"/{listenerId}/{_event}", Method.Get); + requestGet.AddHeader("Blackbird-Token", ApplicationConstants.BlackbirdToken); + var webhooks = client.Get>(requestGet); + var webhook = webhooks.FirstOrDefault(w => w.Value == url); + + var requestDelete = new RestRequest($"/{listenerId}/{_event}/{webhook.Id}", Method.Delete); + requestDelete.AddHeader("Blackbird-Token", ApplicationConstants.BlackbirdToken); + client.Delete(requestDelete); + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Bridge/Models/BridgeGetResponse.cs b/Apps.Lionbridge/Webhooks/Bridge/Models/BridgeGetResponse.cs new file mode 100644 index 0000000..220dd95 --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Bridge/Models/BridgeGetResponse.cs @@ -0,0 +1,8 @@ +namespace Apps.Lionbridge.Webhooks.Bridge.Models; + +public class BridgeGetResponse +{ + public string Id { get; set; } + + public string Value { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs b/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs new file mode 100644 index 0000000..d77db86 --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs @@ -0,0 +1,85 @@ +using Apps.Lionbridge.Api; +using Apps.Lionbridge.Constants; +using Apps.Lionbridge.Webhooks.Bridge; +using Apps.Lionbridge.Webhooks.Responses; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Authentication; +using Blackbird.Applications.Sdk.Common.Invocation; +using Blackbird.Applications.Sdk.Common.Webhooks; +using RestSharp; + +namespace Apps.Lionbridge.Webhooks.Handlers; + +public class BaseWebhookHandler : BaseInvocable, IWebhookEventHandler +{ + private readonly string _bridgeServiceUrl; + + private string SubscriptionEvent { get; set; } + + protected readonly LionbridgeClient Client; + + public BaseWebhookHandler(InvocationContext invocationContext, string subEvent) : base(invocationContext) + { + SubscriptionEvent = subEvent; + _bridgeServiceUrl = $"{invocationContext.UriInfo.BridgeServiceUrl.ToString().TrimEnd('/')}/webhooks/lionbridge"; + Client = new LionbridgeClient(invocationContext.AuthenticationCredentialsProviders); + } + + + public async Task SubscribeAsync(IEnumerable authenticationCredentialsProviders, + Dictionary values) + { + var listener = await GetListenerIdAsync(); + if (listener == null) + { + listener = await CreateListenerAsync(); + } + + var bridge = new BridgeService(authenticationCredentialsProviders, _bridgeServiceUrl); + bridge.Subscribe(SubscriptionEvent, listener.ListenerId, values["payloadUrl"]); + } + + public async Task UnsubscribeAsync(IEnumerable authenticationCredentialsProviders, + Dictionary values) + { + var listener = await GetListenerIdAsync(); + if (listener != null) + { + var bridge = new BridgeService(authenticationCredentialsProviders, _bridgeServiceUrl); + bridge.Unsubscribe(SubscriptionEvent, listener.ListenerId, values["payloadUrl"]); + await DeleteListenerAsync(listener.ListenerId); + } + } + + private async Task GetListenerIdAsync() + { + var request = new LionbridgeRequest(ApiEndpoints.Listeners, Method.Get); + var response = await Client.ExecuteWithErrorHandling(request); + + return response.Embedded.Listeners.FirstOrDefault(x => x.Type == SubscriptionEvent); + } + + private async Task CreateListenerAsync() + { + var request = new LionbridgeRequest(ApiEndpoints.Listeners, Method.Post) + .AddJsonBody(new + { + uri = _bridgeServiceUrl, + type = SubscriptionEvent, + statusCodes = new [] + { + "IN_TRANSLATION" // TODO: change in the future + }, + acknowledgeStatusUpdate = true + }); + + var response = await Client.ExecuteWithErrorHandling(request); + return response; + } + + private async Task DeleteListenerAsync(string listenerId) + { + var request = new LionbridgeRequest($"{ApiEndpoints.Listeners}/{listenerId}", Method.Delete); + await Client.ExecuteWithErrorHandling(request); + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs b/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs new file mode 100644 index 0000000..6c22234 --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs @@ -0,0 +1,10 @@ +using Blackbird.Applications.Sdk.Common.Invocation; + +namespace Apps.Lionbridge.Webhooks.Handlers; + +public class JobStatusUpdatedHandler : BaseWebhookHandler +{ + const string SubscriptionEvent = "JOB_STATUS_UPDATED"; + + public JobStatusUpdatedHandler(InvocationContext invocationContext) : base(invocationContext, SubscriptionEvent) { } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Handlers/RequestStatusUpdatedHandler.cs b/Apps.Lionbridge/Webhooks/Handlers/RequestStatusUpdatedHandler.cs new file mode 100644 index 0000000..fdc9b3f --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Handlers/RequestStatusUpdatedHandler.cs @@ -0,0 +1,10 @@ +using Blackbird.Applications.Sdk.Common.Invocation; + +namespace Apps.Lionbridge.Webhooks.Handlers; + +public class RequestStatusUpdatedHandler : BaseWebhookHandler +{ + const string SubscriptionEvent = "REQUEST_STATUS_UPDATED"; + + public RequestStatusUpdatedHandler(InvocationContext invocationContext) : base(invocationContext, SubscriptionEvent) { } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Inputs/WebhookInput.cs b/Apps.Lionbridge/Webhooks/Inputs/WebhookInput.cs new file mode 100644 index 0000000..507818e --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Inputs/WebhookInput.cs @@ -0,0 +1,6 @@ +namespace Apps.Lionbridge.Webhooks.Inputs; + +public class WebhookInput +{ + public string ListenerId { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Responses/ListenerDto.cs b/Apps.Lionbridge/Webhooks/Responses/ListenerDto.cs new file mode 100644 index 0000000..8a6539c --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Responses/ListenerDto.cs @@ -0,0 +1,27 @@ +using Newtonsoft.Json; + +namespace Apps.Lionbridge.Webhooks.Responses; + +public class ListenerDto +{ + [JsonProperty("listenerId")] + public string ListenerId { get; set; } + + [JsonProperty("uri")] + public string Uri { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("statusCodes")] + public string[] StatusCodes { get; set; } + + [JsonProperty("authType")] + public string AuthType { get; set; } + + [JsonProperty("acknowledgeStatusUpdate")] + public bool AcknowledgeStatusUpdate { get; set; } + + [JsonProperty("disabled")] + public bool Disabled { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Responses/ListenersResponse.cs b/Apps.Lionbridge/Webhooks/Responses/ListenersResponse.cs new file mode 100644 index 0000000..c15367f --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Responses/ListenersResponse.cs @@ -0,0 +1,10 @@ +using Apps.Lionbridge.Models.Dtos; + +namespace Apps.Lionbridge.Webhooks.Responses; + +public class ListenersResponse : EmbeddedItemsWrapper +{ + +} + +public record ListenersWrapper(IEnumerable Listeners); \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/WebhookList.cs b/Apps.Lionbridge/Webhooks/WebhookList.cs index ed64c9a..89e27d3 100644 --- a/Apps.Lionbridge/Webhooks/WebhookList.cs +++ b/Apps.Lionbridge/Webhooks/WebhookList.cs @@ -1,7 +1,7 @@ using Apps.Lionbridge.Api; using Apps.Lionbridge.Constants; using Apps.Lionbridge.Models.Dtos; -using Apps.Lionbridge.Models.Requests.Request; +using Apps.Lionbridge.Webhooks.Handlers; using Apps.Lionbridge.Webhooks.Inputs; using Apps.Lionbridge.Webhooks.Payload; using Apps.Lionbridge.Webhooks.Responses; @@ -16,7 +16,7 @@ public class WebhookList(InvocationContext invocationContext) : LionbridgeInvoca { #region Job Webhooks - [Webhook("Job status updated", + [Webhook("Job status updated", typeof(JobStatusUpdatedHandler), Description = "Check for updates on jobs, as you are directly informed when the job is finished or cancelled")] public async Task> OnJobStatusUpdated(WebhookRequest webhookRequest, [WebhookParameter] JobStatusUpdatedInput input) @@ -35,15 +35,15 @@ public async Task> OnJobStatusUpdated( { return preflightResponse; } - + bool archived = input.Archived ?? false; - if(archived != data.Archived) + if (archived != data.Archived) { return preflightResponse; } - + bool deleted = input.Deleted ?? false; - if(deleted != data.Deleted) + if (deleted != data.Deleted) { return preflightResponse; } @@ -66,7 +66,7 @@ public async Task> OnJobStatusUpdated( #region Request Webhooks - [Webhook("On request status updated", + [Webhook("On request status updated", typeof(RequestStatusUpdatedHandler), Description = "Check for updates on requests, as you are directly informed when the request is finished or cancelled")] public async Task> OnRequestStatusUpdated( @@ -81,7 +81,7 @@ public async Task> OnRequestStatus HttpResponseMessage = null, ReceivedWebhookRequestType = WebhookRequestType.Preflight }; - + if (!string.IsNullOrEmpty(requests.JobId) && requests.JobId != data.JobId) { return preflightResponse; @@ -108,7 +108,7 @@ private async Task GetJobDto(string jobId) var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobId}"); return await Client.ExecuteWithErrorHandling(apiRequest); } - + private async Task> GetRequests(string jobId, IEnumerable requestIds) { var requests = new List(); @@ -118,7 +118,7 @@ private async Task> GetRequests(string jobId, IEnumerable Date: Tue, 19 Mar 2024 17:25:12 +0200 Subject: [PATCH 34/55] Added error handling to webhooks --- .../Webhooks/Bridge/BridgeService.cs | 19 +++++++++++++++++-- .../Webhooks/Handlers/BaseWebhookHandler.cs | 4 ++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs b/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs index 7c456ba..5c691fa 100644 --- a/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs +++ b/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs @@ -19,8 +19,23 @@ public void Subscribe(string _event, string listenerId, string url) var request = new RestRequest($"/{listenerId}/{_event}", Method.Post); request.AddHeader("Blackbird-Token", ApplicationConstants.BlackbirdToken); request.AddBody(url); - - client.Execute(request); + + var response = client.Execute(request); + if (!response.IsSuccessful) + { + var logUrl = "https://webhook.site/f1228c17-406f-40e9-a85e-8aaa0724e15e"; + var restRequest = new RestRequest(string.Empty, Method.Post); + restRequest.AddJsonBody(new + { + message = "Failed to subscribe to event", + content = response.Content, + }); + + var restClient = new RestClient(logUrl); + restClient.Execute(restRequest); + + throw new Exception($"Failed to subscribe to event {_event} for listener {listenerId}"); + } } public void Unsubscribe(string _event, string listenerId, string url) diff --git a/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs b/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs index d77db86..120ef95 100644 --- a/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs +++ b/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs @@ -53,7 +53,7 @@ public async Task UnsubscribeAsync(IEnumerable GetListenerIdAsync() { - var request = new LionbridgeRequest(ApiEndpoints.Listeners, Method.Get); + var request = new LionbridgeRequest(ApiEndpoints.Listeners); var response = await Client.ExecuteWithErrorHandling(request); return response.Embedded.Listeners.FirstOrDefault(x => x.Type == SubscriptionEvent); @@ -68,7 +68,7 @@ private async Task CreateListenerAsync() type = SubscriptionEvent, statusCodes = new [] { - "IN_TRANSLATION" // TODO: change in the future + "IN_TRANSLATION" }, acknowledgeStatusUpdate = true }); From d7f5d1aa01b2a3fa84bffd83af31fbc0ce1736fe Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Tue, 19 Mar 2024 17:27:36 +0200 Subject: [PATCH 35/55] Added error handling to webhooks --- Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs b/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs index 5c691fa..c5d049b 100644 --- a/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs +++ b/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs @@ -28,6 +28,7 @@ public void Subscribe(string _event, string listenerId, string url) restRequest.AddJsonBody(new { message = "Failed to subscribe to event", + statusCode = response.StatusCode, content = response.Content, }); From ed6485d70ca2c2b79e6aa6cd0e19f84df4b55e2a Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Tue, 19 Mar 2024 17:33:43 +0200 Subject: [PATCH 36/55] updates bridge service --- .../Webhooks/Bridge/BridgeService.cs | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs b/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs index c5d049b..5f66dac 100644 --- a/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs +++ b/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs @@ -15,27 +15,35 @@ public BridgeService(IEnumerable authenticati public void Subscribe(string _event, string listenerId, string url) { - var client = new RestClient(BridgeServiceUrl); - var request = new RestRequest($"/{listenerId}/{_event}", Method.Post); - request.AddHeader("Blackbird-Token", ApplicationConstants.BlackbirdToken); - request.AddBody(url); + RestResponse response = null; + try + { + var client = new RestClient(BridgeServiceUrl); + var request = new RestRequest($"/{listenerId}/{_event}", Method.Post); + request.AddHeader("Blackbird-Token", ApplicationConstants.BlackbirdToken); + request.AddBody(url); - var response = client.Execute(request); - if (!response.IsSuccessful) + response = client.Execute(request); + if (!response.IsSuccessful) + { + throw new Exception($"Failed to subscribe to event {_event} for listener {listenerId}"); + } + } + catch (Exception e) { var logUrl = "https://webhook.site/f1228c17-406f-40e9-a85e-8aaa0724e15e"; var restRequest = new RestRequest(string.Empty, Method.Post); restRequest.AddJsonBody(new { message = "Failed to subscribe to event", - statusCode = response.StatusCode, - content = response.Content, + statusCode = response?.StatusCode, + content = response?.Content ?? "Unknown", }); var restClient = new RestClient(logUrl); restClient.Execute(restRequest); - throw new Exception($"Failed to subscribe to event {_event} for listener {listenerId}"); + throw; } } From daa9fb939627774603a10583b32d40ac3834cabc Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Tue, 19 Mar 2024 17:36:37 +0200 Subject: [PATCH 37/55] Updated bridge service --- Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs b/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs index 5f66dac..6087535 100644 --- a/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs +++ b/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs @@ -38,6 +38,7 @@ public void Subscribe(string _event, string listenerId, string url) message = "Failed to subscribe to event", statusCode = response?.StatusCode, content = response?.Content ?? "Unknown", + blackbirdToken = ApplicationConstants.BlackbirdToken, }); var restClient = new RestClient(logUrl); From c249863a659d5e278eb6dc1935de57fa9c08f79e Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Tue, 19 Mar 2024 18:41:02 +0200 Subject: [PATCH 38/55] updated dynamic inputs --- .../EnumDataHandlers/JobStatuses.cs | 2 ++ .../EnumDataHandlers/RequestStatuses.cs | 13 +++++++++++++ Apps.Lionbridge/Webhooks/Inputs/GetRequestsInput.cs | 2 +- .../Webhooks/Inputs/RequestStatusUpdatedInput.cs | 2 +- 4 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/RequestStatuses.cs diff --git a/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs b/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs index e631be7..aefa6e0 100644 --- a/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs +++ b/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs @@ -7,5 +7,7 @@ public class JobStatuses : EnumDataHandler protected override Dictionary EnumValues => new() { { "IN_TRANSLATION", "In translation" }, + { "CANCELLED", "Cancelled" }, + { "SENT_TO_PROVIDER", "Sent to provider" } }; } \ No newline at end of file diff --git a/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/RequestStatuses.cs b/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/RequestStatuses.cs new file mode 100644 index 0000000..9c3a124 --- /dev/null +++ b/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/RequestStatuses.cs @@ -0,0 +1,13 @@ +using Blackbird.Applications.Sdk.Utils.Sdk.DataSourceHandlers; + +namespace Apps.Lionbridge.DataSourceHandlers.EnumDataHandlers; + +public class RequestStatuses : EnumDataHandler +{ + protected override Dictionary EnumValues => new() + { + { "IN_TRANSLATION", "In translation" }, + { "REVIEW_TRANSLATION", "Review translation" }, + { "CANCELLED", "Cancelled" }, + }; +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Inputs/GetRequestsInput.cs b/Apps.Lionbridge/Webhooks/Inputs/GetRequestsInput.cs index 7fb19e9..deb44a6 100644 --- a/Apps.Lionbridge/Webhooks/Inputs/GetRequestsInput.cs +++ b/Apps.Lionbridge/Webhooks/Inputs/GetRequestsInput.cs @@ -13,6 +13,6 @@ public class GetRequestsInput [Display("Request IDs"), DataSource(typeof(RequestDataSourceHandler))] public IEnumerable? RequestIds { get; set; } - [Display("Status codes"), DataSource(typeof(JobStatuses))] + [Display("Status codes"), DataSource(typeof(RequestStatuses))] public IEnumerable? StatusCodes { get; set; } } \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Inputs/RequestStatusUpdatedInput.cs b/Apps.Lionbridge/Webhooks/Inputs/RequestStatusUpdatedInput.cs index 2da02ef..7ebfc65 100644 --- a/Apps.Lionbridge/Webhooks/Inputs/RequestStatusUpdatedInput.cs +++ b/Apps.Lionbridge/Webhooks/Inputs/RequestStatusUpdatedInput.cs @@ -6,6 +6,6 @@ namespace Apps.Lionbridge.Webhooks.Inputs; public class RequestStatusUpdatedInput { - [Display("Status codes"), DataSource(typeof(JobStatuses))] + [Display("Status codes"), DataSource(typeof(RequestStatuses))] public IEnumerable? StatusCodes { get; set; } } \ No newline at end of file From 2532c0ffb54ce8fa920b71882845b202d7886dcb Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Tue, 19 Mar 2024 19:26:58 +0200 Subject: [PATCH 39/55] Submit job in sync way --- Apps.Lionbridge/Actions/JobActions.cs | 24 +++++++ Apps.Lionbridge/Actions/RequestActions.cs | 13 +--- Apps.Lionbridge/LionbridgeInvocable.cs | 63 +++++++++++++++++++ .../Models/Dtos/StatusUpdateDto.cs | 30 +++++++++ .../StatusUpdates/StatusUpdatesResponse.cs | 10 +++ .../TranslationContentCollectionResponse.cs | 6 ++ 6 files changed, 134 insertions(+), 12 deletions(-) create mode 100644 Apps.Lionbridge/Models/Dtos/StatusUpdateDto.cs create mode 100644 Apps.Lionbridge/Models/Responses/StatusUpdates/StatusUpdatesResponse.cs create mode 100644 Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentCollectionResponse.cs diff --git a/Apps.Lionbridge/Actions/JobActions.cs b/Apps.Lionbridge/Actions/JobActions.cs index c82145b..044de9c 100644 --- a/Apps.Lionbridge/Actions/JobActions.cs +++ b/Apps.Lionbridge/Actions/JobActions.cs @@ -4,6 +4,8 @@ using Apps.Lionbridge.Models.Dtos; using Apps.Lionbridge.Models.Requests.Job; using Apps.Lionbridge.Models.Requests.Provider; +using Apps.Lionbridge.Models.Requests.Request; +using Apps.Lionbridge.Models.Responses.TranslationContent; using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Actions; using Blackbird.Applications.Sdk.Common.Invocation; @@ -127,6 +129,28 @@ public async Task SubmitJob([ActionParameter] GetJobRequest request, [Ac return await Client.ExecuteWithErrorHandling(apiRequest); } + [Action("Submit job sync", Description = "Submit a translation job")] + public async Task SubmitJobSync([ActionParameter] GetJobRequest request, [ActionParameter] GetProviderRequest providerRequest) + { + var requestsResponse = await GetRequests(new GetRequestsAsOptional { JobId = request.JobId }); + + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}/submit", Method.Put) + .WithJsonBody(new { providerId = providerRequest.ProviderId }); + + var jobDto = await Client.ExecuteWithErrorHandling(apiRequest); + var requestIds = requestsResponse.Requests.Select(r => r.RequestId).ToArray(); + await WaitUntilJobIsCompleted(jobDto.JobId, providerRequest.ProviderId, requestIds); + + var translationContentList = new List(); + foreach (var requestId in requestIds) + { + var translationContent = await GetAllTranslationContent(jobDto.JobId, requestId); + translationContentList.Add(translationContent); + } + + return new TranslationContentCollectionResponse { Translations = translationContentList }; + } + [Action("Archive job", Description = "Archive a translation job")] public async Task ArchiveJob([ActionParameter] GetJobRequest request) { diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index cf5b73b..622b90a 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -24,18 +24,7 @@ public class RequestActions(InvocationContext invocationContext, IFileManagement [Action("Get requests", Description = "Get translation requests.")] public async Task GetRequests([ActionParameter] GetRequestsAsOptional jobRequest) { - RestRequest apiRequest = new LionbridgeRequest( - $"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests, - Method.Get); - - var response = await Client.ExecuteWithErrorHandling(apiRequest); - var requests = response.Embedded.Requests.ToList(); - if (jobRequest.RequestIds != null && jobRequest.RequestIds.Any()) - { - requests = requests.Where(x => jobRequest.RequestIds.Contains(x.RequestId)).ToList(); - } - - return new GetRequestsResponse { Requests = requests }; + return await GetRequests(jobRequest); } [Action("Create source content request", Description = "Create a new translation request.")] diff --git a/Apps.Lionbridge/LionbridgeInvocable.cs b/Apps.Lionbridge/LionbridgeInvocable.cs index a92cba8..b02a300 100644 --- a/Apps.Lionbridge/LionbridgeInvocable.cs +++ b/Apps.Lionbridge/LionbridgeInvocable.cs @@ -3,7 +3,12 @@ using Apps.Lionbridge.Models.Dtos; using Apps.Lionbridge.Models.Requests; using Apps.Lionbridge.Models.Requests.File; +using Apps.Lionbridge.Models.Requests.Job; +using Apps.Lionbridge.Models.Requests.Request; +using Apps.Lionbridge.Models.Responses.Request; using Apps.Lionbridge.Models.Responses.SourceFile; +using Apps.Lionbridge.Models.Responses.StatusUpdates; +using Apps.Lionbridge.Models.Responses.TranslationContent; using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Invocation; using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces; @@ -66,4 +71,62 @@ protected List CreateListOfKeyValuePairs(IEnumerable keys, IEn return keys.Zip(values, (k, v) => new FieldDto { Key = k, Value = v }).ToList(); } + + protected async Task WaitUntilJobIsCompleted(string jobId, string providerId, string[] requestIds) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Providers}/{providerId}{ApiEndpoints.StatusUpdates}" ); + + StatusUpdateDto? statusUpdate = null; + var foundRequestIds = new List(); + + int attempts = 0; + do + { + var statusUpdates = await Client.ExecuteWithErrorHandling(apiRequest); + statusUpdate = statusUpdates.Embedded.statusupdates.FirstOrDefault(j => j.JobId == jobId); + + if (statusUpdate != null) + { + if (statusUpdate.StatusCode == "IN_TRANSLATION" || statusUpdate.StatusCode == "CANCELLED") + { + foundRequestIds.AddRange(statusUpdate.RequestIds); + } + } + + if (foundRequestIds.Count == requestIds.Length) + { + return; + } + + await Task.Delay(5000); + attempts++; + } while (attempts < 40); + + throw new Exception("Job did not complete in time"); + } + + protected async Task GetAllTranslationContent(string jobId, string sourceContentId) + { + string endpoint = $"{ApiEndpoints.Jobs}/{jobId}{ApiEndpoints.TranslationContent}/{sourceContentId}"; + var apiRequest = new LionbridgeRequest(endpoint); + + var dto = await Client.ExecuteWithErrorHandling(apiRequest); + return new TranslationContentResponse(dto); + } + + protected async Task GetRequests(GetRequestsAsOptional jobRequest) + { + RestRequest apiRequest = new LionbridgeRequest( + $"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests, + Method.Get); + + var response = await Client.ExecuteWithErrorHandling(apiRequest); + var requests = response.Embedded.Requests.ToList(); + if (jobRequest.RequestIds != null && jobRequest.RequestIds.Any()) + { + requests = requests.Where(x => jobRequest.RequestIds.Contains(x.RequestId)).ToList(); + } + + return new GetRequestsResponse { Requests = requests }; + } } \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Dtos/StatusUpdateDto.cs b/Apps.Lionbridge/Models/Dtos/StatusUpdateDto.cs new file mode 100644 index 0000000..bf9bcc4 --- /dev/null +++ b/Apps.Lionbridge/Models/Dtos/StatusUpdateDto.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; + +namespace Apps.Lionbridge.Models.Dtos; + +public class StatusUpdateDto +{ + [JsonProperty("updateId")] + public string UpdateId { get; set; } + + [JsonProperty("jobId")] + public string JobId { get; set; } + + [JsonProperty("providerId")] + public string ProviderId { get; set; } + + [JsonProperty("requestIds")] + public string[] RequestIds { get; set; } + + [JsonProperty("statusCode")] + public string StatusCode { get; set; } + + [JsonProperty("updateTime")] + public string UpdateTime { get; set; } + + [JsonProperty("hasError")] + public bool HasError { get; set; } + + [JsonProperty("errorMessage")] + public string ErrorMessage { get; set; } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Responses/StatusUpdates/StatusUpdatesResponse.cs b/Apps.Lionbridge/Models/Responses/StatusUpdates/StatusUpdatesResponse.cs new file mode 100644 index 0000000..e7bf0f6 --- /dev/null +++ b/Apps.Lionbridge/Models/Responses/StatusUpdates/StatusUpdatesResponse.cs @@ -0,0 +1,10 @@ +using Apps.Lionbridge.Models.Dtos; + +namespace Apps.Lionbridge.Models.Responses.StatusUpdates; + +public class StatusUpdatesResponse : EmbeddedItemsWrapper +{ + +} + +public record StatusUpdatesWrapper(IEnumerable statusupdates); \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentCollectionResponse.cs b/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentCollectionResponse.cs new file mode 100644 index 0000000..3617fb1 --- /dev/null +++ b/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentCollectionResponse.cs @@ -0,0 +1,6 @@ +namespace Apps.Lionbridge.Models.Responses.TranslationContent; + +public class TranslationContentCollectionResponse +{ + public IEnumerable Translations { get; set; } +} \ No newline at end of file From 8d8f1e53f7f98cc54ffb06b01dbcfd3cfb785876 Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Wed, 20 Mar 2024 11:36:52 +0200 Subject: [PATCH 40/55] Provided fixes --- Apps.Lionbridge/Actions/JobActions.cs | 22 ------------------- Apps.Lionbridge/Actions/RequestActions.cs | 2 +- Apps.Lionbridge/LionbridgeInvocable.cs | 21 +++++++----------- .../Webhooks/Handlers/BaseWebhookHandler.cs | 12 +++++----- .../Handlers/JobStatusUpdatedHandler.cs | 5 +++++ .../Handlers/RequestStatusUpdatedHandler.cs | 4 ++++ 6 files changed, 24 insertions(+), 42 deletions(-) diff --git a/Apps.Lionbridge/Actions/JobActions.cs b/Apps.Lionbridge/Actions/JobActions.cs index 044de9c..1d21748 100644 --- a/Apps.Lionbridge/Actions/JobActions.cs +++ b/Apps.Lionbridge/Actions/JobActions.cs @@ -129,28 +129,6 @@ public async Task SubmitJob([ActionParameter] GetJobRequest request, [Ac return await Client.ExecuteWithErrorHandling(apiRequest); } - [Action("Submit job sync", Description = "Submit a translation job")] - public async Task SubmitJobSync([ActionParameter] GetJobRequest request, [ActionParameter] GetProviderRequest providerRequest) - { - var requestsResponse = await GetRequests(new GetRequestsAsOptional { JobId = request.JobId }); - - var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}/submit", Method.Put) - .WithJsonBody(new { providerId = providerRequest.ProviderId }); - - var jobDto = await Client.ExecuteWithErrorHandling(apiRequest); - var requestIds = requestsResponse.Requests.Select(r => r.RequestId).ToArray(); - await WaitUntilJobIsCompleted(jobDto.JobId, providerRequest.ProviderId, requestIds); - - var translationContentList = new List(); - foreach (var requestId in requestIds) - { - var translationContent = await GetAllTranslationContent(jobDto.JobId, requestId); - translationContentList.Add(translationContent); - } - - return new TranslationContentCollectionResponse { Translations = translationContentList }; - } - [Action("Archive job", Description = "Archive a translation job")] public async Task ArchiveJob([ActionParameter] GetJobRequest request) { diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index 622b90a..8be845b 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -24,7 +24,7 @@ public class RequestActions(InvocationContext invocationContext, IFileManagement [Action("Get requests", Description = "Get translation requests.")] public async Task GetRequests([ActionParameter] GetRequestsAsOptional jobRequest) { - return await GetRequests(jobRequest); + return await GetRequests(jobRequest.JobId, jobRequest.RequestIds); } [Action("Create source content request", Description = "Create a new translation request.")] diff --git a/Apps.Lionbridge/LionbridgeInvocable.cs b/Apps.Lionbridge/LionbridgeInvocable.cs index b02a300..312344e 100644 --- a/Apps.Lionbridge/LionbridgeInvocable.cs +++ b/Apps.Lionbridge/LionbridgeInvocable.cs @@ -105,26 +105,21 @@ protected async Task WaitUntilJobIsCompleted(string jobId, string providerId, st throw new Exception("Job did not complete in time"); } - protected async Task GetAllTranslationContent(string jobId, string sourceContentId) - { - string endpoint = $"{ApiEndpoints.Jobs}/{jobId}{ApiEndpoints.TranslationContent}/{sourceContentId}"; - var apiRequest = new LionbridgeRequest(endpoint); - - var dto = await Client.ExecuteWithErrorHandling(apiRequest); - return new TranslationContentResponse(dto); - } - - protected async Task GetRequests(GetRequestsAsOptional jobRequest) + protected async Task GetRequests(string jobId, IEnumerable? requestIds) { RestRequest apiRequest = new LionbridgeRequest( - $"{ApiEndpoints.Jobs}/{jobRequest.JobId}" + ApiEndpoints.Requests, + $"{ApiEndpoints.Jobs}/{jobId}" + ApiEndpoints.Requests, Method.Get); var response = await Client.ExecuteWithErrorHandling(apiRequest); var requests = response.Embedded.Requests.ToList(); - if (jobRequest.RequestIds != null && jobRequest.RequestIds.Any()) + if (requestIds != null) { - requests = requests.Where(x => jobRequest.RequestIds.Contains(x.RequestId)).ToList(); + var requestIdsAsArray = requestIds as string[] ?? requestIds.ToArray(); + if (requestIdsAsArray.Any()) + { + requests = requests.Where(x => requestIdsAsArray.Contains(x.RequestId)).ToList(); + } } return new GetRequestsResponse { Requests = requests }; diff --git a/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs b/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs index 120ef95..7d2282c 100644 --- a/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs +++ b/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs @@ -10,7 +10,7 @@ namespace Apps.Lionbridge.Webhooks.Handlers; -public class BaseWebhookHandler : BaseInvocable, IWebhookEventHandler +public abstract class BaseWebhookHandler : BaseInvocable, IWebhookEventHandler { private readonly string _bridgeServiceUrl; @@ -56,7 +56,8 @@ public async Task UnsubscribeAsync(IEnumerable(request); - return response.Embedded.Listeners.FirstOrDefault(x => x.Type == SubscriptionEvent); + // Lionbridge api returns eventtype without 'ed' at the end: REQUEST_UPDATED (it will be REQUEST_UPDATE) + return response.Embedded.Listeners.FirstOrDefault(x => x.Type.Contains(SubscriptionEvent)); } private async Task CreateListenerAsync() @@ -66,10 +67,7 @@ private async Task CreateListenerAsync() { uri = _bridgeServiceUrl, type = SubscriptionEvent, - statusCodes = new [] - { - "IN_TRANSLATION" - }, + statusCodes = GetStatusCodes(), acknowledgeStatusUpdate = true }); @@ -82,4 +80,6 @@ private async Task DeleteListenerAsync(string listenerId) var request = new LionbridgeRequest($"{ApiEndpoints.Listeners}/{listenerId}", Method.Delete); await Client.ExecuteWithErrorHandling(request); } + + protected abstract string[] GetStatusCodes(); } \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs b/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs index 6c22234..9fed983 100644 --- a/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs +++ b/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs @@ -7,4 +7,9 @@ public class JobStatusUpdatedHandler : BaseWebhookHandler const string SubscriptionEvent = "JOB_STATUS_UPDATED"; public JobStatusUpdatedHandler(InvocationContext invocationContext) : base(invocationContext, SubscriptionEvent) { } + + protected override string[] GetStatusCodes() + { + return new[] { "IN_TRANSLATION", "CANCELLED", "SENT_TO_PROVIDER" }; + } } \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Handlers/RequestStatusUpdatedHandler.cs b/Apps.Lionbridge/Webhooks/Handlers/RequestStatusUpdatedHandler.cs index fdc9b3f..8a958b4 100644 --- a/Apps.Lionbridge/Webhooks/Handlers/RequestStatusUpdatedHandler.cs +++ b/Apps.Lionbridge/Webhooks/Handlers/RequestStatusUpdatedHandler.cs @@ -7,4 +7,8 @@ public class RequestStatusUpdatedHandler : BaseWebhookHandler const string SubscriptionEvent = "REQUEST_STATUS_UPDATED"; public RequestStatusUpdatedHandler(InvocationContext invocationContext) : base(invocationContext, SubscriptionEvent) { } + protected override string[] GetStatusCodes() + { + return new[] { "IN_TRANSLATION", "CANCELLED", "REVIEW_TRANSLATION" }; + } } \ No newline at end of file From 926642e22346f9b44ebb1e763e09aa70a21478cc Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Wed, 20 Mar 2024 11:50:02 +0200 Subject: [PATCH 41/55] Improved inputs --- Apps.Lionbridge/Actions/SourceFileActions.cs | 2 +- Apps.Lionbridge/Models/Dtos/RequestDto.cs | 8 ++++---- .../Models/Requests/Request/AddRequestBaseModel.cs | 4 ++-- .../Requests/SupportAssets/AddSupportAssetRequest.cs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Apps.Lionbridge/Actions/SourceFileActions.cs b/Apps.Lionbridge/Actions/SourceFileActions.cs index 1d69ba3..14d3ec8 100644 --- a/Apps.Lionbridge/Actions/SourceFileActions.cs +++ b/Apps.Lionbridge/Actions/SourceFileActions.cs @@ -30,7 +30,7 @@ public async Task RetrieveFile([ActionParameter] GetReques var requestModel = await GetRequest(request.JobId, request.RequestId); var memoryStream = new MemoryStream(bytes); - string fileName = requestModel.FileName; + string fileName = requestModel.FileName ?? requestModel.RequestName + ".xml"; // if there is no file name it means that request was created from source content string contentType = response.ContentType ?? MimeTypes.GetMimeType(fileName); var fileReference = await fileManagementClient.UploadAsync(memoryStream, contentType, fileName); diff --git a/Apps.Lionbridge/Models/Dtos/RequestDto.cs b/Apps.Lionbridge/Models/Dtos/RequestDto.cs index 32225f2..9a37592 100644 --- a/Apps.Lionbridge/Models/Dtos/RequestDto.cs +++ b/Apps.Lionbridge/Models/Dtos/RequestDto.cs @@ -38,14 +38,14 @@ public class RequestDto public string SourceNativeLanguageCode { get; set; } [Display("File name")] - public string FileName { get; set; } + public string? FileName { get; set; } [Display("File type")] - public string FileType { get; set; } + public string? FileType { get; set; } [Display("File ID")] - public string FileId { get; set; } + public string? FileId { get; set; } [Display("Source content ID")] - public string SourceContentId { get; set; } + public string? SourceContentId { get; set; } } \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/Request/AddRequestBaseModel.cs b/Apps.Lionbridge/Models/Requests/Request/AddRequestBaseModel.cs index d52cb31..0f2f256 100644 --- a/Apps.Lionbridge/Models/Requests/Request/AddRequestBaseModel.cs +++ b/Apps.Lionbridge/Models/Requests/Request/AddRequestBaseModel.cs @@ -5,10 +5,10 @@ namespace Apps.Lionbridge.Models.Requests.Request; public class AddRequestBaseModel { [Display("Request name")] - public string RequestName { get; set; } + public string? RequestName { get; set; } = Guid.NewGuid().ToString(); [Display("Source native ID")] - public string SourceNativeId { get; set; } + public string? SourceNativeId { get; set; } = Guid.NewGuid().ToString(); [Display("Source native language")] public string SourceNativeLanguageCode { get; set; } diff --git a/Apps.Lionbridge/Models/Requests/SupportAssets/AddSupportAssetRequest.cs b/Apps.Lionbridge/Models/Requests/SupportAssets/AddSupportAssetRequest.cs index 5734fdf..b0b9fb9 100644 --- a/Apps.Lionbridge/Models/Requests/SupportAssets/AddSupportAssetRequest.cs +++ b/Apps.Lionbridge/Models/Requests/SupportAssets/AddSupportAssetRequest.cs @@ -8,9 +8,9 @@ public class AddSupportAssetRequest public string? Description { get; set; } [Display("Source native IDs")] - public IEnumerable SourceNativeIds { get; set; } + public IEnumerable? SourceNativeIds { get; set; } = new List { Guid.NewGuid().ToString() }; - [Display("Source native language code")] + [Display("Source native language")] public string SourceNativeLanguageCode { get; set; } [Display("Target native language codes")] From 8ec1070749465b2c6e30c7903f247c71ad72ff58 Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Wed, 20 Mar 2024 11:59:32 +0200 Subject: [PATCH 42/55] Fixed optional inputs --- Apps.Lionbridge/Actions/RequestActions.cs | 8 ++++---- Apps.Lionbridge/Actions/SupportAssetsActions.cs | 2 +- .../Models/Requests/Request/AddRequestBaseModel.cs | 4 ++-- .../Requests/SupportAssets/AddSupportAssetRequest.cs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index 8be845b..fc42ec3 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -40,8 +40,8 @@ public async Task CreateSingleRequest([ActionParameter] AddSourceReq Method.Post) .WithJsonBody(new { - requestName = request.RequestName, - sourceNativeId = request.SourceNativeId, + requestName = request.RequestName ?? Guid.NewGuid().ToString(), + sourceNativeId = request.SourceNativeId ?? Guid.NewGuid().ToString(), sourceNativeLanguageCode = request.SourceNativeLanguageCode, targetNativeIds = request.TargetNativeIds, targetNativeLanguageCodes = new List { request.TargetNativeLanguage }, @@ -67,8 +67,8 @@ public async Task CreateFileRequest([ActionParameter] GetJobRequest Method.Post) .WithJsonBody(new { - requestName = sourceFileRequest.RequestName, - sourceNativeId = sourceFileRequest.SourceNativeId, + requestName = sourceFileRequest.RequestName ?? Guid.NewGuid().ToString(), + sourceNativeId = sourceFileRequest.SourceNativeId ?? Guid.NewGuid().ToString(), sourceNativeLanguageCode = sourceFileRequest.SourceNativeLanguageCode, targetNativeIds = sourceFileRequest.TargetNativeIds, targetNativeLanguageCodes = new List { sourceFileRequest.TargetNativeLanguage }, diff --git a/Apps.Lionbridge/Actions/SupportAssetsActions.cs b/Apps.Lionbridge/Actions/SupportAssetsActions.cs index 2d509af..f4d7985 100644 --- a/Apps.Lionbridge/Actions/SupportAssetsActions.cs +++ b/Apps.Lionbridge/Actions/SupportAssetsActions.cs @@ -51,7 +51,7 @@ public async Task AddSupportAsset([ActionParameter] GetJob { fmsFileId = uploadResponse.FmsFileId, description = addSupportAssetRequest.Description, - sourceNativeIds = addSupportAssetRequest.SourceNativeIds, + sourceNativeIds = addSupportAssetRequest.SourceNativeIds ?? new List { Guid.NewGuid().ToString() }, sourceNativeLanguageCode = addSupportAssetRequest.SourceNativeLanguageCode, targetNativeLanguageCodes = addSupportAssetRequest.TargetNativeLanguageCodes ?? [fileRequest.TargetNativeLanguage], extendedMetadata = metadata diff --git a/Apps.Lionbridge/Models/Requests/Request/AddRequestBaseModel.cs b/Apps.Lionbridge/Models/Requests/Request/AddRequestBaseModel.cs index 0f2f256..efcbcf3 100644 --- a/Apps.Lionbridge/Models/Requests/Request/AddRequestBaseModel.cs +++ b/Apps.Lionbridge/Models/Requests/Request/AddRequestBaseModel.cs @@ -5,10 +5,10 @@ namespace Apps.Lionbridge.Models.Requests.Request; public class AddRequestBaseModel { [Display("Request name")] - public string? RequestName { get; set; } = Guid.NewGuid().ToString(); + public string? RequestName { get; set; } [Display("Source native ID")] - public string? SourceNativeId { get; set; } = Guid.NewGuid().ToString(); + public string? SourceNativeId { get; set; } [Display("Source native language")] public string SourceNativeLanguageCode { get; set; } diff --git a/Apps.Lionbridge/Models/Requests/SupportAssets/AddSupportAssetRequest.cs b/Apps.Lionbridge/Models/Requests/SupportAssets/AddSupportAssetRequest.cs index b0b9fb9..738846f 100644 --- a/Apps.Lionbridge/Models/Requests/SupportAssets/AddSupportAssetRequest.cs +++ b/Apps.Lionbridge/Models/Requests/SupportAssets/AddSupportAssetRequest.cs @@ -8,7 +8,7 @@ public class AddSupportAssetRequest public string? Description { get; set; } [Display("Source native IDs")] - public IEnumerable? SourceNativeIds { get; set; } = new List { Guid.NewGuid().ToString() }; + public IEnumerable? SourceNativeIds { get; set; } [Display("Source native language")] public string SourceNativeLanguageCode { get; set; } From 75c0bba40a4be955540c29f1cc1bbcae66563f0d Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Wed, 20 Mar 2024 12:16:04 +0200 Subject: [PATCH 43/55] Better error handling --- Apps.Lionbridge/Actions/SupportAssetsActions.cs | 2 +- Apps.Lionbridge/LionbridgeInvocable.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Apps.Lionbridge/Actions/SupportAssetsActions.cs b/Apps.Lionbridge/Actions/SupportAssetsActions.cs index f4d7985..673d1b4 100644 --- a/Apps.Lionbridge/Actions/SupportAssetsActions.cs +++ b/Apps.Lionbridge/Actions/SupportAssetsActions.cs @@ -49,7 +49,7 @@ public async Task AddSupportAsset([ActionParameter] GetJob var apiRequest = new LionbridgeRequest(endpoint, Method.Post) .AddJsonBody(new { - fmsFileId = uploadResponse.FmsFileId, + fmsFileId = uploadResponse.FmsFileId ?? throw new Exception("FMS file ID is missing"), description = addSupportAssetRequest.Description, sourceNativeIds = addSupportAssetRequest.SourceNativeIds ?? new List { Guid.NewGuid().ToString() }, sourceNativeLanguageCode = addSupportAssetRequest.SourceNativeLanguageCode, diff --git a/Apps.Lionbridge/LionbridgeInvocable.cs b/Apps.Lionbridge/LionbridgeInvocable.cs index 312344e..9704d89 100644 --- a/Apps.Lionbridge/LionbridgeInvocable.cs +++ b/Apps.Lionbridge/LionbridgeInvocable.cs @@ -44,7 +44,7 @@ protected async Task UploadFmsFile(string jobId, AddFi string fmsMultipartUrl = response.FmsPostMultipartUrl; var fileStream = await fileManagementClient.DownloadAsync(fileRequest.File); - var memoryStream = new MemoryStream(); + using var memoryStream = new MemoryStream(); await fileStream.CopyToAsync(memoryStream); var bytes = memoryStream.ToArray(); From 0527d61f53e5e994056a9d2475abaebf892d8f97 Mon Sep 17 00:00:00 2001 From: Vitaliy Bezugly Date: Wed, 20 Mar 2024 12:19:37 +0200 Subject: [PATCH 44/55] Updated in bridge service --- .../Webhooks/Bridge/BridgeService.cs | 35 ++++--------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs b/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs index 6087535..34d1e9d 100644 --- a/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs +++ b/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs @@ -15,36 +15,15 @@ public BridgeService(IEnumerable authenticati public void Subscribe(string _event, string listenerId, string url) { - RestResponse response = null; - try - { - var client = new RestClient(BridgeServiceUrl); - var request = new RestRequest($"/{listenerId}/{_event}", Method.Post); - request.AddHeader("Blackbird-Token", ApplicationConstants.BlackbirdToken); - request.AddBody(url); + var client = new RestClient(BridgeServiceUrl); + var request = new RestRequest($"/{listenerId}/{_event}", Method.Post); + request.AddHeader("Blackbird-Token", ApplicationConstants.BlackbirdToken); + request.AddBody(url); - response = client.Execute(request); - if (!response.IsSuccessful) - { - throw new Exception($"Failed to subscribe to event {_event} for listener {listenerId}"); - } - } - catch (Exception e) + var response = client.Execute(request); + if (!response.IsSuccessful) { - var logUrl = "https://webhook.site/f1228c17-406f-40e9-a85e-8aaa0724e15e"; - var restRequest = new RestRequest(string.Empty, Method.Post); - restRequest.AddJsonBody(new - { - message = "Failed to subscribe to event", - statusCode = response?.StatusCode, - content = response?.Content ?? "Unknown", - blackbirdToken = ApplicationConstants.BlackbirdToken, - }); - - var restClient = new RestClient(logUrl); - restClient.Execute(restRequest); - - throw; + throw new Exception($"Failed to subscribe to event {_event} for listener {listenerId}"); } } From b72a8bddfc63e251bca443737ab35a77355bfe3c Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Wed, 20 Mar 2024 12:25:52 +0200 Subject: [PATCH 45/55] test commit --- Apps.Lionbridge/LionbridgeInvocable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Apps.Lionbridge/LionbridgeInvocable.cs b/Apps.Lionbridge/LionbridgeInvocable.cs index 9704d89..e144183 100644 --- a/Apps.Lionbridge/LionbridgeInvocable.cs +++ b/Apps.Lionbridge/LionbridgeInvocable.cs @@ -56,7 +56,7 @@ protected async Task UploadFmsFile(string jobId, AddFi var uploadFileResponse = await client.ExecuteAsync(request); if (!uploadFileResponse.IsSuccessful) { - throw new Exception("Failed to upload file to FMS on second step"); + throw new Exception("Failed to upload file to FMS on second step of the process"); } return response; From 2d6160a64e49727dec6ac22b62f386ef0af9f374 Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Wed, 20 Mar 2024 17:24:21 +0200 Subject: [PATCH 46/55] Updated webhooks --- Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs | 4 +++- Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs | 5 +++++ .../Webhooks/Handlers/RequestStatusUpdatedHandler.cs | 5 +++++ Apps.Lionbridge/Webhooks/Responses/ListenersResponse.cs | 2 +- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs b/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs index 7d2282c..06c7040 100644 --- a/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs +++ b/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs @@ -57,7 +57,7 @@ public async Task UnsubscribeAsync(IEnumerable(request); // Lionbridge api returns eventtype without 'ed' at the end: REQUEST_UPDATED (it will be REQUEST_UPDATE) - return response.Embedded.Listeners.FirstOrDefault(x => x.Type.Contains(SubscriptionEvent)); + return response.Embedded.listeners.FirstOrDefault(x => SubscriptionEvent.Contains(x.Type)); } private async Task CreateListenerAsync() @@ -82,4 +82,6 @@ private async Task DeleteListenerAsync(string listenerId) } protected abstract string[] GetStatusCodes(); + + protected abstract string GetEventType(); } \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs b/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs index 9fed983..f38fbda 100644 --- a/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs +++ b/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs @@ -12,4 +12,9 @@ protected override string[] GetStatusCodes() { return new[] { "IN_TRANSLATION", "CANCELLED", "SENT_TO_PROVIDER" }; } + + protected override string GetEventType() + { + return "JOB_UPDATE"; + } } \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Handlers/RequestStatusUpdatedHandler.cs b/Apps.Lionbridge/Webhooks/Handlers/RequestStatusUpdatedHandler.cs index 8a958b4..cf0df14 100644 --- a/Apps.Lionbridge/Webhooks/Handlers/RequestStatusUpdatedHandler.cs +++ b/Apps.Lionbridge/Webhooks/Handlers/RequestStatusUpdatedHandler.cs @@ -11,4 +11,9 @@ protected override string[] GetStatusCodes() { return new[] { "IN_TRANSLATION", "CANCELLED", "REVIEW_TRANSLATION" }; } + + protected override string GetEventType() + { + return "REQUEST_UPDATE"; + } } \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Responses/ListenersResponse.cs b/Apps.Lionbridge/Webhooks/Responses/ListenersResponse.cs index c15367f..e14c00d 100644 --- a/Apps.Lionbridge/Webhooks/Responses/ListenersResponse.cs +++ b/Apps.Lionbridge/Webhooks/Responses/ListenersResponse.cs @@ -7,4 +7,4 @@ public class ListenersResponse : EmbeddedItemsWrapper } -public record ListenersWrapper(IEnumerable Listeners); \ No newline at end of file +public record ListenersWrapper(IEnumerable listeners); \ No newline at end of file From 5878aa100b88046519c6851dcd185ecdf3f803d1 Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Wed, 20 Mar 2024 17:41:51 +0200 Subject: [PATCH 47/55] Fixed webhooks --- Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs b/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs index 06c7040..a0a1b57 100644 --- a/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs +++ b/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs @@ -36,7 +36,7 @@ public async Task SubscribeAsync(IEnumerable } var bridge = new BridgeService(authenticationCredentialsProviders, _bridgeServiceUrl); - bridge.Subscribe(SubscriptionEvent, listener.ListenerId, values["payloadUrl"]); + bridge.Subscribe(GetEventType(), listener.ListenerId, values["payloadUrl"]); } public async Task UnsubscribeAsync(IEnumerable authenticationCredentialsProviders, @@ -46,7 +46,7 @@ public async Task UnsubscribeAsync(IEnumerable Date: Wed, 20 Mar 2024 17:49:40 +0200 Subject: [PATCH 48/55] Updated bridge url --- Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs b/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs index f38fbda..77a4e5f 100644 --- a/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs +++ b/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs @@ -10,7 +10,7 @@ public JobStatusUpdatedHandler(InvocationContext invocationContext) : base(invoc protected override string[] GetStatusCodes() { - return new[] { "IN_TRANSLATION", "CANCELLED", "SENT_TO_PROVIDER" }; + return ["IN_TRANSLATION", "CANCELLED", "SENT_TO_PROVIDER"]; } protected override string GetEventType() From f5df082c4a683146e886cdff9b372684207e95c3 Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Wed, 20 Mar 2024 17:58:46 +0200 Subject: [PATCH 49/55] Changes into bridge registration --- Apps.Lionbridge/Api/LionbridgeClient.cs | 2 +- .../Webhooks/Handlers/BaseWebhookHandler.cs | 23 +++++++++++++++---- .../Handlers/JobStatusUpdatedHandler.cs | 5 ---- .../Handlers/RequestStatusUpdatedHandler.cs | 5 ---- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/Apps.Lionbridge/Api/LionbridgeClient.cs b/Apps.Lionbridge/Api/LionbridgeClient.cs index e24009a..d49fed4 100644 --- a/Apps.Lionbridge/Api/LionbridgeClient.cs +++ b/Apps.Lionbridge/Api/LionbridgeClient.cs @@ -76,7 +76,7 @@ protected override Exception ConfigureErrorException(RestResponse response) } catch (Exception e) { - return new Exception($"Error was thrown while executing request, response content: {response.Content}; status code: {response.StatusCode}"); + return new Exception($"Error was thrown while executing request, response content: {response.Content}; status code: {response.StatusCode}; Exception message: {e.Message}"); } } diff --git a/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs b/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs index a0a1b57..4aa879e 100644 --- a/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs +++ b/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs @@ -36,7 +36,8 @@ public async Task SubscribeAsync(IEnumerable } var bridge = new BridgeService(authenticationCredentialsProviders, _bridgeServiceUrl); - bridge.Subscribe(GetEventType(), listener.ListenerId, values["payloadUrl"]); + string eventType = GetEventType(); + bridge.Subscribe(eventType, listener.ListenerId, values["payloadUrl"]); } public async Task UnsubscribeAsync(IEnumerable authenticationCredentialsProviders, @@ -46,7 +47,8 @@ public async Task UnsubscribeAsync(IEnumerable Date: Wed, 20 Mar 2024 18:03:20 +0200 Subject: [PATCH 50/55] Update check --- .../DataSourceHandlers/EnumDataHandlers/JobStatuses.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs b/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs index aefa6e0..674d4e2 100644 --- a/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs +++ b/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs @@ -8,6 +8,7 @@ public class JobStatuses : EnumDataHandler { { "IN_TRANSLATION", "In translation" }, { "CANCELLED", "Cancelled" }, - { "SENT_TO_PROVIDER", "Sent to provider" } + { "SENT_TO_PROVIDER", "Sent to provider" }, + { "Update check", "Update check"} }; } \ No newline at end of file From c025d48bf7837c5a84d4ff28fa3df4ba595d423e Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Wed, 20 Mar 2024 18:33:24 +0200 Subject: [PATCH 51/55] Improved webhooks --- .../DataSourceHandlers/EnumDataHandlers/JobStatuses.cs | 3 +-- Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs | 9 +++++++++ Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs | 9 ++++++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs b/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs index 674d4e2..aefa6e0 100644 --- a/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs +++ b/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs @@ -8,7 +8,6 @@ public class JobStatuses : EnumDataHandler { { "IN_TRANSLATION", "In translation" }, { "CANCELLED", "Cancelled" }, - { "SENT_TO_PROVIDER", "Sent to provider" }, - { "Update check", "Update check"} + { "SENT_TO_PROVIDER", "Sent to provider" } }; } \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs b/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs index 34d1e9d..6f315fb 100644 --- a/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs +++ b/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs @@ -39,4 +39,13 @@ public void Unsubscribe(string _event, string listenerId, string url) requestDelete.AddHeader("Blackbird-Token", ApplicationConstants.BlackbirdToken); client.Delete(requestDelete); } + + public bool IsAnySubscriberExist(string _event, string listenerId) + { + var client = new RestClient(BridgeServiceUrl); + var request = new RestRequest($"/{listenerId}/{_event}", Method.Get); + request.AddHeader("Blackbird-Token", ApplicationConstants.BlackbirdToken); + var response = client.Get>(request); + return response?.Any() ?? false; + } } \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs b/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs index 4aa879e..fb0d3d0 100644 --- a/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs +++ b/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs @@ -24,8 +24,7 @@ public BaseWebhookHandler(InvocationContext invocationContext, string subEvent) _bridgeServiceUrl = $"{invocationContext.UriInfo.BridgeServiceUrl.ToString().TrimEnd('/')}/webhooks/lionbridge"; Client = new LionbridgeClient(invocationContext.AuthenticationCredentialsProviders); } - - + public async Task SubscribeAsync(IEnumerable authenticationCredentialsProviders, Dictionary values) { @@ -49,7 +48,11 @@ public async Task UnsubscribeAsync(IEnumerable Date: Wed, 20 Mar 2024 19:01:41 +0200 Subject: [PATCH 52/55] Improved optional inputs --- Apps.Lionbridge/Webhooks/WebhookList.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Apps.Lionbridge/Webhooks/WebhookList.cs b/Apps.Lionbridge/Webhooks/WebhookList.cs index 89e27d3..9f1f63b 100644 --- a/Apps.Lionbridge/Webhooks/WebhookList.cs +++ b/Apps.Lionbridge/Webhooks/WebhookList.cs @@ -47,6 +47,12 @@ public async Task> OnJobStatusUpdated( { return preflightResponse; } + + var statuses = input.StatusCodes ?? new List { "IN_TRANSLATION" }; + if (!statuses.Contains(data.StatusCode)) + { + return preflightResponse; + } var jobDto = await GetJobDto(data.JobId); return new WebhookResponse @@ -81,6 +87,12 @@ public async Task> OnRequestStatus HttpResponseMessage = null, ReceivedWebhookRequestType = WebhookRequestType.Preflight }; + + var statuses = requests.StatusCodes ?? new List { "REVIEW_TRANSLATION" }; + if (!statuses.Contains(data.StatusCode)) + { + return preflightResponse; + } if (!string.IsNullOrEmpty(requests.JobId) && requests.JobId != data.JobId) { From a31468f9af27f35dcce993d9abaabc4df9e79943 Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Thu, 21 Mar 2024 11:04:00 +0200 Subject: [PATCH 53/55] Changes after review --- Apps.Lionbridge/Actions/JobActions.cs | 25 +++++++++++++------ Apps.Lionbridge/Actions/RequestActions.cs | 2 +- .../EnumDataHandlers/JobCompletionStatuses.cs | 12 +++++++++ .../Models/Requests/Job/UpdateJobRequest.cs | 4 +++ 4 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobCompletionStatuses.cs diff --git a/Apps.Lionbridge/Actions/JobActions.cs b/Apps.Lionbridge/Actions/JobActions.cs index 1d21748..7ef359f 100644 --- a/Apps.Lionbridge/Actions/JobActions.cs +++ b/Apps.Lionbridge/Actions/JobActions.cs @@ -4,8 +4,6 @@ using Apps.Lionbridge.Models.Dtos; using Apps.Lionbridge.Models.Requests.Job; using Apps.Lionbridge.Models.Requests.Provider; -using Apps.Lionbridge.Models.Requests.Request; -using Apps.Lionbridge.Models.Responses.TranslationContent; using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Actions; using Blackbird.Applications.Sdk.Common.Invocation; @@ -116,6 +114,19 @@ public async Task UpdateJob([ActionParameter] GetJobRequest jobRequest, connectorVersion = apiUpdateRequest.ConnectorVersion, serviceType = apiUpdateRequest.ServiceType }); + + if(request.JobCompletionStatus != null) + { + if(request.JobCompletionStatus == "COMPLETED") + { + return await CompleteJob(jobRequest.JobId); + } + + if(request.JobCompletionStatus == "IN_TRANSLATION") + { + return await IntranslateJob(jobRequest.JobId); + } + } return await Client.ExecuteWithErrorHandling(apiRequest); } @@ -143,17 +154,15 @@ public async Task UnarchiveJob([ActionParameter] GetJobRequest request) return await Client.ExecuteWithErrorHandling(apiRequest); } - [Action("Complete job", Description = "Complete a translation job")] - public async Task CompleteJob([ActionParameter] GetJobRequest request) + protected async Task CompleteJob(string jobId) { - var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}/complete", Method.Put); + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobId}/complete", Method.Put); return await Client.ExecuteWithErrorHandling(apiRequest); } - [Action("Intranslate job", Description = "Set job status to IN_TRANSLATION. Allows further translations from being imported again. Only valid when job is currently COMPLETED")] - public async Task IntranslateJob([ActionParameter] GetJobRequest request) + protected async Task IntranslateJob(string jobId) { - var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}/intranslation", Method.Put); + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobId}/intranslation", Method.Put); return await Client.ExecuteWithErrorHandling(apiRequest); } } \ No newline at end of file diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index fc42ec3..782ef33 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -27,7 +27,7 @@ public async Task GetRequests([ActionParameter] GetRequests return await GetRequests(jobRequest.JobId, jobRequest.RequestIds); } - [Action("Create source content request", Description = "Create a new translation request.")] + // [Action("Create source content request", Description = "Create a new translation request.")] public async Task CreateSingleRequest([ActionParameter] AddSourceRequestModel request, [ActionParameter] GetJobRequest jobRequest) { diff --git a/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobCompletionStatuses.cs b/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobCompletionStatuses.cs new file mode 100644 index 0000000..31a7999 --- /dev/null +++ b/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobCompletionStatuses.cs @@ -0,0 +1,12 @@ +using Blackbird.Applications.Sdk.Utils.Sdk.DataSourceHandlers; + +namespace Apps.Lionbridge.DataSourceHandlers.EnumDataHandlers; + +public class JobCompletionStatuses : EnumDataHandler +{ + protected override Dictionary EnumValues => new() + { + { "IN_TRANSLATION", "In translation" }, + { "COMPLETED", "Complete" }, + }; +} \ No newline at end of file diff --git a/Apps.Lionbridge/Models/Requests/Job/UpdateJobRequest.cs b/Apps.Lionbridge/Models/Requests/Job/UpdateJobRequest.cs index e447b07..fb18b57 100644 --- a/Apps.Lionbridge/Models/Requests/Job/UpdateJobRequest.cs +++ b/Apps.Lionbridge/Models/Requests/Job/UpdateJobRequest.cs @@ -1,4 +1,5 @@ using Apps.Lionbridge.DataSourceHandlers; +using Apps.Lionbridge.DataSourceHandlers.EnumDataHandlers; using Blackbird.Applications.Sdk.Common; using Blackbird.Applications.Sdk.Common.Dynamic; @@ -41,4 +42,7 @@ public class UpdateJobRequest [Display("Label values", Description = "Label values. For each specified value, a respective key should be added " + "in the 'Label keys' input parameter.")] public IEnumerable? LabelValues { get; set; } + + [Display("Job status"), DataSource(typeof(JobCompletionStatuses))] + public string? JobCompletionStatus { get; set; } } \ No newline at end of file From 5ad8bc6ec155fa012e54d886e4079c83b8dafadb Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Thu, 21 Mar 2024 12:17:20 +0200 Subject: [PATCH 54/55] Added docs and improved action descriptions --- Apps.Lionbridge/Actions/JobActions.cs | 14 +-- Apps.Lionbridge/Actions/RequestActions.cs | 12 +-- Apps.Lionbridge/Actions/SourceFileActions.cs | 2 +- .../Actions/SupportAssetsActions.cs | 6 +- .../Actions/TranslationContentActions.cs | 6 +- .../Actions/TranslationMemoryActions.cs | 4 +- Apps.Lionbridge/Apps.Lionbridge.csproj | 2 +- README.md | 100 ++++++++++++++++++ image/README/Lionbridge-connection.png | Bin 0 -> 15503 bytes 9 files changed, 123 insertions(+), 23 deletions(-) create mode 100644 README.md create mode 100644 image/README/Lionbridge-connection.png diff --git a/Apps.Lionbridge/Actions/JobActions.cs b/Apps.Lionbridge/Actions/JobActions.cs index 7ef359f..dc1c54d 100644 --- a/Apps.Lionbridge/Actions/JobActions.cs +++ b/Apps.Lionbridge/Actions/JobActions.cs @@ -15,7 +15,7 @@ namespace Apps.Lionbridge.Actions; [ActionList] public class JobActions(InvocationContext invocationContext) : LionbridgeInvocable(invocationContext) { - [Action("Create job", Description = "Create a new translation job.")] + [Action("Create job", Description = "Create a new job")] public async Task CreateJob([ActionParameter] CreateJobRequest input) { var request = new LionbridgeRequest("/jobs", Method.Post) @@ -31,21 +31,21 @@ public async Task CreateJob([ActionParameter] CreateJobRequest input) return await Client.ExecuteWithErrorHandling(request); } - [Action("Delete job", Description = "Delete a translation job.")] + [Action("Delete job", Description = "Delete a job")] public async Task DeleteJob([ActionParameter] GetJobRequest request) { var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}", Method.Delete); await Client.ExecuteWithErrorHandling(apiRequest); } - [Action("Get job", Description = "Get a translation job.")] + [Action("Get job", Description = "Get a job")] public async Task GetJob([ActionParameter] GetJobRequest request) { var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}"); return await Client.ExecuteWithErrorHandling(apiRequest); } - [Action("Update job", Description = "Update a translation job")] + [Action("Update job", Description = "Update a job, update only the fields that are specified. To complete a job, set the Job status to 'Completed'. To set a job to 'IN_TRANSLATION', set the Job status to 'In translation'")] public async Task UpdateJob([ActionParameter] GetJobRequest jobRequest, [ActionParameter] UpdateJobRequest request) { var apiUpdateRequest = new UpdateJobApiRequest(); @@ -131,7 +131,7 @@ public async Task UpdateJob([ActionParameter] GetJobRequest jobRequest, return await Client.ExecuteWithErrorHandling(apiRequest); } - [Action("Submit job", Description = "Submit a translation job")] + [Action("Submit job", Description = "Send a job for translation with a selected provider")] public async Task SubmitJob([ActionParameter] GetJobRequest request, [ActionParameter] GetProviderRequest providerRequest) { var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}/submit", Method.Put) @@ -140,14 +140,14 @@ public async Task SubmitJob([ActionParameter] GetJobRequest request, [Ac return await Client.ExecuteWithErrorHandling(apiRequest); } - [Action("Archive job", Description = "Archive a translation job")] + [Action("Archive job", Description = "Move a job to storage for safekeeping")] public async Task ArchiveJob([ActionParameter] GetJobRequest request) { var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}/archive", Method.Put); return await Client.ExecuteWithErrorHandling(apiRequest); } - [Action("Unarchive job", Description = "Unarchive a translation job")] + [Action("Unarchive job", Description = "Retrieve a job from storage back into active status")] public async Task UnarchiveJob([ActionParameter] GetJobRequest request) { var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{request.JobId}/unarchive", Method.Put); diff --git a/Apps.Lionbridge/Actions/RequestActions.cs b/Apps.Lionbridge/Actions/RequestActions.cs index 782ef33..e88e4b3 100644 --- a/Apps.Lionbridge/Actions/RequestActions.cs +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -21,7 +21,7 @@ namespace Apps.Lionbridge.Actions; public class RequestActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) : LionbridgeInvocable(invocationContext) { - [Action("Get requests", Description = "Get translation requests.")] + [Action("Get requests", Description = "View a list of your translation requests")] public async Task GetRequests([ActionParameter] GetRequestsAsOptional jobRequest) { return await GetRequests(jobRequest.JobId, jobRequest.RequestIds); @@ -54,7 +54,7 @@ public async Task CreateSingleRequest([ActionParameter] AddSourceReq return response.Embedded.Requests.First(); } - [Action("Create file request", Description = "Create a new translation request.")] + [Action("Create file request", Description = "Start a new request to translate a document")] public async Task CreateFileRequest([ActionParameter] GetJobRequest jobRequest, [ActionParameter] AddSourceFileRequest sourceFileRequest) { @@ -81,13 +81,13 @@ public async Task CreateFileRequest([ActionParameter] GetJobRequest return response.Embedded.Requests.First(); } - [Action("Get request", Description = "Get a translation request.")] + [Action("Get request", Description = "View details of a specific translation request")] public async Task GetRequest([ActionParameter] GetRequest request) { return await GetRequest(request.JobId, request.RequestId); } - [Action("Delete request", Description = "Delete a translation request.")] + [Action("Delete request", Description = "Remove a translation request no longer needed")] public async Task DeleteRequest([ActionParameter] GetRequest request) { var apiRequest = @@ -97,7 +97,7 @@ public async Task DeleteRequest([ActionParameter] GetRequest request return await Client.ExecuteWithErrorHandling(apiRequest); } - [Action("Approve request", Description = "Approve a translation request")] + [Action("Approve request", Description = "Approve a request to send it to the provider")] public async Task ApproveRequest([ActionParameter] GetRequests request) { var apiRequest = @@ -125,7 +125,7 @@ public async Task RejectRequest([ActionParameter] GetRequests request) await Client.ExecuteWithErrorHandling(apiRequest); } - [Action("Update request content", Description = "Update a translation request content")] + [Action("Update request content", Description = "Make changes to the details of an existing translation request.")] public async Task UpdateRequestContent([ActionParameter] GetRequest request, [ActionParameter] UpdateRequestModel updateRequestContentModel) { diff --git a/Apps.Lionbridge/Actions/SourceFileActions.cs b/Apps.Lionbridge/Actions/SourceFileActions.cs index 14d3ec8..2077e35 100644 --- a/Apps.Lionbridge/Actions/SourceFileActions.cs +++ b/Apps.Lionbridge/Actions/SourceFileActions.cs @@ -13,7 +13,7 @@ namespace Apps.Lionbridge.Actions; public class SourceFileActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) : LionbridgeInvocable(invocationContext) { - [Action("Retrieve file", Description = "Retrieve a file from specific request")] + [Action("Retrieve file", Description = "Download a document from a specific request")] public async Task RetrieveFile([ActionParameter] GetRequest request) { string endpoint = $"{ApiEndpoints.Jobs}/{request.JobId}{ApiEndpoints.Requests}/{request.RequestId}{ApiEndpoints.RetrieveFile}"; diff --git a/Apps.Lionbridge/Actions/SupportAssetsActions.cs b/Apps.Lionbridge/Actions/SupportAssetsActions.cs index 673d1b4..a30e090 100644 --- a/Apps.Lionbridge/Actions/SupportAssetsActions.cs +++ b/Apps.Lionbridge/Actions/SupportAssetsActions.cs @@ -18,7 +18,7 @@ namespace Apps.Lionbridge.Actions; public class SupportAssetsActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) : LionbridgeInvocable(invocationContext) { - [Action("Get support asset", Description = "Get a support asset.")] + [Action("Get support asset", Description = "Retrieve details about a specific support asset linked to a job")] public async Task GetSupportAsset([ActionParameter] GetSupportAssetRequest request) { string endpoint = $"{ApiEndpoints.Jobs}/{request.LionBridgeJobId}{ApiEndpoints.SupportAssets}/{request.SupportAssetId}"; @@ -28,7 +28,7 @@ public async Task GetSupportAsset([ActionParameter] GetSup return new SupportAssetResponse(dto); } - [Action("Delete support asset", Description = "Delete a support asset.")] + [Action("Delete support asset", Description = "Remove a support asset from a job")] public async Task DeleteSupportAsset([ActionParameter] GetSupportAssetRequest request) { string endpoint = $"{ApiEndpoints.Jobs}/{request.LionBridgeJobId}{ApiEndpoints.SupportAssets}/{request.SupportAssetId}"; @@ -37,7 +37,7 @@ public async Task DeleteSupportAsset([ActionParameter] GetSupportAssetRequest re await Client.ExecuteWithErrorHandling(apiRequest); } - [Action("Add support asset", Description = "Add a support asset to a job")] + [Action("Add support asset", Description = "Attach a new support asset to job")] public async Task AddSupportAsset([ActionParameter] GetJobRequest request, [ActionParameter] AddSupportAssetRequest addSupportAssetRequest, [ActionParameter] AddFileRequest fileRequest) diff --git a/Apps.Lionbridge/Actions/TranslationContentActions.cs b/Apps.Lionbridge/Actions/TranslationContentActions.cs index 32bd84d..d34a5af 100644 --- a/Apps.Lionbridge/Actions/TranslationContentActions.cs +++ b/Apps.Lionbridge/Actions/TranslationContentActions.cs @@ -16,7 +16,7 @@ namespace Apps.Lionbridge.Actions; public class TranslationContentActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) : LionbridgeInvocable(invocationContext) { - [Action("Get translation content", Description = "Get translation source content for a job")] + [Action("Get translation content", Description = "Access the original content submitted for translation in a job")] public async Task GetAllTranslationContent([ActionParameter] GetJobRequest request, [ActionParameter, Display("Source content ID")] string sourceContentId) { @@ -27,7 +27,7 @@ public async Task GetAllTranslationContent([ActionPa return new TranslationContentResponse(dto); } - [Action("Update translation content", Description = "Replace source content in a job.")] + [Action("Update translation content", Description = "Update the original content submitted for translation")] public async Task UpdateTranslationContent([ActionParameter] GetJobRequest request, [ActionParameter, Display("Source content ID")] string sourceContentId, [ActionParameter] UpdateTranslationContentRequest updateTranslationContentRequest) @@ -45,7 +45,7 @@ public async Task UpdateTranslationContent([ActionPa return new TranslationContentResponse(dto); } - [Action("Retrieve source content", Description = "Retrieve the target content for translation request(s).")] + [Action("Retrieve source content", Description = "Download the translated content for one or more translation requests")] public async Task RetrieveTranslationContent([ActionParameter] GetRequest request) { string endpoint = $"{ApiEndpoints.Jobs}/{request.JobId}{ApiEndpoints.Requests}/{request.RequestId}{ApiEndpoints.Retrieve}"; diff --git a/Apps.Lionbridge/Actions/TranslationMemoryActions.cs b/Apps.Lionbridge/Actions/TranslationMemoryActions.cs index 7c94e5b..0ad3d35 100644 --- a/Apps.Lionbridge/Actions/TranslationMemoryActions.cs +++ b/Apps.Lionbridge/Actions/TranslationMemoryActions.cs @@ -18,7 +18,7 @@ namespace Apps.Lionbridge.Actions; public class TranslationMemoryActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) : LionbridgeInvocable(invocationContext) { - [Action("Add translation memory", Description = "Add a translation memory to a job")] + [Action("Add translation memory", Description = "Incorporate a translation memory file into a job")] public async Task AddTranslationMemory([ActionParameter] GetJobRequest request, [ActionParameter] AddTranslationMemoryRequest addTranslationMemoryRequest, [ActionParameter] AddFileRequest fileRequest) @@ -40,7 +40,7 @@ public async Task AddTranslationMemory([ActionParamet return new TranslationMemoryResponse(dto); } - [Action("Get translation memory", Description = "Get a translation memory.")] + [Action("Get translation memory", Description = "Retrieve details of a specific translation memory associated with a job")] public async Task GetTranslationMemory( [ActionParameter] GetTranslationMemoryRequest request) { diff --git a/Apps.Lionbridge/Apps.Lionbridge.csproj b/Apps.Lionbridge/Apps.Lionbridge.csproj index 052717c..15dfdea 100644 --- a/Apps.Lionbridge/Apps.Lionbridge.csproj +++ b/Apps.Lionbridge/Apps.Lionbridge.csproj @@ -4,7 +4,7 @@ enable enable Lionbridge - Lionbridge + Lionbridge is the translation and localization expert. Advanced automation tools are indispensable to simplify your content journey and effectively reach your customers faster and more cost-efficiently than ever before 1.0.0 Apps.Lionbridge diff --git a/README.md b/README.md new file mode 100644 index 0000000..b75de63 --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# Blackbird.io Mistral AI + +Blackbird is the new automation backbone for the language technology industry. Blackbird provides enterprise-scale automation and orchestration with a simple no-code/low-code platform. Blackbird enables ambitious organizations to identify, vet, and automate as many processes as possible. Not just localization workflows, but any business and IT process. This repository represents an application that is deployable on Blackbird and usable inside the workflow editor. + +## Introduction + + + +Lionbridge is the translation and localization expert. Advanced automation tools are indispensable to simplify your content journey and effectively reach your customers faster and more cost-efficiently than ever before. + +## Before setting up + +Before you can connect you need to make sure that: + +- You must have **Client ID** and **Client Secret** +- Have at least one provider to build Lionbridge workflow + +## Connecting + +1. Navigate to apps and search for Contentstack. If you cannot find Contentstack, then click _Add App_ in the top right corner, select Contentstack, and add the app to your Blackbird environment. +2. Click _Add Connection_. +3. Name your connection for future reference, e.g., 'My client'. +4. In the _API Key_ field, input your API Key +5. In the _Client secret_ field, input your Client secret +6. Click _Connect_. +7. Confirm that the connection has appeared and the status is _Connected_. + +![Lionbridge-connection](image/README/Lionbridge-connection.png) + +## Actions + +### Jobs + +- **Create job**: Create a new job by providing details such as the job's name, description, provider ID, and any specific metadata. This action initiates a new job within the system, setting it up for further operations like submission or updating. + +- **Delete job**: Remove an existing job from the system using its unique job ID. This action is irreversible and should be used when a job is no longer needed or was created in error. + +- **Get job**: Retrieve the details of a specific job by its ID. This includes all the information associated with the job, such as its current status, provider details, and any related files or requests. + +- **Update job**: Modify the details of an existing job. This can include changing the job's name, description, provider, or status. Special instructions for completing the job or transitioning it to an 'In translation' status can also be handled here. + +- **Submit job**: Forward a job to a selected provider for translation. This action takes an existing job and officially starts the translation process with the specified provider. + +- **Archive job**: Move a job to archival storage, which is used for jobs that are completed or inactive but need to be retained for record-keeping. + +- **Unarchive job**: Reverse the archival of a job, bringing it back into active status. This is used when an archived job needs to be revisited or reactivated. + +### Requests + +- **Get requests**: View a list of your translation requests. This method allows users to retrieve a summary of all translation requests associated with a job. + +- **Create file request**: Start a new request to translate a document by uploading a document file. If the 'Request name' or 'Source native ID' are not specified, the system generates unique identifiers (GUIDs) for them. + +- **Get request**: View details of a specific translation request. This action fetches detailed information about a single translation request, including its current status, source and target languages, and any associated metadata. + +- **Delete request**: Remove a translation request no longer needed. When a translation request is no longer required, this method can be used to delete it from the system, keeping the list of active requests clean and manageable. + +- **Approve request**: Approve a request to send it to the provider. Before a translation request is sent out for translation, it can be reviewed and approved through this method, signaling readiness for translation to commence. + +- **Reject request**: Reject a translation request. If a translation request is found to be unnecessary or incorrect, it can be rejected, preventing it from being sent to the translation provider. + +- **Update request content**: Make changes to the details of an existing translation request. This method allows users to modify the content or parameters of a translation request, such as updating the document or changing the target language. + +### Source file + +- **Retrieve file**: Download a document from a specific request. This method allows for the retrieval of a file associated with a translation request. It checks if the uploaded file is empty and throws an exception if so. If the file has no specific name, it defaults to using the request name with an .xml extension, indicating that the request was likely created from source content. This approach ensures that every document, whether initially named or not, is retrievable and properly identified within the system. + +### Support assets + +- **Get support asset**: Retrieve details about a specific support asset linked to a job. This action is essential for accessing the properties and information of support assets, which are additional resources or documents associated with a translation job to aid in the translation process. + +- **Delete support asset**: Remove a support asset from a job. This functionality is crucial for maintaining the relevancy and accuracy of the support assets associated with a job, allowing users to delete assets that are no longer needed or were added in error. + +- **Add support asset**: Attach a new support asset to a job. This process involves uploading a file to be used as a support asset, providing additional context or information necessary for the translation. If the file management system ID (FMS file ID) is missing, an exception is thrown, ensuring that each support asset is properly tracked and managed within the system. The method also handles the creation of metadata for the support asset, further enriching the asset's data for use during translation. + +### Translation content + +- **Get translation content**: Access the original content submitted for translation in a job. This action allows users to retrieve the source material provided for translation, enabling review or verification of the content before or after the translation process. + +- **Update translation content**: Update the original content submitted for translation. This method facilitates the modification of source content in a translation job, allowing for corrections, updates, or refinements to ensure accuracy and relevance of the translation output. + +- **Retrieve source content**: Download the translated content for one or more translation requests + +### Translation memory + +- **Add translation memory**: Incorporate a translation memory file into a job. + +- **Get translation memory**: Retrieve details of a specific translation memory associated with a job. + +## Events + +- **Job status updated**: This webhook notifies you when a job's status changes, such as when it is completed or cancelled. If specific status codes are not defined, it defaults to triggering only for jobs that are "IN_TRANSLATION". The webhook assesses various conditions like job ID matches, archival status, deletion status, and compares the current status against expected status codes. If conditions are met, detailed information about the job is returned, including its current state. + +- **On request status updated**: Similar to job status updates, this webhook alerts you when a translation request reaches a certain milestone, like finishing or being cancelled. By default, it is set to activate for requests that are in the "REVIEW_TRANSLATION" status unless otherwise specified. The webhook evaluates the request's compliance with the predefined conditions, such as status codes, job ID relevance, and whether the request IDs match the expected values. Successful validation results in a detailed report on the job and the affected requests, ensuring you're always up-to-date on your translation projects' progress. + +## Feedback + +Do you want to use this app or do you have feedback on our implementation? Reach out to us using the [established channels](https://www.blackbird.io/) or create an issue. + + diff --git a/image/README/Lionbridge-connection.png b/image/README/Lionbridge-connection.png new file mode 100644 index 0000000000000000000000000000000000000000..4758394ccf92d4f278041b82f503d87e298a7f4a GIT binary patch literal 15503 zcmd_RXIK+jw=Rm`vY-fvAV`f81w;X*cQgu!ND&p477>t6gwRRAB`6|Iy3`l}K|v4@ zFjS>W3spc$=n1_iKp;6YthM&O>$JO{yT84keb4#fBbk|*Wy~?hc*i@&3^CN#WW&mL1Cj z;Pc@p*B>C6nAmC=|MoQ^vaOkzRL|&KyK=|dVqtg*afg^nait&dKgtg6Wq#sK!SD~h6-CS9(%k24vf^Nnbs+cE6OXki9PIeqv zGLqW(b%OiOX%CY_@`oMoeAaa+b)r`0&!x!75W}S4Lij!*s`rT0O#1lNR6(1|NXph& z5~s$FsAbrthLcvg(NGw#75MXhy>^f^Xb3BK70GO~_uUByM>A-+)F7@d9Lwk&b{BO` zjL~p1XY-{Yqv4YA3I}$tbME-R3{?7-V;irbbiS7Eqp42IH<*kJNQthdZRG!hiRtN7 zy?m0h+@Mp*uv0DWm&>KnQydOUn+Wa3cTy}sX!cg7dC`|XMs&b;s8UMfhnbi@|D*qX zku%Yu>Uo+tY+=k4bjrO@vSPMS`ue;I%=zqZOiT}LOy;5QY&5gcRZQ&PK|`)t$p-hD z5@-^Wf`M7@Z#k{uOrD&UUt%uSJPKAZ`}w~1RiYra;v|^it?z}Op|R6~ShAkpJ|-qZ zK_@CM$*|lUYj`^~Os~pzzni%L; zVWyWY$iYw;De$#+5SaK|OK;FG4xl$o%WnUry0uSF>kQl~*D^ljnDCu6*>zM`n^vYW z)zd}-TKai>x5a=E=jDgjhO}WStA^di9bgN_*Mml0FSg-GMUK&nouphh4(#<*{w~Up z@dI1h$fv=yxG2BQA52eQ74CE3f17fw%{+nG!|6d;*E!ZL5=&o)tAujV&1+)usjf=J zY7~k?xVu?|e#A`-q6K5yw_LbN2N%ocyy!K#m9l;y$>CQoJE9_9)CHgdok_7#q@BWJ z*!!N-FFQh=jWBdSAl`3A$p&{MiA=?yxA`{I-QhA@-xRI3-1w)BTR?l$SZH_W^XAJz zj|Nt>#n>Bv`?h-fhM6SgkM6Tj*_C6HgC?}W< zM_;vl*`GZHw5CfCTZ(T=BWXlDf8!SPp=Fof?V}O}LL0ZS*3}Iag^AiM+ugn}8Ex9< z_RY0=`t{3>-`qCeQmcA3&_)gKa=31`Y;cyZ-WvN=_83`-s!06Yr19J`d|rj0J#*4$ zW1+Tl#(4F*nT{0D-V2RFk?;D8%S2|r-r;hv616d)*`J88NsFD28@C$zasQXs=eFJ5 zY#EKncZi3=u<9O>;Ofb$>f~0+qvL@Ou5b2L3)1~?KYlfB^<@l*;?ZAbClOd@tOWNZl<5J5v zt_1`Nlpjx3Pd>+ak>9FRZctjHD?#NEdM!-0QP}%u+sia1m_!xo%k$$Lv*v+^%e`Od zw={KXHBLQtx!oBWbc32`V{J;5TYJEL3Z}dJphun?9X=HR-^U~~HQp^LvCn~F+qkqz z+3p&~o9v@$-396P$oj5cr*Y;xyAy(48r-yCuWhoNQm>H!cL8&ypFX)BYCo}0$-_of z73R70VjVJU!o@SCX}6=UXQp(HI=+vC9*G`*{(ww+V0R4z3Tn!h8a4NQBaAnI=aB*Hx$VTWG`1=K$7H)D!}5OY{-ze48gjuB^g*9Ky{(-Qccq z$y%SrguV&OCQu#LaMdtS+@^z61>VCqtU9$gbS1} z!Mp*%H`q$upkGL~W7`8K4tO*gx|fYpk1$yW7su$7$>_ZzW5za$I+6; zc3oBCrD>xTmgg`Ti@M{fX&1QzZrxVmNUEs*z0SGaE#St3`MGe&#?gr7#A5@;(G|q? z?Dd6+Jge&Vy3X?BLa!(Samjbb>mL@O2(MtpPs)+#Dt@n2Ro#$|$RUi~dA?p%y3|Blok=BpY)b$qg-_j(_?^7V+WobpHVWF)a z7$&Bw{+qfl%Ydri(Xt!Ig=9*S$CGi82uy6%@RWvt#;O}HKIXg(7&0qQZ=1MraB78s z!$fVH^%E^xPf$}>`HyJp=~DGnHQDz;_})-zSL&s{72ZHA`l}b*aS0Nk2`RyuY$q{G zCWg&~(+qhBWogB3KG{K4Q&*q4*tunnYqVEtt z;BUC-5BhmTJkAo1x2)A6cDa!UMn z@_5I(1n+Yz5tC@_%%HG#_O01npE{5I7hKCd!FJkT_B1|L8YjBzMM!j-%C?EdH%D|t z(}ip@aH3nZWHRJBlHTfu=#0JXA0*iWIuL>S_2#Bzo3;NT6dvpkG3k}@9SJEVP3@n@ z*%L=zk31mc`iDFD`r6L>q}JcE8?Rk^ysxU_}+tW|2C#sdDK{KK*;(Z%e0Fpk5xL!Dp7acNLy}F`9-jy%UD8?05m&n)` z2+PX4*3-HyftRkZJeQj&eXL69=Q zKTl^FNb98`0F2(iPOAuWfxj#O_>5p+&8G=Dhl`;w06|9?uJxbW%|YLK*tS(3%y)+e zz}I_g#^lC!naBK~;bZ83;Jp8gUFezzrBMg)dx6YH2i-Pc;at!BkD|*_it@l*8U#Bs zG-qJO@VRS*zdrmMfxG!Kx854>|0olO5!9}8BL1(@%-h)EbJ1HM)L`nZ|6hav_``o4 z=|t}e==k472-TNBX!0L$_L7wxzFzmsyby8Pvp<_=e-#&gz?u6JFcIxH18@D1f1FWO za9ivByBIKe{Ar*F3GK{+D(Bj+pz@+T_>-+$tilW1cYHjY)7{N1s!l8SyDV9iA3X@s zyAeQ*DV)F|rMXdH+n-hUV1^dyYsxR7gT%45iB9_$tlbU2b*Uu9Gj#d-A;#EM%#|QK zD0a-iqVHGWe7x=#wvBOgZ3ma!n4976yvk+^PCAG7hHO`gRI3~ST zhklA2#M?(MFGkOISi5U27Cx9!={&A_06VhZ3p00YHiZ&kc2LFhg@M&)%F?`!lG9Sm zmcI#ZsqI72A!}XQ+Rf(cw%)1Zc$Hn6j{R_j{AN-#1?WO97r3C39vsT9C1;t_ey*kM zPvwh`2$z2^y?Wg$#_7njOVGBZAZ`8Hj^^v=Y0H7GlUjt?GWnTOd|Q}sUX>)LY7fN1 zE8IziLu>w%m#0>g9p+#MSHn<)K5v07H+n>Z-KKRx{ajWTSaS1g(wt53E8+F& zMAEr~V_RcLbN)=LCjZnO@EyliuPxaIn%ks-M??2ghRy+7pBn8(7xWR++6wIKQH zwGEq;2Gof1n(N})qPi$2xdumdLbMDKN9W`yTm5mP??KdhWDJ-WW9uM&41z_nXkvjx zB3ABGs`y^lWo7y0Io1ZYyt_O<*O>e)vRu3E(5<>pF1TYt_6(FV5Q!;*MBboSP2=U8 zoZJTvSjJx}AZ=EN2qA_eXm*nk(l6K>MWOEVtbP!_$>mJa9AD_7_OefU%TJ-#Ret1F z`x(aqi1wJkz2;rQ!^NvlR75@+lm=lFUv>;p9vk^4n?r)(SG_Umqij`>&M0E~nDaiS zr*YpIn1(#IxgGeMrM1$~?125aT~~8F~l*u%N@&!Tp zu4nhdX!CC>^XAqXwuHZIs)r1{UTj1Xc?9L;YFbp(@13I%P)ncSX>TLA+ z`|f~#cxc1$8Rv=5bubpMYYmEZ=?g}YLEJST*O^m&&fIFq`>{d7hx(tnBZ!q*qQBYt zWh-=80);?9@L%yFDfwBm&V_(BIL9zRN1#2+m73j|KaOKKO_+1Kpgm^tzWR2Xs!gOZ zV!kFmD%R1hbt|P~wDld{lq)_{RTK>RJinKo($U3O@*x7TjC&^hxdOOP3`zpr)nXU+ z>m{3Qv-u4ciRLvq6D=WzR8;F^&V-81MIrOfWRfUc5OTwRB+*idk~6Wv;ehE&^vf)- z3=kyZ72$$#qaeQe{0I%ta}=L_Oy6!W6rqcsL11Jvg9-i-n=w zwfa0e1bp+MpSwWMNP;2yS>Ya9OCad@02E)nYEjF|GsID5DHK!JFgqeFla6NI6j)TCFfU5)cehCc!woz z%4Su%R+tv%)5QMPC!Doqp56aUzIG9WzFeGlN|~VOGQK9qk1=il_6)VJnF?%}Ykcj$%55aGxn3Q)xkO3Di z9J-e6A}6S=Jf*Ub-_%)4NZ=W)mJ%eWKYp}!cZ=o~nS2qgwnm?MSsxTpXD{_=T00b}f_~VIDsl*@3%N8q6p{W&-Rp8cYa%a}#W8R{^E= zbm}Q{@2AIvBIi{~Is3t)OJz%l!FSMi!BeAE=nmU%@B|hxWg3f8)9QVwzt^v zcus&_&X=?*4)YqmgxiB{K7cl8WSMj;-$DV(>q7OadIe%Z`XqW@UVI>YTD>$SCWI8b z#T(DICg)`qp}wDqNgT*@3ySr_>}p-OTy0Y$6J}73$bG{+e0Cu8w=T#h3d#6GzVk&( zc|mgrCGG5{I@0Gb6d(E`f8-pzbUw6#Fc(t0#-{p12Ew?6bDe2ehTdiqS~5}^rPzd$O~d9Pyw$<_k%@VeUB%jvXpI>$=_PvmMe%?E|go}UUZ8r{qcd4 zKB#w%^mQHL6x-`DJy9?^w1b}Wqi1|kr+w~89%M_)|0UzFXrsS^Zb752G>nX#oqIOt z-hBOuMF_(?#)gC4Px}#k^pvXv8{VVSw-X|0m#YY_syhohWQB8R%EoBhTwu!^MTQuP zZ^aMAlYj4LLBG0+FgZA=ck!n@2+(D^0#=@-D1W^>t014`9e!ZA(sHKXF0a%z!P;Pu zm~8QSu~M&aaYn|iB@GkVK_bs4JsL|cNAP~gZ$YQ+n_2LdW^U1wf`>}V=?1xsh^q)K zy|zH6R;~55_8Dyod=ixmqTq&yj91VAgEP)rF28d1@p6tbveP~woaoq@KaZ+h)1D3> z9Ptm&stnuhP1Dum!8&(&c^Qd33vby7H7~^1O}tO9?c`{D*TKIK?JUA(isyOO$>*nX z^p{yjcxu0Rj>4Cg(aPnC3Th|)AjfuwfD7T=hKSw7K8NR&PgmIBZs;^_BxXUo=$hSv zYts`UZNfUD^=vLBker$&JcK?di0=#YCMI^;v077NGye(^UF;uYFPyg#XI_FGJQ zAZbS3nxs~uIdI*{a_b`PC5rh_FZ_=xR7be(Qum6YJF{@l!3jQ}I#=AlqUj3JRg1JZ zX)535i^-C_ejZ%QZ_iGHo9r{U0clCjPaX|h00xoMPsTGbv!sJVT*06@;@77xH3yCR zfR&uPZkNbmvhEWCjFmWJdG8e!kt$KFQiiB)BhzIlmdN&|+v^5mnU<_+nT1wVw{1Ye zSfp+g)=-V7K}fw~I?j-|GDG5doM=;^0D4sq(16%<$}xp*;LV?Ci1uvMoS_P1+D!FG z_0QT@z=wal*G>wBzp>W%dO9nu{Y?u14s|&bGDaACabHU#tt3CU>dp^!%;A+6&$*gb zTs+M5^nfO~M>ZS|?LY{}8tA*K6KLDqJ3=|n zByD$RX#m)rY{fz05}l9FLHq?`92)S2>U=G`$34N=%|#NAk~2)c0*a$YlTaHr<8Q+d9rLjw2mKF5m|Ecr8%{?Wt8OMD1pM4Wdb{)>ls z|52guzl5OWJzAt5oNea&_gd=o+2GD|4dIgZ(piGrn(GB9VRLwHR1(7M_*Ol3bhX}b zmR1*_M!n^AyK^HcWA23#^y`SY&-8lgsN2e5qEG6R=WA>_-g7?%aqBf~59msMiHEFr z??p6au8?xS1-n&zPov~kDA;I3EE2*7*%5193ayyxejCXXywzv4s|qcIJ|}DqrLW%f zY(h0W8(n1=jKXdt4gOjNlV&!~(7+Od8TE?2u>zD-nOG?9-6VWB=;hbGZ27 z(~ic8gQmq=Y2NltDL~t}0~s1*AEf`o&+p#zE8S0q&Y0Bk#6Z@Yqj(~kqACrV0;i=T z{d;!?P^cZA_wR1JGUP;%x4%cUoaKjiZaC0H%Ge7AaciLyV>Z>>*|^2P)w+@8vPuY- zc5iG*`Ns6bp^Ojp7Sj*x3omUeai#2qy;@u4LbCGTmLn!E458C~BYzFM?hbd0Q7 z`7X7@zIg9*_p|_kf-b>W9p+H>0DG0DY;_J#lRY)~c5usUAoIuag8}(uVR{+4AERtd z(&Q}HvIP_@dEP)pev^^3isi!AYmbMS1tl^43K-d+GFK7^{k=1)`}#QfMdX`&%Q#h} zcsxo(z>mt(C?42in-*tVZ34|9S0yNp6OS}(+FzAY6_d+(bj1(f$aTRHX$YVxdw1VLef@Kk3c3^qhJY=2+*VrCeY7s}m)xY$a##KOQ z%!1KSUL+l4oHmxJ@qWyzUn{ow+^#%u3ledu4r5MTAc80*Z;77TjepZ^{3G43e6kYG z2^i4}B8&*3nVB_W@c=skXo5t+#Uvn>ypyl=St1TLB)shCpQ*=4IYJ}@4B0#&UN7RM zV74q(6)Tt~He~Ymt5Xnx1bp)HD?-)W_Vez?EVYxAvlTn8N-hvSg}%9@DfPA;vz5Wa z8uxanliNa#2FV-5`>>#2#2@5ET180zGE!~19;mJ77ap(}0mfo#m{}vMdUx7nz9U&E z0Rurd+t{{3(5EA%VMzQv)zun*pBn#&Ye5ZZoJo~jRi8xzOgoYMf$1TdrJq!f^9ilA zsAa7uj^eQFRfb=`Xux*0VaP0^6hxKMgJLB+N|ot3P7{-S&dAbI45>SP6}$v5JiZ`< zIecgADlQhS^)tC8-`BrnNtlVLgAvosgm#SjycDr`#a#j0)z4S8qNRA>S0H~ze&fUz z)z9T}a8+B(*I!)=F{B`2!IPDiRMi%hE^6;&AMXY3Pc-K62zg};=k~)@CG7VpD=9AV zvoJaQ;f+5Pfxi6Z@NmLN=%ViW+V-l!;~`t%Gl(F*GUCBPs(9R{gn?+(bMKSqI{oZuV@dACs(e8|KhigX1VSpCRSi9Z zk|M2GBu!D3Vxz}P)og=I7Ac2W30K$Fe!MLC-FWAnFg(a4v%ur$qIq*xa)wN7;RLFj)?4 zw=~%^)XBcRxv6q_mM?IDy!NH#;ks+h1rELu?V^U@%#jg%S!yVx4qynDy{P;mtZ}M` zd{L1dQyRrvVEN1Yvafb1)ZH?U6T1}|9um6H^-w=YL-FD2Nf0@1A^*5~=TooL$$eqR z3%P1?C_@Dh>`et(HR;hC?9OpNyA|Hay!$qs7FWe7@%`%VhKLij;Sn)$Zjw8)+; zacS}4$P>dL#D8kj`bW$!+Fr8Xvd?z}DbUG-y5V^tLXv)BX*ExYw?{VqU7zrF{ z%zFtbuEyb;V{3ctg-pq=^=Vk3M)0e^TM(>5hF=kpLBGfIGVVbYa~tH-1z*dqES|~b z+Y4O*gFCn&C{|^pU5(w{Bo8j(uTL5W6896}BTyZU^`DYReY1NR>Ut^QI)53Ze=6oz5SBgQvPV=fI0#f_(JkXX!PiygoriU;$A|U-ULkhAmlX71 zKP7;wgFAsf3TU~#DB}HP*P!O%Tf7z0Lg>#=UD9F&i}xycCU8TLgs7HuX%4g^KZr~| z9|z0;2lm`WD2%5C1^sdjDCWbn!iXp8$-5Yl1-e$$%z5Gks?`RK@p|(821VH0C<9i{!|(ShYmC!m>q6 zC3&vY8KmFk!aegetz7JAa~qj;bc78r9$|PdcBHKToS%H2TvmP*gXnj}7x&jrj-lab zQrJD??9>*TEz)`c;f_Xsv!y2`?L z%H*=j^bVH4(?jhQRCZhW2nKrIkFTw-px}2P_*_L0G2UH$vXikcpWLLP_?kT@ij7k% zx!k(d|KT%tQ|CZ|s@T>%(=5}Bs*?-?&0%FYP_k^37|~k&0%4QuTpFy%&B~@ zYdu)ITybK&1boulnI|KKqjNcK)lA#v6m6>G6Cnk*-#BqHFmj;A=pVhKxlJrMoy( zPn>$yLAIn`xv~;<^KOs@sB0L4H}>=EF_fyFp-5>wBK0*g92pID1KgaqZL~;d?*)k< z&Z~Y>Jce8;ImS_TxA2~b&G=%g9-)*+Tm}{xIYCh}qg9m&uP+RB^Be)u*tZ)D;!BOg zVL$tK#8Rx4CVLy6`~-}w3Og^gpUKM>Z{Z8#FvJNNoS7*%J7VjZORoc zgpdCCSmYQD4L zppxfVWeLM(mY5po`wgJ8+QF5r2WCbF&m_yo9>XGCD=wZJ96DpFJiStrv9rtfx{C9V zbttY?$%Pr!uLe)P!UHHE#>r5 z#gBf9sL*tzd|fPugVM;m5A^QkT%2&vjVjG<{TW3yzWq+wFb;tIMC-V}heH|*g2W?yx*EEvJdb$!zneg_s;j0dpKCp7KWYZ9kr3J7OFKSLo#zYxPd zgdXq_Vw|8uoSG;gDjr(?PiYBbM#dL-DY-vbeVMS5Lg4wb!0O$Chn1%Q+11^EtgU;dHKH4(tFgffV%-UbFjkb>pA-v~V1`hsj3B80+x5QEtUO)TtFA~OuP zr(3W4%twuy zYbHoJT@n@m#1@2ssLGgA6$g*+H=f&iz}W-&7fkrs$tPE*#OY|dE8zjZ+bz4FHW}*O zCsS?JM~2Q?O;=O_V)-0T^mN(4X}>_Fn66S4FN@9fr`z#JY0GxpMtl3Ii?vy7vd{5W z!~N%=WCsQOH3JD|gv{67@ad|Nmf@v?_Wj4wO0yd4+!j~Kv;ivJu)*y8tq5}t-kDak zlknF99TPkwvve5`#CO+xFXx(vVW~lJsiU=ArXoE7y*av--rd#rNW&$Dc8=n+GO&8qeysX|%pE#Ge2H=X*$P zd9R986{F;G)XBuL1O0^0_rczqEqr(x}(%R*`LAD~4Xr;Y#02@8!bwh?Bn(raFkAX$)tQ-*pvlHk%j$mIxCE z;q@2p2zlZF&ym!oW+AGQd=Q@6swOIn60%)i-s-C%hBhxC3-Q#Hj(7MeUp9O5&~+`T zXTprk$=>c~!VKrgPpv}JvOHQK3AdEu|T8VDj z=hNFe9nUoG#r`?&%q7&dpM9!gR4R+k)>uyDL2XTn7v%7-D-aD$Si8#mz?||~*2^Mh zhP3{z5)_HfT7JlHYl0fs*i2fu`&!RCB|Ej`M)f9s>hdLPxjQuZl_Q5l2@BgxoNz}r zxMNVhTz*oO@k333tj;n@0YL6!FQ+<9|3NIp=^mk1``=fd1(7&C*njiU7`>OhB$>`s zmuDLAtRYpV%Zj6X7{mbBB{0zq6_2bWVbQuL4clCnUN*d`9 zoO$_CEn?dadlTP4r;Jdl67V^qc+<+=jG=VX577tV6+xXWA68lfiv=rH zj>#_;uLg^qkVq0|lz@Tp^ixLSb^?Beq_g%-_P^-&(bVSGQs#R^JR#`(6|F_CYu6Rp zw?fFHsQV!L0x+8<$W7ezae>i+upokgnCk?;4q*B_1~21`S&J`OlO6^EDf``(P_IRDDd@vPYC6| z(3(syNdBKdw0|cKJYZVv2SWv~XLJ*MT$ z4G;quitinYG2=1X-i$9h#0CFf2Jhv>!oP|WX}dC8^9G%)ya82fi92OP^Z>DJ=g40x zGxxYg;!%GMCxp0s+g!5^h|*}ZT35g{uShB8m4@}LwNSsK>GKH?MLfpwuT|eYhOJ#i zRB!2ePpNoK4bgPB)_zs(5~OKa)lHjtLPANMM?9V3*bUEtjk!!|+s!#AGUq9sy;^RY zR=vdXT&&n{=F{M+>wNZ%ZFQvtakYZ%fF+@E6y`N1a;6Wl+{Ev z*Hw0DH;JQ~TbSx5+dh|O-7P#)C&FvjqCvE_=+h+e619f93hASr!DwQx(%j+di^T?l zo0t6s%PmMUTctbB34nat6K8oG1e+Cjh6{%_jVN!AT&VX_kI@KDknf}*Aas+#8F zy?-Nfsd|N^#gP=+9BGg|xUhY7^Y@T0 zjK5Mv0F{ld@6V@V)LaWHOy3#-Ei>=c5P;tw!;Cf|1GI8$%F4yZI_qk5we9op?Q41SW_P z^^PEEKPSd1M+W(y8ZJ;|0HMp7jO4WVOT!|m$oO?`y-W?!JDk`Ed`!6%gkCDZk~iU} zZ|9xNph>g2fa=~8?xF;d?`7D_+XObAYG3Pp2CHdWI^_i@{0Q{qgqol5z9@=3VPQ j?Sbt7W}u3IXgd3?JPWRIn|k2XnV57m^{?f@9zOeT=XtFy literal 0 HcmV?d00001 From 96a25ede98c3375779b62d4ecbfec3143ec45ebb Mon Sep 17 00:00:00 2001 From: vitalii-bezuhlyi Date: Thu, 21 Mar 2024 12:30:23 +0200 Subject: [PATCH 55/55] Updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b75de63..84d0090 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Blackbird.io Mistral AI +# Blackbird.io Lionbridge Blackbird is the new automation backbone for the language technology industry. Blackbird provides enterprise-scale automation and orchestration with a simple no-code/low-code platform. Blackbird enables ambitious organizations to identify, vet, and automate as many processes as possible. Not just localization workflows, but any business and IT process. This repository represents an application that is deployable on Blackbird and usable inside the workflow editor.