diff --git a/Apps.Lionbridge/Actions/JobActions.cs b/Apps.Lionbridge/Actions/JobActions.cs new file mode 100644 index 0000000..dc1c54d --- /dev/null +++ b/Apps.Lionbridge/Actions/JobActions.cs @@ -0,0 +1,168 @@ +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.Provider; +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(InvocationContext invocationContext) : LionbridgeInvocable(invocationContext) +{ + [Action("Create job", Description = "Create a new 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); + } + + [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 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 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(); + + 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.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"); + } + + 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.Patch) + .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 + }); + + 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); + } + + [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) + .WithJsonBody(new { providerId = providerRequest.ProviderId }); + + return await Client.ExecuteWithErrorHandling(apiRequest); + } + + [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 = "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); + return await Client.ExecuteWithErrorHandling(apiRequest); + } + + protected async Task CompleteJob(string jobId) + { + var apiRequest = new LionbridgeRequest($"{ApiEndpoints.Jobs}/{jobId}/complete", Method.Put); + return await Client.ExecuteWithErrorHandling(apiRequest); + } + + protected async Task IntranslateJob(string jobId) + { + 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 new file mode 100644 index 0000000..e88e4b3 --- /dev/null +++ b/Apps.Lionbridge/Actions/RequestActions.cs @@ -0,0 +1,165 @@ +using Apps.Lionbridge.Api; +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; +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; +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, IFileManagementClient fileManagementClient) + : LionbridgeInvocable(invocationContext) +{ + [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); + } + + // [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(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, + Method.Post) + .WithJsonBody(new + { + requestName = request.RequestName ?? Guid.NewGuid().ToString(), + sourceNativeId = request.SourceNativeId ?? Guid.NewGuid().ToString(), + sourceNativeLanguageCode = request.SourceNativeLanguageCode, + targetNativeIds = request.TargetNativeIds, + targetNativeLanguageCodes = new List { request.TargetNativeLanguage }, + wordCount = request.WordCount, + extendedMetadata = metadata, + sourcecontentId = sourceContentId + }); + + var response = await Client.ExecuteWithErrorHandling(apiRequest); + return response.Embedded.Requests.First(); + } + + [Action("Create file request", Description = "Start a new request to translate a document")] + public async Task CreateFileRequest([ActionParameter] GetJobRequest jobRequest, + [ActionParameter] AddSourceFileRequest sourceFileRequest) + { + var uploadResponse = await UploadFmsFile(jobRequest.JobId, new AddFileRequest(sourceFileRequest), fileManagementClient); + + 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 ?? Guid.NewGuid().ToString(), + sourceNativeId = sourceFileRequest.SourceNativeId ?? Guid.NewGuid().ToString(), + 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 = "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 = "Remove a translation request no longer needed")] + 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); + } + + [Action("Approve request", Description = "Approve a request to send it to the provider")] + 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 = "Make changes to the details of an existing translation request.")] + public async Task UpdateRequestContent([ActionParameter] GetRequest request, + [ActionParameter] UpdateRequestModel updateRequestContentModel) + { + string endpoint = $"{ApiEndpoints.Jobs}/{request.JobId}{ApiEndpoints.Requests}/{request}"; + var apiRequest = + new LionbridgeRequest(endpoint, Method.Patch) + .WithJsonBody(new + { + 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 response; + } + + private async Task CreateTranslationContent(string jobId, IEnumerable? keys, + IEnumerable? values) + { + 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); + return response.SourceContentId; + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Actions/SourceFileActions.cs b/Apps.Lionbridge/Actions/SourceFileActions.cs new file mode 100644 index 0000000..2077e35 --- /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 = "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}"; + 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 ?? 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); + + return new RetrieveFileResponse { File = fileReference }; + } +} \ 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..a30e090 --- /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; +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 = "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}"; + var apiRequest = new LionbridgeRequest(endpoint); + + var dto = await Client.ExecuteWithErrorHandling(apiRequest); + return new SupportAssetResponse(dto); + } + + [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}"; + var apiRequest = new LionbridgeRequest(endpoint, Method.Delete); + + await Client.ExecuteWithErrorHandling(apiRequest); + } + + [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) + { + 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 ?? throw new Exception("FMS file ID is missing"), + description = addSupportAssetRequest.Description, + sourceNativeIds = addSupportAssetRequest.SourceNativeIds ?? new List { Guid.NewGuid().ToString() }, + 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/Actions/TranslationContentActions.cs b/Apps.Lionbridge/Actions/TranslationContentActions.cs new file mode 100644 index 0000000..d34a5af --- /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 = "Access the original content submitted for translation in 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 = "Update the original content submitted for translation")] + 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 = "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}"; + + 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/Actions/TranslationMemoryActions.cs b/Apps.Lionbridge/Actions/TranslationMemoryActions.cs new file mode 100644 index 0000000..0ad3d35 --- /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; +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 = "Incorporate a translation memory file into a job")] + public async Task AddTranslationMemory([ActionParameter] GetJobRequest request, + [ActionParameter] AddTranslationMemoryRequest addTranslationMemoryRequest, + [ActionParameter] AddFileRequest 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 = fileRequest.TargetNativeLanguage, + extendedMetadata = extendedMetadata + }); + + var dto = await Client.ExecuteWithErrorHandling(apiRequest); + return new TranslationMemoryResponse(dto); + } + + [Action("Get translation memory", Description = "Retrieve details of a specific translation memory associated with a job")] + 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/Api/LionbridgeClient.cs b/Apps.Lionbridge/Api/LionbridgeClient.cs index d361c57..d49fed4 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,20 +23,61 @@ 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; + } + + public override async Task ExecuteWithErrorHandling(RestRequest request) + { + var response = await ExecuteWithErrorHandling(request); + return JsonConvert.DeserializeObject(response.Content, JsonSettings); + } + 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}; Exception message: {e.Message}"); + } } private string GetAccessToken(IEnumerable authenticationCredentialsProviders) 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/Apps.Lionbridge.csproj b/Apps.Lionbridge/Apps.Lionbridge.csproj index abbb7b9..15dfdea 100644 --- a/Apps.Lionbridge/Apps.Lionbridge.csproj +++ b/Apps.Lionbridge/Apps.Lionbridge.csproj @@ -4,20 +4,20 @@ 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 + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - - - diff --git a/Apps.Lionbridge/Constants/ApiEndpoints.cs b/Apps.Lionbridge/Constants/ApiEndpoints.cs new file mode 100644 index 0000000..20115b3 --- /dev/null +++ b/Apps.Lionbridge/Constants/ApiEndpoints.cs @@ -0,0 +1,21 @@ +namespace Apps.Lionbridge.Constants; + +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"; + public const string UpdateContent = "/updatecontent"; + public const string UpdateFileContent = "/updatefilecontent"; + 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"; + public const string StatusUpdates = "/statusupdates"; + public const string Listeners = StatusUpdates + "/listeners"; +} \ No newline at end of file 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/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs b/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs new file mode 100644 index 0000000..aefa6e0 --- /dev/null +++ b/Apps.Lionbridge/DataSourceHandlers/EnumDataHandlers/JobStatuses.cs @@ -0,0 +1,13 @@ +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" }, + { "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/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 new file mode 100644 index 0000000..f10503a --- /dev/null +++ b/Apps.Lionbridge/DataSourceHandlers/ProviderDataSourceHandler.cs @@ -0,0 +1,34 @@ +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(InvocationContext invocationContext) + : LionbridgeInvocable(invocationContext), IAsyncDataSourceHandler +{ + 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/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/DataSourceHandlers/SupportAssetDataSourceHandler.cs b/Apps.Lionbridge/DataSourceHandlers/SupportAssetDataSourceHandler.cs new file mode 100644 index 0000000..8b1806c --- /dev/null +++ b/Apps.Lionbridge/DataSourceHandlers/SupportAssetDataSourceHandler.cs @@ -0,0 +1,38 @@ +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.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.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/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/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/LionbridgeInvocable.cs b/Apps.Lionbridge/LionbridgeInvocable.cs index eae08cc..e144183 100644 --- a/Apps.Lionbridge/LionbridgeInvocable.cs +++ b/Apps.Lionbridge/LionbridgeInvocable.cs @@ -1,6 +1,18 @@ 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.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; +using RestSharp; namespace Apps.Lionbridge; @@ -12,4 +24,104 @@ 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); + } + + protected async Task UploadFmsFile(string jobId, AddFileRequest 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); + using 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 of the process"); + } + + 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(); + } + + 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 GetRequests(string jobId, IEnumerable? requestIds) + { + RestRequest apiRequest = new LionbridgeRequest( + $"{ApiEndpoints.Jobs}/{jobId}" + ApiEndpoints.Requests, + Method.Get); + + var response = await Client.ExecuteWithErrorHandling(apiRequest); + var requests = response.Embedded.Requests.ToList(); + if (requestIds != null) + { + var requestIdsAsArray = requestIds as string[] ?? requestIds.ToArray(); + if (requestIdsAsArray.Any()) + { + requests = requests.Where(x => requestIdsAsArray.Contains(x.RequestId)).ToList(); + } + } + + return new GetRequestsResponse { Requests = requests }; + } } \ 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/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/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/Dtos/RequestDto.cs b/Apps.Lionbridge/Models/Dtos/RequestDto.cs new file mode 100644 index 0000000..9a37592 --- /dev/null +++ b/Apps.Lionbridge/Models/Dtos/RequestDto.cs @@ -0,0 +1,51 @@ +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; } + + [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/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/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/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/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/Dtos/UpdateJobApiRequest.cs b/Apps.Lionbridge/Models/Dtos/UpdateJobApiRequest.cs new file mode 100644 index 0000000..2b74f67 --- /dev/null +++ b/Apps.Lionbridge/Models/Dtos/UpdateJobApiRequest.cs @@ -0,0 +1,44 @@ +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; } + + [Display("Labels")] + public Dictionary Labels { get; set; } +} \ No newline at end of file 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/File/AddSourceFileRequest.cs b/Apps.Lionbridge/Models/Requests/File/AddSourceFileRequest.cs new file mode 100644 index 0000000..f830586 --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/File/AddSourceFileRequest.cs @@ -0,0 +1,16 @@ +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; } +} \ 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/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/Requests/Job/UpdateJobRequest.cs b/Apps.Lionbridge/Models/Requests/Job/UpdateJobRequest.cs new file mode 100644 index 0000000..fb18b57 --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/Job/UpdateJobRequest.cs @@ -0,0 +1,48 @@ +using Apps.Lionbridge.DataSourceHandlers; +using Apps.Lionbridge.DataSourceHandlers.EnumDataHandlers; +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("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; } + + [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; } + + [Display("Job status"), DataSource(typeof(JobCompletionStatuses))] + public string? JobCompletionStatus { 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 diff --git a/Apps.Lionbridge/Models/Requests/Request/AddRequestBaseModel.cs b/Apps.Lionbridge/Models/Requests/Request/AddRequestBaseModel.cs new file mode 100644 index 0000000..efcbcf3 --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/Request/AddRequestBaseModel.cs @@ -0,0 +1,27 @@ +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Lionbridge.Models.Requests.Request; + +public class AddRequestBaseModel +{ + [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("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("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/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 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/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/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 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/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/Requests/SupportAssets/AddSupportAssetRequest.cs b/Apps.Lionbridge/Models/Requests/SupportAssets/AddSupportAssetRequest.cs new file mode 100644 index 0000000..738846f --- /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 IDs")] + public IEnumerable? SourceNativeIds { get; set; } + + [Display("Source native language")] + 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..6314837 --- /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 LionBridgeJobId { 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/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/Requests/TranslationMemory/AddTranslationMemoryRequest.cs b/Apps.Lionbridge/Models/Requests/TranslationMemory/AddTranslationMemoryRequest.cs new file mode 100644 index 0000000..b761c72 --- /dev/null +++ b/Apps.Lionbridge/Models/Requests/TranslationMemory/AddTranslationMemoryRequest.cs @@ -0,0 +1,15 @@ +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("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/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 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 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 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 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 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/SupportAssets/SupportAssetResponse.cs b/Apps.Lionbridge/Models/Responses/SupportAssets/SupportAssetResponse.cs new file mode 100644 index 0000000..fd683d2 --- /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 IEnumerable ExtendedMetadataKeys { get; set; } + + [Display("Extended metadata values")] + public IEnumerable 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 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/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 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 new file mode 100644 index 0000000..75292cd --- /dev/null +++ b/Apps.Lionbridge/Models/Responses/TranslationContent/TranslationContentResponse.cs @@ -0,0 +1,22 @@ +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Lionbridge.Models.Responses.TranslationContent; + +public class TranslationContentResponse +{ + [Display("Source Content ID")] + public string SourceContentId { 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 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 diff --git a/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs b/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs new file mode 100644 index 0000000..6f315fb --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Bridge/BridgeService.cs @@ -0,0 +1,51 @@ +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); + + var response = client.Execute(request); + if (!response.IsSuccessful) + { + throw new Exception($"Failed to subscribe to event {_event} for listener {listenerId}"); + } + } + + 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); + } + + 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/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..fb0d3d0 --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Handlers/BaseWebhookHandler.cs @@ -0,0 +1,105 @@ +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 abstract 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); + string eventType = GetEventType(); + bridge.Subscribe(eventType, 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); + string eventType = GetEventType(); + bridge.Unsubscribe(eventType, listener.ListenerId, values["payloadUrl"]); + + if (!bridge.IsAnySubscriberExist(eventType, listener.ListenerId)) + { + await DeleteListenerAsync(listener.ListenerId); + } + } + } + + private async Task GetListenerIdAsync() + { + var request = new LionbridgeRequest(ApiEndpoints.Listeners); + var response = await Client.ExecuteWithErrorHandling(request); + + // Lionbridge api returns eventtype without 'ed' at the end: REQUEST_UPDATED (it will be REQUEST_UPDATE) + return response.Embedded.listeners.FirstOrDefault(x => SubscriptionEvent.Contains(x.Type)); + } + + private async Task CreateListenerAsync() + { + var request = new LionbridgeRequest(ApiEndpoints.Listeners, Method.Post) + .AddJsonBody(new + { + uri = _bridgeServiceUrl, + type = SubscriptionEvent, + statusCodes = GetStatusCodes(), + 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); + } + + protected abstract string[] GetStatusCodes(); + + private string GetEventType() + { + if(SubscriptionEvent.Contains("JOB")) + { + return "JOB_UPDATE"; + } + + if(SubscriptionEvent.Contains("REQUEST")) + { + return "REQUEST_UPDATE"; + } + + throw new Exception("Invalid event type"); + } +} \ 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..6ebe34a --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Handlers/JobStatusUpdatedHandler.cs @@ -0,0 +1,15 @@ +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) { } + + protected override string[] GetStatusCodes() + { + return ["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 new file mode 100644 index 0000000..8a958b4 --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Handlers/RequestStatusUpdatedHandler.cs @@ -0,0 +1,14 @@ +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) { } + protected override string[] GetStatusCodes() + { + return new[] { "IN_TRANSLATION", "CANCELLED", "REVIEW_TRANSLATION" }; + } +} \ No newline at end of file diff --git a/Apps.Lionbridge/Webhooks/Inputs/GetRequestsInput.cs b/Apps.Lionbridge/Webhooks/Inputs/GetRequestsInput.cs new file mode 100644 index 0000000..deb44a6 --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Inputs/GetRequestsInput.cs @@ -0,0 +1,18 @@ +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 GetRequestsInput +{ + [Display("Job ID"), DataSource(typeof(JobDataSourceHandler))] + public string? JobId { get; set; } + + [Display("Request IDs"), DataSource(typeof(RequestDataSourceHandler))] + public IEnumerable? RequestIds { get; set; } + + [Display("Status codes"), DataSource(typeof(RequestStatuses))] + public IEnumerable? StatusCodes { get; set; } +} \ 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..8f0ce6e --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Inputs/JobStatusUpdatedInput.cs @@ -0,0 +1,19 @@ +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; } + + 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..7ebfc65 --- /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(RequestStatuses))] + public IEnumerable? StatusCodes { get; set; } +} \ 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/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/Payload/RequestStatusUpdatedPayload.cs b/Apps.Lionbridge/Webhooks/Payload/RequestStatusUpdatedPayload.cs new file mode 100644 index 0000000..285076e --- /dev/null +++ b/Apps.Lionbridge/Webhooks/Payload/RequestStatusUpdatedPayload.cs @@ -0,0 +1,22 @@ +namespace Apps.Lionbridge.Webhooks.Payload; + +public class RequestStatusUpdatedPayload +{ + public List? 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/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/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..e14c00d --- /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/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 new file mode 100644 index 0000000..9f1f63b --- /dev/null +++ b/Apps.Lionbridge/Webhooks/WebhookList.cs @@ -0,0 +1,136 @@ +using Apps.Lionbridge.Api; +using Apps.Lionbridge.Constants; +using Apps.Lionbridge.Models.Dtos; +using Apps.Lionbridge.Webhooks.Handlers; +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", 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) + { + 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; + } + + bool archived = input.Archived ?? false; + if (archived != data.Archived) + { + return preflightResponse; + } + + bool deleted = input.Deleted ?? false; + if (deleted != data.Deleted) + { + 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 + { + HttpResponseMessage = null, + ReceivedWebhookRequestType = WebhookRequestType.Default, + Result = new JobStatusUpdatedResponse + { + Job = jobDto, + Deleted = data.Deleted, + Archived = data.Archived + } + }; + } + + #endregion + + #region Request Webhooks + + [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( + WebhookRequest webhookRequest, [WebhookParameter] GetRequestsInput requests) + { + 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 + }; + + var statuses = requests.StatusCodes ?? new List { "REVIEW_TRANSLATION" }; + if (!statuses.Contains(data.StatusCode)) + { + return preflightResponse; + } + + 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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..84d0090 --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# 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. + +## 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 0000000..4758394 Binary files /dev/null and b/image/README/Lionbridge-connection.png differ