From 1b0d3b44f84199c4e4c511ecc94731c83b757812 Mon Sep 17 00:00:00 2001 From: Max Inno <40697586+innomaxx@users.noreply.github.com> Date: Thu, 30 May 2024 15:21:48 +0300 Subject: [PATCH] feat(api): add String-based API support (#235) * feat(api): add String-based API support * chore: deprecate branch methods in Source Files section --- src/Crowdin.Api/AI/AiPromptConfiguration.cs | 2 +- src/Crowdin.Api/Branches/AddBranchRequest.cs | 20 ++ src/Crowdin.Api/Branches/Branch.cs | 29 ++ src/Crowdin.Api/Branches/BranchCloneStatus.cs | 35 +++ src/Crowdin.Api/Branches/BranchMergeStatus.cs | 45 ++++ .../Branches/BranchMergeStatusId.cs | 22 ++ .../Branches/BranchMergeSummary.cs | 41 +++ src/Crowdin.Api/Branches/BranchPatch.cs | 30 +++ .../Branches/BranchesApiExecutor.cs | 196 ++++++++++++++ .../Branches/CloneBranchRequest.cs | 20 ++ .../Branches/MergeBranchRequest.cs | 19 ++ src/Crowdin.Api/Core/Constants.cs | 2 + .../ProjectsGroups/EnterpriseProjectForm.cs | 12 + .../ProjectsGroups/FileBasedProjectForm.cs | 6 + src/Crowdin.Api/ProjectsGroups/ProjectBase.cs | 6 + .../ProjectsGroups/ProjectInfoPatch.cs | 9 +- .../ProjectsGroups/ProjectSettingPatch.cs | 27 +- .../ProjectsGroups/ProjectSettings.cs | 12 + .../ProjectsGroupsApiExecutor.cs | 6 + .../ProjectsGroups/StringsBasedProjectForm.cs | 15 ++ .../ProjectsGroups/TmContextType.cs | 19 ++ .../WorkflowTemplateStepConfig.cs | 75 ++++++ src/Crowdin.Api/Reports/ReportStatus.cs | 5 + .../SourceFiles/AddBranchRequest.cs | 15 +- src/Crowdin.Api/SourceFiles/Branch.cs | 24 +- src/Crowdin.Api/SourceFiles/BranchPatch.cs | 6 + .../SourceFiles/SourceFilesApiExecutor.cs | 10 +- .../SourceStrings/AddStringRequest.cs | 4 +- src/Crowdin.Api/SourceStrings/SourceString.cs | 4 +- .../SourceStrings/SourceStringsApiExecutor.cs | 36 +++ .../StringBasedProjectFileType.cs | 37 +++ .../StringUploadResponseModel.cs | 64 +++++ .../SourceStrings/UploadStringsRequest.cs | 40 +++ .../StringTranslations/StringTranslation.cs | 6 + .../Tasks/LanguageServiceTaskCreateForm.cs | 11 +- src/Crowdin.Api/Tasks/TaskCreateForm.cs | 7 +- .../Tasks/VendorGengoTaskCreateForm.cs | 7 +- .../Tasks/VendorManualTaskCreateForm.cs | 9 +- .../Tasks/VendorOhtTaskCreateForm.cs | 7 +- .../Tasks/VendorTranslatedTaskCreateForm.cs | 7 +- .../ApplyPreTranslationRequest.cs | 7 +- .../Translations/PreTranslateAttributes.cs | 12 +- .../Translations/UploadTranslationsRequest.cs | 7 +- .../UploadTranslationsResponse.cs | 5 +- .../Branches/BranchesCrudApiTests.cs | 182 +++++++++++++ .../Branches/BranchesOperationsApiTests.cs | 255 ++++++++++++++++++ .../Core/Resources/Branches.Designer.cs | 102 +++++++ .../Core/Resources/Branches.resx | 133 +++++++++ .../Core/Resources/SourceStrings.Designer.cs | 59 ++++ .../Core/Resources/SourceStrings.resx | 58 ++++ .../Core/Resources/Tasks.Designer.cs | 7 +- .../Core/Resources/Tasks.resx | 7 +- .../Crowdin.Api.Tests.csproj | 45 ++++ .../SourceStrings/SourceStringsApiTests.cs | 131 ++++++++- 54 files changed, 1885 insertions(+), 72 deletions(-) create mode 100644 src/Crowdin.Api/Branches/AddBranchRequest.cs create mode 100644 src/Crowdin.Api/Branches/Branch.cs create mode 100644 src/Crowdin.Api/Branches/BranchCloneStatus.cs create mode 100644 src/Crowdin.Api/Branches/BranchMergeStatus.cs create mode 100644 src/Crowdin.Api/Branches/BranchMergeStatusId.cs create mode 100644 src/Crowdin.Api/Branches/BranchMergeSummary.cs create mode 100644 src/Crowdin.Api/Branches/BranchPatch.cs create mode 100644 src/Crowdin.Api/Branches/BranchesApiExecutor.cs create mode 100644 src/Crowdin.Api/Branches/CloneBranchRequest.cs create mode 100644 src/Crowdin.Api/Branches/MergeBranchRequest.cs create mode 100644 src/Crowdin.Api/ProjectsGroups/TmContextType.cs create mode 100644 src/Crowdin.Api/ProjectsGroups/WorkflowTemplateStepConfig.cs create mode 100644 src/Crowdin.Api/SourceStrings/StringBasedProjectFileType.cs create mode 100644 src/Crowdin.Api/SourceStrings/StringUploadResponseModel.cs create mode 100644 src/Crowdin.Api/SourceStrings/UploadStringsRequest.cs create mode 100644 tests/Crowdin.Api.Tests/Branches/BranchesCrudApiTests.cs create mode 100644 tests/Crowdin.Api.Tests/Branches/BranchesOperationsApiTests.cs create mode 100644 tests/Crowdin.Api.Tests/Core/Resources/Branches.Designer.cs create mode 100644 tests/Crowdin.Api.Tests/Core/Resources/Branches.resx diff --git a/src/Crowdin.Api/AI/AiPromptConfiguration.cs b/src/Crowdin.Api/AI/AiPromptConfiguration.cs index 10a94d5f..27268a84 100644 --- a/src/Crowdin.Api/AI/AiPromptConfiguration.cs +++ b/src/Crowdin.Api/AI/AiPromptConfiguration.cs @@ -45,7 +45,7 @@ public class BasicModeAiPromptConfiguration : AiPromptConfiguration public bool PublicProjectDescription { get; set; } [PublicAPI] - public class OtherLanguageTranslationsConfig // TODO: to outer? + public class OtherLanguageTranslationsConfig { [JsonProperty("isEnabled")] public bool IsEnabled { get; set; } diff --git a/src/Crowdin.Api/Branches/AddBranchRequest.cs b/src/Crowdin.Api/Branches/AddBranchRequest.cs new file mode 100644 index 00000000..e126e37b --- /dev/null +++ b/src/Crowdin.Api/Branches/AddBranchRequest.cs @@ -0,0 +1,20 @@ + +using JetBrains.Annotations; +using Newtonsoft.Json; + +#nullable enable + +namespace Crowdin.Api.Branches +{ + [PublicAPI] + public class AddBranchRequest + { + [JsonProperty("name")] +#pragma warning disable CS8618 + public string Name { get; set; } +#pragma warning restore CS8618 + + [JsonProperty("title")] + public string? Title { get; set; } + } +} \ No newline at end of file diff --git a/src/Crowdin.Api/Branches/Branch.cs b/src/Crowdin.Api/Branches/Branch.cs new file mode 100644 index 00000000..a46854bf --- /dev/null +++ b/src/Crowdin.Api/Branches/Branch.cs @@ -0,0 +1,29 @@ + +using System; +using JetBrains.Annotations; +using Newtonsoft.Json; + +namespace Crowdin.Api.Branches +{ + [PublicAPI] + public class Branch + { + [JsonProperty("id")] + public int Id { get; set; } + + [JsonProperty("projectId")] + public int ProjectId { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("createdAt")] + public DateTimeOffset CreatedAt { get; set; } + + [JsonProperty("updatedAt")] + public DateTimeOffset? UpdatedAt { get; set; } + } +} \ No newline at end of file diff --git a/src/Crowdin.Api/Branches/BranchCloneStatus.cs b/src/Crowdin.Api/Branches/BranchCloneStatus.cs new file mode 100644 index 00000000..02efc6ca --- /dev/null +++ b/src/Crowdin.Api/Branches/BranchCloneStatus.cs @@ -0,0 +1,35 @@ + +using System; +using JetBrains.Annotations; +using Newtonsoft.Json; + +namespace Crowdin.Api.Branches +{ + [PublicAPI] + public class BranchCloneStatus + { + [JsonProperty("identifier")] + public string Identifier { get; set; } + + [JsonProperty("status")] + public OperationStatus Status { get; set; } + + [JsonProperty("progress")] + public int Progress { get; set; } + + [JsonProperty("attributes")] + public object Attributes { get; set; } + + [JsonProperty("createdAt")] + public DateTimeOffset CreatedAt { get; set; } + + [JsonProperty("updatedAt")] + public DateTimeOffset? UpdatedAt { get; set; } + + [JsonProperty("startedAt")] + public DateTimeOffset? StartedAt { get; set; } + + [JsonProperty("finishedAt")] + public DateTimeOffset? FinishedAt { get; set; } + } +} \ No newline at end of file diff --git a/src/Crowdin.Api/Branches/BranchMergeStatus.cs b/src/Crowdin.Api/Branches/BranchMergeStatus.cs new file mode 100644 index 00000000..28e2a6f3 --- /dev/null +++ b/src/Crowdin.Api/Branches/BranchMergeStatus.cs @@ -0,0 +1,45 @@ + +using System; +using JetBrains.Annotations; +using Newtonsoft.Json; + +namespace Crowdin.Api.Branches +{ + [PublicAPI] + public class BranchMergeStatus + { + [JsonProperty("identifier")] + public string Identifier { get; set; } + + [JsonProperty("status")] + public OperationStatus Status { get; set; } + + [JsonProperty("progress")] + public int Progress { get; set; } + + [JsonProperty("attributes")] + public AttributesData Attributes { get; set; } + + [JsonProperty("createdAt")] + public DateTimeOffset CreatedAt { get; set; } + + [JsonProperty("updatedAt")] + public DateTimeOffset? UpdatedAt { get; set; } + + [JsonProperty("startedAt")] + public DateTimeOffset? StartedAt { get; set; } + + [JsonProperty("finishedAt")] + public DateTimeOffset FinishedAt { get; set; } + + [PublicAPI] + public class AttributesData + { + [JsonProperty("sourceBranchId")] + public int SourceBranchId { get; set; } + + [JsonProperty("deleteAfterMerge")] + public bool DeleteAfterMerge { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Crowdin.Api/Branches/BranchMergeStatusId.cs b/src/Crowdin.Api/Branches/BranchMergeStatusId.cs new file mode 100644 index 00000000..c70b13e5 --- /dev/null +++ b/src/Crowdin.Api/Branches/BranchMergeStatusId.cs @@ -0,0 +1,22 @@ + +using System.ComponentModel; +using JetBrains.Annotations; + +namespace Crowdin.Api.Branches +{ + [PublicAPI] + public enum BranchMergeStatusId + { + [Description("conflict")] + Conflict, + + [Description("failed")] + Failed, + + [Description("inProgress")] + InProgress, + + [Description("merged")] + Merged + } +} \ No newline at end of file diff --git a/src/Crowdin.Api/Branches/BranchMergeSummary.cs b/src/Crowdin.Api/Branches/BranchMergeSummary.cs new file mode 100644 index 00000000..54f219dd --- /dev/null +++ b/src/Crowdin.Api/Branches/BranchMergeSummary.cs @@ -0,0 +1,41 @@ + +using JetBrains.Annotations; +using Newtonsoft.Json; + +namespace Crowdin.Api.Branches +{ + [PublicAPI] + public class BranchMergeSummary + { + [JsonProperty("status")] + public BranchMergeStatusId Status { get; set; } + + [JsonProperty("sourceBranchId")] + public int SourceBranchId { get; set; } + + [JsonProperty("targetBranchId")] + public int TargetBranchId { get; set; } + + [JsonProperty("dryRun")] + public bool DryRun { get; set; } + + [JsonProperty("details")] + public DetailsData Details { get; set; } + + [PublicAPI] + public class DetailsData + { + [JsonProperty("added")] + public int Added { get; set; } + + [JsonProperty("deleted")] + public int Deleted { get; set; } + + [JsonProperty("updated")] + public int Updated { get; set; } + + [JsonProperty("conflicted")] + public int Conflicted { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Crowdin.Api/Branches/BranchPatch.cs b/src/Crowdin.Api/Branches/BranchPatch.cs new file mode 100644 index 00000000..0dbf084d --- /dev/null +++ b/src/Crowdin.Api/Branches/BranchPatch.cs @@ -0,0 +1,30 @@ + +using System.ComponentModel; +using JetBrains.Annotations; +using Newtonsoft.Json; + +namespace Crowdin.Api.Branches +{ + [PublicAPI] + public class BranchPatch : PatchEntry + { + [JsonProperty("path")] + public BranchPatchPath Path { get; set; } + } + + [PublicAPI] + public enum BranchPatchPath + { + [Description("/name")] + Name, + + [Description("/title")] + Title, + + [Description("/exportPattern")] + ExportPattern, + + [Description("/priority")] + Priority + } +} \ No newline at end of file diff --git a/src/Crowdin.Api/Branches/BranchesApiExecutor.cs b/src/Crowdin.Api/Branches/BranchesApiExecutor.cs new file mode 100644 index 00000000..1d75c7a5 --- /dev/null +++ b/src/Crowdin.Api/Branches/BranchesApiExecutor.cs @@ -0,0 +1,196 @@ + +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; + +using JetBrains.Annotations; + +using Crowdin.Api.Core; + +#nullable enable + +namespace Crowdin.Api.Branches +{ + public class BranchesApiExecutor + { + private readonly ICrowdinApiClient _apiClient; + private readonly IJsonParser _jsonParser; + + public BranchesApiExecutor(ICrowdinApiClient apiClient) + { + _apiClient = apiClient; + _jsonParser = apiClient.DefaultJsonParser; + } + + public BranchesApiExecutor(ICrowdinApiClient apiClient, IJsonParser jsonParser) + { + _apiClient = apiClient; + _jsonParser = jsonParser; + } + + /// + /// Get Cloned Branch. Documentation: + /// Crowdin API + /// Crowdin Enterprise API + /// + [PublicAPI] + public async Task GetClonedBranch(int projectId, int branchId, string cloneId) + { + var url = $"/projects/{projectId}/branches/{branchId}/clones/{cloneId}/branch"; + CrowdinApiResult result = await _apiClient.SendGetRequest(url); + return _jsonParser.ParseResponseObject(result.JsonObject); + } + + /// + /// Clone Branch. Documentation: + /// Crowdin API + /// Crowdin Enterprise API + /// + [PublicAPI] + public async Task CloneBranch(int projectId, int branchId, CloneBranchRequest request) + { + var url = $"/projects/{projectId}/branches/{branchId}/clones"; + CrowdinApiResult result = await _apiClient.SendPostRequest(url, request); + return _jsonParser.ParseResponseObject(result.JsonObject); + } + + /// + /// Check Branch Clone Status. Documentation: + /// Crowdin API + /// Crowdin Enterprise API + /// + [PublicAPI] + public async Task CheckBranchCloneStatus(int projectId, int branchId, string cloneId) + { + var url = $"/projects/{projectId}/branches/{branchId}/clones/{cloneId}"; + CrowdinApiResult result = await _apiClient.SendGetRequest(url); + return _jsonParser.ParseResponseObject(result.JsonObject); + } + + /// + /// List Branches. Documentation: + /// Crowdin API + /// Crowdin Enterprise API + /// + [PublicAPI] + public async Task> ListBranches( + int projectId, + string? name = null, + int limit = 25, + int offset = 0) + { + string url = FormUrl_Branches(projectId); + + IDictionary queryParams = Utils.CreateQueryParamsFromPaging(limit, offset); + queryParams.AddParamIfPresent("name", name); + + CrowdinApiResult result = await _apiClient.SendGetRequest(url, queryParams); + return _jsonParser.ParseResponseList(result.JsonObject); + } + + /// + /// Add Branch. Documentation: + /// Crowdin API + /// Crowdin Enterprise API + /// + [PublicAPI] + public async Task AddBranch(int projectId, AddBranchRequest request) + { + string url = FormUrl_Branches(projectId); + CrowdinApiResult result = await _apiClient.SendPostRequest(url, request); + return _jsonParser.ParseResponseObject(result.JsonObject); + } + + /// + /// Get Branch. Documentation: + /// Crowdin API + /// Crowdin Enterprise API + /// + [PublicAPI] + public async Task GetBranch(int projectId, int branchId) + { + string url = FormUrl_BranchId(projectId, branchId); + CrowdinApiResult result = await _apiClient.SendGetRequest(url); + return _jsonParser.ParseResponseObject(result.JsonObject); + } + + /// + /// Delete Branch. Documentation: + /// Crowdin API + /// Crowdin Enterprise API + /// + [PublicAPI] + public async Task DeleteBranch(int projectId, int branchId) + { + string url = FormUrl_BranchId(projectId, branchId); + HttpStatusCode statusCode = await _apiClient.SendDeleteRequest(url); + Utils.ThrowIfStatusNot204(statusCode, $"Branch {branchId} removal failed"); + } + + /// + /// Edit Branch. Documentation: + /// Crowdin API + /// Crowdin Enterprise API + /// + [PublicAPI] + public async Task EditBranch(int projectId, int branchId, IEnumerable patches) + { + string url = FormUrl_BranchId(projectId, branchId); + CrowdinApiResult result = await _apiClient.SendPatchRequest(url, patches); + return _jsonParser.ParseResponseObject(result.JsonObject); + } + + /// + /// Merge Branch. Documentation: + /// Crowdin API + /// Crowdin Enterprise API + /// + [PublicAPI] + public async Task MergeBranch(int projectId, int branchId, MergeBranchRequest request) + { + var url = $"/projects/{projectId}/branches/{branchId}/merges"; + CrowdinApiResult result = await _apiClient.SendPostRequest(url, request); + return _jsonParser.ParseResponseObject(result.JsonObject); + } + + /// + /// Check Branch Merge Status. Documentation: + /// Crowdin API + /// Crowdin Enterprise API + /// + [PublicAPI] + public async Task CheckBranchMergeStatus(int projectId, int branchId, string mergeId) + { + var url = $"/projects/{projectId}/branches/{branchId}/merges/{mergeId}"; + CrowdinApiResult result = await _apiClient.SendGetRequest(url); + return _jsonParser.ParseResponseObject(result.JsonObject); + } + + /// + /// Get Branch Merge Summary. Documentation: + /// Crowdin API + /// Crowdin Enterprise API + /// + [PublicAPI] + public async Task GetBranchMergeSummary(int projectId, int branchId, string mergeId) + { + var url = $"/projects/{projectId}/branches/{branchId}/merges/{mergeId}/summary"; + CrowdinApiResult result = await _apiClient.SendGetRequest(url); + return _jsonParser.ParseResponseObject(result.JsonObject); + } + + #region Helper methods + + private static string FormUrl_Branches(int projectId) + { + return $"/projects/{projectId}/branches"; + } + + private static string FormUrl_BranchId(int projectId, int branchId) + { + return $"/projects/{projectId}/branches/{branchId}"; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Crowdin.Api/Branches/CloneBranchRequest.cs b/src/Crowdin.Api/Branches/CloneBranchRequest.cs new file mode 100644 index 00000000..b2c6a49f --- /dev/null +++ b/src/Crowdin.Api/Branches/CloneBranchRequest.cs @@ -0,0 +1,20 @@ + +using JetBrains.Annotations; +using Newtonsoft.Json; + +#nullable enable + +namespace Crowdin.Api.Branches +{ + [PublicAPI] + public class CloneBranchRequest + { + [JsonProperty("name")] +#pragma warning disable CS8618 + public string Name { get; set; } +#pragma warning restore CS8618 + + [JsonProperty("title")] + public string? Title { get; set; } + } +} \ No newline at end of file diff --git a/src/Crowdin.Api/Branches/MergeBranchRequest.cs b/src/Crowdin.Api/Branches/MergeBranchRequest.cs new file mode 100644 index 00000000..74ea7b97 --- /dev/null +++ b/src/Crowdin.Api/Branches/MergeBranchRequest.cs @@ -0,0 +1,19 @@ + +using JetBrains.Annotations; +using Newtonsoft.Json; + +namespace Crowdin.Api.Branches +{ + [PublicAPI] + public class MergeBranchRequest + { + [JsonProperty("deleteAfterMerge")] + public bool? DeleteAfterMerge { get; set; } + + [JsonProperty("sourceBranchId")] + public int SourceBranchId { get; set; } + + [JsonProperty("dryRun")] + public bool? DryRun { get; set; } + } +} \ No newline at end of file diff --git a/src/Crowdin.Api/Core/Constants.cs b/src/Crowdin.Api/Core/Constants.cs index 742c2951..cfda4667 100644 --- a/src/Crowdin.Api/Core/Constants.cs +++ b/src/Crowdin.Api/Core/Constants.cs @@ -6,5 +6,7 @@ internal static class MessageTexts public const string DeprecatedProperty = "This property is deprecated, please avoid using it"; public const string DeprecatedModel = "This model is deprecated, see API docs for alternative"; + + public const string UseBranchesNamespace = "This API is deprecated, see Crowdin.Api.Branches namespace"; } } \ No newline at end of file diff --git a/src/Crowdin.Api/ProjectsGroups/EnterpriseProjectForm.cs b/src/Crowdin.Api/ProjectsGroups/EnterpriseProjectForm.cs index 68c08ad2..3fbc01e6 100644 --- a/src/Crowdin.Api/ProjectsGroups/EnterpriseProjectForm.cs +++ b/src/Crowdin.Api/ProjectsGroups/EnterpriseProjectForm.cs @@ -23,6 +23,9 @@ public class EnterpriseProjectForm : AddProjectRequest [JsonProperty("templateId")] public int? TemplateId { get; set; } + [JsonProperty("steps")] + public ICollection? Steps { get; set; } + [JsonProperty("groupId")] public int? GroupId { get; set; } @@ -47,6 +50,9 @@ public class EnterpriseProjectForm : AddProjectRequest [JsonProperty("isMtAllowed")] public bool? IsMtAllowed { get; set; } + [JsonProperty("taskBasedAccessControl")] + public bool? TaskBasedAccessControl { get; set; } + [JsonProperty("autoSubstitution")] public bool? AutoSubstitution { get; set; } @@ -89,6 +95,9 @@ public class EnterpriseProjectForm : AddProjectRequest [JsonProperty("qaCheckIsActive")] public bool? QaCheckIsActive { get; set; } + [JsonProperty("qaApprovalsCount")] + public int? QaApprovalsCount { get; set; } + [JsonProperty("qaCheckCategories")] public QaCheckCategories? QaCheckCategories { get; set; } @@ -98,6 +107,9 @@ public class EnterpriseProjectForm : AddProjectRequest [JsonProperty("customQaCheckIds")] public ICollection? CustomQaCheckIds { get; set; } + [JsonProperty("tmContextType")] + public TmContextType? TmContextType { get; set; } + [JsonProperty("languageMapping")] public IDictionary? LanguageMapping { get; set; } diff --git a/src/Crowdin.Api/ProjectsGroups/FileBasedProjectForm.cs b/src/Crowdin.Api/ProjectsGroups/FileBasedProjectForm.cs index b81f0873..ee669b14 100644 --- a/src/Crowdin.Api/ProjectsGroups/FileBasedProjectForm.cs +++ b/src/Crowdin.Api/ProjectsGroups/FileBasedProjectForm.cs @@ -56,6 +56,9 @@ public class FileBasedProjectForm : ProjectForm [JsonProperty("isMtAllowed")] public bool? IsMtAllowed { get; set; } + [JsonProperty("taskBasedAccessControl")] + public bool? TaskBasedAccessControl { get; set; } + [JsonProperty("autoSubstitution")] public bool? AutoSubstitution { get; set; } @@ -103,5 +106,8 @@ public class FileBasedProjectForm : ProjectForm [JsonProperty("glossaryAccess")] public bool? GlossaryAccess { get; set; } + + [JsonProperty("tmContextType")] + public TmContextType? TmContextType { get; set; } } } \ No newline at end of file diff --git a/src/Crowdin.Api/ProjectsGroups/ProjectBase.cs b/src/Crowdin.Api/ProjectsGroups/ProjectBase.cs index 4829c06d..403ee9c1 100644 --- a/src/Crowdin.Api/ProjectsGroups/ProjectBase.cs +++ b/src/Crowdin.Api/ProjectsGroups/ProjectBase.cs @@ -12,6 +12,9 @@ public class ProjectBase [JsonProperty("id")] public int Id { get; set; } + [JsonProperty("type")] + public ProjectType Type { get; set; } + [JsonProperty("userId")] public int UserId { get; set; } @@ -45,6 +48,9 @@ public class ProjectBase [JsonProperty("lastActivity")] public DateTimeOffset? LastActivity { get; set; } + [JsonProperty("sourceLanguage")] + public Language SourceLanguage { get; set; } + [JsonProperty("targetLanguages")] public Language[] TargetLanguages { get; set; } } diff --git a/src/Crowdin.Api/ProjectsGroups/ProjectInfoPatch.cs b/src/Crowdin.Api/ProjectsGroups/ProjectInfoPatch.cs index c2677628..45f37c91 100644 --- a/src/Crowdin.Api/ProjectsGroups/ProjectInfoPatch.cs +++ b/src/Crowdin.Api/ProjectsGroups/ProjectInfoPatch.cs @@ -1,11 +1,13 @@  using System.Collections.Generic; using System.ComponentModel; -using Crowdin.Api.Core; -using Crowdin.Api.Core.Converters; + using JetBrains.Annotations; using Newtonsoft.Json; +using Crowdin.Api.Core; +using Crowdin.Api.Core.Converters; + namespace Crowdin.Api.ProjectsGroups { [PublicAPI] @@ -82,6 +84,9 @@ public enum ProjectInfoPathCode [Description("/isMtAllowed")] IsMachineTranslationAllowed, + [Description("/taskBasedAccessControl")] + TaskBasedAccessControl, + [Description("/autoSubstitution")] AutoSubstitution, diff --git a/src/Crowdin.Api/ProjectsGroups/ProjectSettingPatch.cs b/src/Crowdin.Api/ProjectsGroups/ProjectSettingPatch.cs index 31d92b1a..e4d1ce2d 100644 --- a/src/Crowdin.Api/ProjectsGroups/ProjectSettingPatch.cs +++ b/src/Crowdin.Api/ProjectsGroups/ProjectSettingPatch.cs @@ -1,11 +1,13 @@  using System.Collections.Generic; using System.ComponentModel; -using Crowdin.Api.Core; -using Crowdin.Api.Core.Converters; + using JetBrains.Annotations; using Newtonsoft.Json; +using Crowdin.Api.Core; +using Crowdin.Api.Core.Converters; + namespace Crowdin.Api.ProjectsGroups { [PublicAPI] @@ -64,6 +66,9 @@ public enum ProjectSettingPathCode [Description("/autoSubstitution")] AutoSubstitution, + [Description("/skipUntranslatedFiles")] + SkipUntranslatedFiles, + [Description("/skipUntranslatedStrings")] SkipUntranslatedStrings, @@ -79,6 +84,9 @@ public enum ProjectSettingPathCode [Description("/useGlobalTm")] UseGlobalTm, + [Description("/showTmSuggestionsDialects")] + ShowTmSuggestionsDialects, + [Description("/normalizePlaceholder")] NormalizePlaceholder, @@ -88,6 +96,12 @@ public enum ProjectSettingPathCode [Description("/inContext")] InContext, + [Description("/inContextPseudoLanguageId")] + InContextPseudoLanguageId, + + [Description("/inContextProcessHiddenStrings")] + InContextProcessHiddenStrings, + [Description("/pseudoLanguageId")] PseudoLanguageId, @@ -113,5 +127,14 @@ public enum ProjectSettingPathCode TmPenalties, // /tmPenalties/{penaltyKey} + + [Description("/tmContextType")] + TmContextType, + + [Description("/tmPreTranslate")] + TmPreTranslate, + + [Description("/mtPreTranslate")] + MtPreTranslate, } } \ No newline at end of file diff --git a/src/Crowdin.Api/ProjectsGroups/ProjectSettings.cs b/src/Crowdin.Api/ProjectsGroups/ProjectSettings.cs index b19d1574..71004c1a 100644 --- a/src/Crowdin.Api/ProjectsGroups/ProjectSettings.cs +++ b/src/Crowdin.Api/ProjectsGroups/ProjectSettings.cs @@ -9,6 +9,9 @@ namespace Crowdin.Api.ProjectsGroups [PublicAPI] public class ProjectSettings : Project { + [JsonProperty("clientOrganizationId")] + public int ClientOrganizationId { get; set; } + [JsonProperty("translateDuplicates")] public DupTranslateAction TranslateDuplicates { get; set; } @@ -21,6 +24,9 @@ public class ProjectSettings : Project [JsonProperty("isMtAllowed")] public bool IsMachineTranslationAllowed { get; set; } + [JsonProperty("taskBasedAccessControl")] + public bool TaskBasedAccessControl { get; set; } + [JsonProperty("hiddenStringsProofreadersAccess")] public bool HiddenStringsProofreadersAccess { get; set; } @@ -48,6 +54,9 @@ public class ProjectSettings : Project [JsonProperty("useGlobalTm")] public bool UseGlobalTm { get; set; } + [JsonProperty("tmContextType")] + public TmContextType TmContextType { get; set; } + [JsonProperty("normalizePlaceholder")] public bool NormalizePlaceholder { get; set; } @@ -73,6 +82,9 @@ public class ProjectSettings : Project [JsonProperty("qaCheckIsActive")] public bool QaCheckIsActive { get; set; } + [JsonProperty("qaApprovalsCount")] + public int QaApprovalsCount { get; set; } + [JsonProperty("qaCheckCategories")] public QaCheckCategories QaCheckCategories { get; set; } diff --git a/src/Crowdin.Api/ProjectsGroups/ProjectsGroupsApiExecutor.cs b/src/Crowdin.Api/ProjectsGroups/ProjectsGroupsApiExecutor.cs index 43b1a658..31ab2d95 100644 --- a/src/Crowdin.Api/ProjectsGroups/ProjectsGroupsApiExecutor.cs +++ b/src/Crowdin.Api/ProjectsGroups/ProjectsGroupsApiExecutor.cs @@ -110,6 +110,7 @@ public async Task EditGroup(int groupId, IEnumerable patches) public async Task> ListProjects( int? userId = null, int? groupId = null, bool hasManagerAccess = false, + ProjectType? type = null, int limit = 25, int offset = 0) where TProject : ProjectBase // Project, EnterpriseProject { @@ -117,6 +118,11 @@ public async Task> ListProjects( queryParams.AddParamIfPresent("userId", userId); queryParams.AddParamIfPresent("groupId", groupId); queryParams.Add("hasManagerAccess", hasManagerAccess ? "1" : "0"); + + if (type.HasValue) + { + queryParams.Add("type", ((int) type).ToString()); + } CrowdinApiResult result = await _apiClient.SendGetRequest(BaseProjectsSubUrl, queryParams); diff --git a/src/Crowdin.Api/ProjectsGroups/StringsBasedProjectForm.cs b/src/Crowdin.Api/ProjectsGroups/StringsBasedProjectForm.cs index 95fa950a..0c224544 100644 --- a/src/Crowdin.Api/ProjectsGroups/StringsBasedProjectForm.cs +++ b/src/Crowdin.Api/ProjectsGroups/StringsBasedProjectForm.cs @@ -47,6 +47,9 @@ public class StringsBasedProjectForm : ProjectForm [JsonProperty("isMtAllowed")] public bool? IsMtAllowed { get; set; } + [JsonProperty("taskBasedAccessControl")] + public bool? TaskBasedAccessControl { get; set; } + [JsonProperty("autoSubstitution")] public bool? AutoSubstitution { get; set; } @@ -92,7 +95,19 @@ public class StringsBasedProjectForm : ProjectForm [JsonProperty("languageMapping")] public IDictionary? LanguageMapping { get; set; } + [JsonProperty("glossaryAccess")] + public bool? GlossaryAccess { get; set; } + [JsonProperty("notificationSettings")] public NotificationSettings? NotificationSettings { get; set; } + + [JsonProperty("tmPenalties")] + public bool? TmPenalties { get; set; } + + [JsonProperty("normalizePlaceholder")] + public bool? NormalizePlaceholder { get; set; } + + [JsonProperty("tmContextType")] + public TmContextType? TmContextType { get; set; } } } \ No newline at end of file diff --git a/src/Crowdin.Api/ProjectsGroups/TmContextType.cs b/src/Crowdin.Api/ProjectsGroups/TmContextType.cs new file mode 100644 index 00000000..842a300f --- /dev/null +++ b/src/Crowdin.Api/ProjectsGroups/TmContextType.cs @@ -0,0 +1,19 @@ + +using System.ComponentModel; +using JetBrains.Annotations; + +namespace Crowdin.Api.ProjectsGroups +{ + [PublicAPI] + public enum TmContextType + { + [Description("segmentContext")] + SegmentContext, + + [Description("auto")] + Auto, + + [Description("prevAndNextSegment")] + PrevAndNextSegment + } +} \ No newline at end of file diff --git a/src/Crowdin.Api/ProjectsGroups/WorkflowTemplateStepConfig.cs b/src/Crowdin.Api/ProjectsGroups/WorkflowTemplateStepConfig.cs new file mode 100644 index 00000000..e6ccd2d8 --- /dev/null +++ b/src/Crowdin.Api/ProjectsGroups/WorkflowTemplateStepConfig.cs @@ -0,0 +1,75 @@ + +using System.Collections.Generic; +using JetBrains.Annotations; +using Newtonsoft.Json; + +#nullable enable + +namespace Crowdin.Api.ProjectsGroups +{ + [PublicAPI] + public abstract class WorkflowTemplateStepConfig + { + [PublicAPI] + public class TranslateProofread : WorkflowTemplateStepConfig + { + [JsonProperty("id")] + public int? Id { get; set; } + + [JsonProperty("languages")] + public List? Languages { get; set; } + + [JsonProperty("assignees")] + public List? Assignees { get; set; } + } + + [PublicAPI] + public class ConfigVendor : WorkflowTemplateStepConfig + { + [JsonProperty("id")] + public int? Id { get; set; } + + [JsonProperty("languages")] + public List? Languages { get; set; } + + [JsonProperty("vendorId")] + public int? VendorId { get; set; } + } + + [PublicAPI] + public class TmPreTranslate : WorkflowTemplateStepConfig + { + [JsonProperty("id")] + public int? Id { get; set; } + + [JsonProperty("languages")] + public List? Languages { get; set; } + + [JsonProperty("config")] + public Config? Config { get; set; } + } + + [PublicAPI] + public class MtPreTranslate : WorkflowTemplateStepConfig + { + [JsonProperty("id")] + public int? Id { get; set; } + + [JsonProperty("languages")] + public List? Languages { get; set; } + + [JsonProperty("mtId")] + public int? MtId { get; set; } + } + + [PublicAPI] + public class Config + { + [JsonProperty("minRelevant")] + public int? MinRelevant { get; set; } + + [JsonProperty("autoSubstitution")] + public bool? AutoSubstitution { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Crowdin.Api/Reports/ReportStatus.cs b/src/Crowdin.Api/Reports/ReportStatus.cs index 201c97bb..72d57c38 100644 --- a/src/Crowdin.Api/Reports/ReportStatus.cs +++ b/src/Crowdin.Api/Reports/ReportStatus.cs @@ -1,5 +1,7 @@  using System; +using System.Collections.Generic; + using JetBrains.Annotations; using Newtonsoft.Json; @@ -43,6 +45,9 @@ public class ReportAttributes [JsonProperty("schema")] public object Schema { get; set; } + + [JsonProperty("projectIds")] + public List ProjectIds { get; set; } } } } \ No newline at end of file diff --git a/src/Crowdin.Api/SourceFiles/AddBranchRequest.cs b/src/Crowdin.Api/SourceFiles/AddBranchRequest.cs index 5c6b3207..a8474968 100644 --- a/src/Crowdin.Api/SourceFiles/AddBranchRequest.cs +++ b/src/Crowdin.Api/SourceFiles/AddBranchRequest.cs @@ -1,22 +1,19 @@  +using System; + using JetBrains.Annotations; using Newtonsoft.Json; +using Crowdin.Api.Core; + #nullable enable namespace Crowdin.Api.SourceFiles { [PublicAPI] - public class AddBranchRequest + [Obsolete(MessageTexts.UseBranchesNamespace)] + public class AddBranchRequest : Crowdin.Api.Branches.AddBranchRequest { - [JsonProperty("name")] -#pragma warning disable CS8618 - public string Name { get; set; } -#pragma warning restore CS8618 - - [JsonProperty("title")] - public string? Title { get; set; } - [JsonProperty("exportPattern")] public string? ExportPattern { get; set; } diff --git a/src/Crowdin.Api/SourceFiles/Branch.cs b/src/Crowdin.Api/SourceFiles/Branch.cs index fa728686..2feb2086 100644 --- a/src/Crowdin.Api/SourceFiles/Branch.cs +++ b/src/Crowdin.Api/SourceFiles/Branch.cs @@ -1,35 +1,21 @@  using System; + using JetBrains.Annotations; using Newtonsoft.Json; +using Crowdin.Api.Core; + namespace Crowdin.Api.SourceFiles { [PublicAPI] - public class Branch + [Obsolete(MessageTexts.UseBranchesNamespace)] + public class Branch : Crowdin.Api.Branches.Branch { - [JsonProperty("id")] - public int Id { get; set; } - - [JsonProperty("projectId")] - public int ProjectId { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("title")] - public string Title { get; set; } - [JsonProperty("exportPattern")] public string ExportPattern { get; set; } [JsonProperty("priority")] public Priority Priority { get; set; } - - [JsonProperty("createdAt")] - public DateTimeOffset CreatedAt { get; set; } - - [JsonProperty("updatedAt")] - public DateTimeOffset? UpdatedAt { get; set; } } } \ No newline at end of file diff --git a/src/Crowdin.Api/SourceFiles/BranchPatch.cs b/src/Crowdin.Api/SourceFiles/BranchPatch.cs index 88902962..82624d75 100644 --- a/src/Crowdin.Api/SourceFiles/BranchPatch.cs +++ b/src/Crowdin.Api/SourceFiles/BranchPatch.cs @@ -1,11 +1,16 @@  +using System; using System.ComponentModel; + using JetBrains.Annotations; using Newtonsoft.Json; +using Crowdin.Api.Core; + namespace Crowdin.Api.SourceFiles { [PublicAPI] + [Obsolete(MessageTexts.UseBranchesNamespace)] public class BranchPatch : PatchEntry { [JsonProperty("path")] @@ -13,6 +18,7 @@ public class BranchPatch : PatchEntry } [PublicAPI] + [Obsolete(MessageTexts.UseBranchesNamespace)] public enum BranchPatchPath { [Description("/name")] diff --git a/src/Crowdin.Api/SourceFiles/SourceFilesApiExecutor.cs b/src/Crowdin.Api/SourceFiles/SourceFilesApiExecutor.cs index 1a5bf1e6..d77eecff 100644 --- a/src/Crowdin.Api/SourceFiles/SourceFilesApiExecutor.cs +++ b/src/Crowdin.Api/SourceFiles/SourceFilesApiExecutor.cs @@ -1,11 +1,14 @@  +using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; -using Crowdin.Api.Core; + using JetBrains.Annotations; +using Crowdin.Api.Core; + #nullable enable namespace Crowdin.Api.SourceFiles @@ -35,6 +38,7 @@ public SourceFilesApiExecutor(ICrowdinApiClient apiClient, IJsonParser jsonParse /// Crowdin Enterprise API /// [PublicAPI] + [Obsolete(MessageTexts.UseBranchesNamespace)] public async Task> ListBranches( int projectId, string? name = null, int limit = 25, int offset = 0) { @@ -52,6 +56,7 @@ public async Task> ListBranches( /// Crowdin Enterprise API /// [PublicAPI] + [Obsolete(MessageTexts.UseBranchesNamespace)] public async Task AddBranch(int projectId, AddBranchRequest request) { string url = FormUrl_ProjectBranches(projectId); @@ -65,6 +70,7 @@ public async Task AddBranch(int projectId, AddBranchRequest request) /// Crowdin Enterprise API /// [PublicAPI] + [Obsolete(MessageTexts.UseBranchesNamespace)] public async Task GetBranch(int projectId, int branchId) { string url = FormUrl_ProjectIdBranchId(projectId, branchId); @@ -78,6 +84,7 @@ public async Task GetBranch(int projectId, int branchId) /// Crowdin Enterprise API /// [PublicAPI] + [Obsolete(MessageTexts.UseBranchesNamespace)] public async Task DeleteBranch(int projectId, int branchId) { string url = FormUrl_ProjectIdBranchId(projectId, branchId); @@ -91,6 +98,7 @@ public async Task DeleteBranch(int projectId, int branchId) /// Crowdin Enterprise API /// [PublicAPI] + [Obsolete(MessageTexts.UseBranchesNamespace)] public async Task EditBranch(int projectId, int branchId, IEnumerable patches) { string url = FormUrl_ProjectIdBranchId(projectId, branchId); diff --git a/src/Crowdin.Api/SourceStrings/AddStringRequest.cs b/src/Crowdin.Api/SourceStrings/AddStringRequest.cs index 5d0816bc..a351963c 100644 --- a/src/Crowdin.Api/SourceStrings/AddStringRequest.cs +++ b/src/Crowdin.Api/SourceStrings/AddStringRequest.cs @@ -1,5 +1,4 @@  -using System.Collections; using System.Collections.Generic; using JetBrains.Annotations; using Newtonsoft.Json; @@ -22,6 +21,9 @@ public class AddStringRequest [JsonProperty("fileId")] public int? FileId { get; set; } + [JsonProperty("branchId")] + public int? BranchId { get; set; } + [JsonProperty("context")] public string? Context { get; set; } diff --git a/src/Crowdin.Api/SourceStrings/SourceString.cs b/src/Crowdin.Api/SourceStrings/SourceString.cs index 9eea6eaa..0e6962d1 100644 --- a/src/Crowdin.Api/SourceStrings/SourceString.cs +++ b/src/Crowdin.Api/SourceStrings/SourceString.cs @@ -15,7 +15,7 @@ public class SourceString public int ProjectId { get; set; } [JsonProperty("fileId")] - public int FileId { get; set; } + public int? FileId { get; set; } [JsonProperty("branchId")] public int? BranchId { get; set; } @@ -48,7 +48,7 @@ public class SourceString public int? MasterStringId { get; set; } [JsonProperty("revision")] - public int Revision { get; set; } + public int? Revision { get; set; } [JsonProperty("hasPlurals")] public bool HasPlurals { get; set; } diff --git a/src/Crowdin.Api/SourceStrings/SourceStringsApiExecutor.cs b/src/Crowdin.Api/SourceStrings/SourceStringsApiExecutor.cs index f1dcb64f..d87b257c 100644 --- a/src/Crowdin.Api/SourceStrings/SourceStringsApiExecutor.cs +++ b/src/Crowdin.Api/SourceStrings/SourceStringsApiExecutor.cs @@ -126,6 +126,32 @@ public async Task EditString(int projectId, int stringId, IEnumera CrowdinApiResult result = await _apiClient.SendPatchRequest(url, patches); return _jsonParser.ParseResponseObject(result.JsonObject); } + + /// + /// Upload strings status. Documentation: + /// Crowdin API + /// Crowdin Enterprise API + /// + [PublicAPI] + public async Task UploadStringsStatus(int projectId, string uploadId) + { + string url = FormUrl_StringsUploadId(projectId, uploadId); + CrowdinApiResult result = await _apiClient.SendGetRequest(url); + return _jsonParser.ParseResponseObject(result.JsonObject); + } + + /// + /// Upload strings. Documentation: + /// Crowdin API + /// Crowdin Enterprise API + /// + [PublicAPI] + public async Task UploadStrings(int projectId, UploadStringsRequest request) + { + string url = FormUrl_StringsUpload(projectId); + CrowdinApiResult result = await _apiClient.SendPostRequest(url, request); + return _jsonParser.ParseResponseObject(result.JsonObject); + } #region Helper methods @@ -138,6 +164,16 @@ private static string FormUrl_StringId(int projectId, int stringId) { return $"/projects/{projectId}/strings/{stringId}"; } + + private static string FormUrl_StringsUpload(int projectId) + { + return $"/projects/{projectId}/strings/upload"; + } + + private static string FormUrl_StringsUploadId(int projectId, string uploadId) + { + return $"/projects/{projectId}/strings/uploads/{uploadId}"; + } #endregion } diff --git a/src/Crowdin.Api/SourceStrings/StringBasedProjectFileType.cs b/src/Crowdin.Api/SourceStrings/StringBasedProjectFileType.cs new file mode 100644 index 00000000..00f9adc2 --- /dev/null +++ b/src/Crowdin.Api/SourceStrings/StringBasedProjectFileType.cs @@ -0,0 +1,37 @@ + +using System.ComponentModel; +using JetBrains.Annotations; + +namespace Crowdin.Api.SourceStrings +{ + [PublicAPI] + public enum StringBasedProjectFileType + { + [Description("auto")] + Auto, + + [Description("android")] + Android, + + [Description("macosx")] + MacOsX, + + [Description("arb")] + Arb, + + [Description("csv")] + Csv, + + [Description("json")] + Json, + + [Description("xlsx")] + Xlsx, + + [Description("xliff")] + Xliff, + + [Description("xliff_two")] + XliffTwo + } +} \ No newline at end of file diff --git a/src/Crowdin.Api/SourceStrings/StringUploadResponseModel.cs b/src/Crowdin.Api/SourceStrings/StringUploadResponseModel.cs new file mode 100644 index 00000000..46e328bd --- /dev/null +++ b/src/Crowdin.Api/SourceStrings/StringUploadResponseModel.cs @@ -0,0 +1,64 @@ + +using System; +using JetBrains.Annotations; +using Newtonsoft.Json; +using Crowdin.Api.SourceFiles; + +namespace Crowdin.Api.SourceStrings +{ + [PublicAPI] + public class StringUploadResponseModel + { + [JsonProperty("identifier")] + public string Identifier { get; set; } + + [JsonProperty("status")] + public OperationStatus Status { get; set; } + + [JsonProperty("progress")] + public int Progress { get; set; } + + [JsonProperty("attributes")] + public AttributesData Attributes { get; set; } + + [JsonProperty("createdAt")] + public DateTimeOffset CreatedAt { get; set; } + + [JsonProperty("updatedAt")] + public DateTimeOffset? UpdatedAt { get; set; } + + [JsonProperty("startedAt")] + public DateTimeOffset? StartedAt { get; set; } + + [JsonProperty("finishedAt")] + public DateTimeOffset? FinishedAt { get; set; } + + [PublicAPI] + public class AttributesData + { + [JsonProperty("branchId")] + public int BranchId { get; set; } + + [JsonProperty("storageId")] + public int StorageId { get; set; } + + [JsonProperty("fileType")] + public string FileType { get; set; } + + [JsonProperty("parserVersion")] + public int ParserVersion { get; set; } + + [JsonProperty("labelIds")] + public int[] LabelIds { get; set; } + + [JsonProperty("importOptions")] + public SpreadsheetFileImportOptions ImportOptions { get; set; } + + [JsonProperty("updateStrings")] + public bool UpdateStrings { get; set; } + + [JsonProperty("cleanupMode")] + public bool CleanupMode { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Crowdin.Api/SourceStrings/UploadStringsRequest.cs b/src/Crowdin.Api/SourceStrings/UploadStringsRequest.cs new file mode 100644 index 00000000..1eb2ea56 --- /dev/null +++ b/src/Crowdin.Api/SourceStrings/UploadStringsRequest.cs @@ -0,0 +1,40 @@ + +using System.Collections.Generic; + +using JetBrains.Annotations; +using Newtonsoft.Json; + +using Crowdin.Api.SourceFiles; + +#nullable enable + +namespace Crowdin.Api.SourceStrings +{ + [PublicAPI] + public class UploadStringsRequest + { + [JsonProperty("storageId")] + public int StorageId { get; set; } + + [JsonProperty("branchId")] + public int BranchId { get; set; } + + [JsonProperty("type")] + public StringBasedProjectFileType? Type { get; set; } + + [JsonProperty("parserVersion")] + public int? ParserVersion { get; set; } + + [JsonProperty("labelIds")] + public ICollection? LabelIds { get; set; } + + [JsonProperty("updateStrings")] + public bool? UpdateStrings { get; set; } + + [JsonProperty("cleanupMode")] + public bool? CleanupMode { get; set; } + + [JsonProperty("importOptions")] + public SpreadsheetFileImportOptions? ImportOptions { get; set; } + } +} \ No newline at end of file diff --git a/src/Crowdin.Api/StringTranslations/StringTranslation.cs b/src/Crowdin.Api/StringTranslations/StringTranslation.cs index 3ba255e4..98427012 100644 --- a/src/Crowdin.Api/StringTranslations/StringTranslation.cs +++ b/src/Crowdin.Api/StringTranslations/StringTranslation.cs @@ -23,6 +23,12 @@ public class StringTranslation [JsonProperty("rating")] public int Rating { get; set; } + [JsonProperty("provider")] + public string Provider { get; set; } + + [JsonProperty("isPreTranslated")] + public bool IsPreTranslated { get; set; } + [JsonProperty("createdAt")] public DateTimeOffset CreatedAt { get; set; } } diff --git a/src/Crowdin.Api/Tasks/LanguageServiceTaskCreateForm.cs b/src/Crowdin.Api/Tasks/LanguageServiceTaskCreateForm.cs index 75375314..b409cb3f 100644 --- a/src/Crowdin.Api/Tasks/LanguageServiceTaskCreateForm.cs +++ b/src/Crowdin.Api/Tasks/LanguageServiceTaskCreateForm.cs @@ -1,5 +1,7 @@ -using System; + +using System; using System.Collections.Generic; + using JetBrains.Annotations; using Newtonsoft.Json; @@ -20,10 +22,11 @@ public class LanguageServiceTaskCreateForm : AddTaskRequest public string LanguageId { get; set; } #pragma warning restore CS8618 + [JsonProperty("branchIds")] + public ICollection? BranchIds { get; set; } + [JsonProperty("fileIds")] -#pragma warning disable CS8618 - public ICollection FileIds { get; set; } -#pragma warning restore CS8618 + public ICollection? FileIds { get; set; } [JsonProperty("type")] public TaskType Type { get; set; } diff --git a/src/Crowdin.Api/Tasks/TaskCreateForm.cs b/src/Crowdin.Api/Tasks/TaskCreateForm.cs index 17129f65..69eed09a 100644 --- a/src/Crowdin.Api/Tasks/TaskCreateForm.cs +++ b/src/Crowdin.Api/Tasks/TaskCreateForm.cs @@ -21,10 +21,11 @@ public class TaskCreateForm : AddTaskRequest public string LanguageId { get; set; } #pragma warning restore CS8618 + [JsonProperty("branchIds")] + public ICollection? BranchIds { get; set; } + [JsonProperty("fileIds")] -#pragma warning disable CS8618 - public ICollection FileIds { get; set; } -#pragma warning restore CS8618 + public ICollection? FileIds { get; set; } [JsonProperty("type")] public TaskType Type { get; set; } diff --git a/src/Crowdin.Api/Tasks/VendorGengoTaskCreateForm.cs b/src/Crowdin.Api/Tasks/VendorGengoTaskCreateForm.cs index cae5b4bf..f42324f0 100644 --- a/src/Crowdin.Api/Tasks/VendorGengoTaskCreateForm.cs +++ b/src/Crowdin.Api/Tasks/VendorGengoTaskCreateForm.cs @@ -23,10 +23,11 @@ public class VendorGengoTaskCreateForm : AddTaskRequest public string LanguageId { get; set; } #pragma warning restore CS8618 + [JsonProperty("branchIds")] + public ICollection? BranchIds { get; set; } + [JsonProperty("fileIds")] -#pragma warning disable CS8618 - public ICollection FileIds { get; set; } -#pragma warning restore CS8618 + public ICollection? FileIds { get; set; } [JsonProperty("type")] public TaskType Type { get; set; } diff --git a/src/Crowdin.Api/Tasks/VendorManualTaskCreateForm.cs b/src/Crowdin.Api/Tasks/VendorManualTaskCreateForm.cs index ed3a69b5..7bc68a62 100644 --- a/src/Crowdin.Api/Tasks/VendorManualTaskCreateForm.cs +++ b/src/Crowdin.Api/Tasks/VendorManualTaskCreateForm.cs @@ -19,9 +19,6 @@ public class VendorManualTaskCreateForm : AddTaskRequest [JsonProperty("languageId")] public string LanguageId { get; set; } - [JsonProperty("fileIds")] - public ICollection FileIds { get; set; } - [JsonProperty("type")] public TaskType Type { get; set; } @@ -36,6 +33,12 @@ public class VendorManualTaskCreateForm : AddTaskRequest [JsonProperty("description")] public string? Description { get; set; } + [JsonProperty("branchIds")] + public ICollection? BranchIds { get; set; } + + [JsonProperty("fileIds")] + public ICollection? FileIds { get; set; } + [JsonProperty("skipAssignedStrings")] public bool? SkipAssignedStrings { get; set; } diff --git a/src/Crowdin.Api/Tasks/VendorOhtTaskCreateForm.cs b/src/Crowdin.Api/Tasks/VendorOhtTaskCreateForm.cs index d9cd8f30..53655317 100644 --- a/src/Crowdin.Api/Tasks/VendorOhtTaskCreateForm.cs +++ b/src/Crowdin.Api/Tasks/VendorOhtTaskCreateForm.cs @@ -22,10 +22,11 @@ public class VendorOhtTaskCreateForm : AddTaskRequest public string LanguageId { get; set; } #pragma warning restore CS8618 + [JsonProperty("branchIds")] + public ICollection? BranchIds { get; set; } + [JsonProperty("fileIds")] -#pragma warning disable CS8618 - public ICollection FileIds { get; set; } -#pragma warning restore CS8618 + public ICollection? FileIds { get; set; } [JsonProperty("type")] public TaskType Type { get; set; } diff --git a/src/Crowdin.Api/Tasks/VendorTranslatedTaskCreateForm.cs b/src/Crowdin.Api/Tasks/VendorTranslatedTaskCreateForm.cs index b66ff147..552995fe 100644 --- a/src/Crowdin.Api/Tasks/VendorTranslatedTaskCreateForm.cs +++ b/src/Crowdin.Api/Tasks/VendorTranslatedTaskCreateForm.cs @@ -22,10 +22,11 @@ public class VendorTranslatedTaskCreateForm : AddTaskRequest public string LanguageId { get; set; } #pragma warning restore CS8618 + [JsonProperty("branchIds")] + public ICollection? BranchIds { get; set; } + [JsonProperty("fileIds")] -#pragma warning disable CS8618 - public ICollection FileIds { get; set; } -#pragma warning restore CS8618 + public ICollection? FileIds { get; set; } [JsonProperty("type")] public TaskType Type { get; set; } diff --git a/src/Crowdin.Api/Translations/ApplyPreTranslationRequest.cs b/src/Crowdin.Api/Translations/ApplyPreTranslationRequest.cs index 68bf586e..0806a934 100644 --- a/src/Crowdin.Api/Translations/ApplyPreTranslationRequest.cs +++ b/src/Crowdin.Api/Translations/ApplyPreTranslationRequest.cs @@ -1,4 +1,4 @@ - + using System.Collections.Generic; using JetBrains.Annotations; using Newtonsoft.Json; @@ -13,8 +13,11 @@ public class ApplyPreTranslationRequest [JsonProperty("languageIds")] public ICollection LanguageIds { get; set; } = new List(); + [JsonProperty("branchIds")] + public ICollection? BranchIds { get; set; } + [JsonProperty("fileIds")] - public ICollection FileIds { get; set; } = new List(); + public ICollection? FileIds { get; set; } [JsonProperty("method")] public PreTranslationMethod? Method { get; set; } diff --git a/src/Crowdin.Api/Translations/PreTranslateAttributes.cs b/src/Crowdin.Api/Translations/PreTranslateAttributes.cs index 5565dd5b..329026bb 100644 --- a/src/Crowdin.Api/Translations/PreTranslateAttributes.cs +++ b/src/Crowdin.Api/Translations/PreTranslateAttributes.cs @@ -1,17 +1,23 @@ - + +using System; using JetBrains.Annotations; using Newtonsoft.Json; +#nullable enable + namespace Crowdin.Api.Translations { [PublicAPI] public class PreTranslateAttributes { [JsonProperty("languageIds")] - public string[] LanguageIds { get; set; } + public string[] LanguageIds { get; set; } = Array.Empty(); + + [JsonProperty("branchIds")] + public string[] BranchIds { get; set; } = Array.Empty(); [JsonProperty("fileIds")] - public int[] FileIds { get; set; } + public int[]? FileIds { get; set; } [JsonProperty("method")] public PreTranslationMethod Method { get; set; } diff --git a/src/Crowdin.Api/Translations/UploadTranslationsRequest.cs b/src/Crowdin.Api/Translations/UploadTranslationsRequest.cs index cba941c9..1ea9662b 100644 --- a/src/Crowdin.Api/Translations/UploadTranslationsRequest.cs +++ b/src/Crowdin.Api/Translations/UploadTranslationsRequest.cs @@ -1,4 +1,4 @@ - + using JetBrains.Annotations; using Newtonsoft.Json; @@ -10,8 +10,11 @@ public class UploadTranslationsRequest [JsonProperty("storageId")] public int StorageId { get; set; } + [JsonProperty("branchId")] + public int? BranchId { get; set; } + [JsonProperty("fileId")] - public int FileId { get; set; } + public int? FileId { get; set; } [JsonProperty("importEqSuggestions")] public bool? ImportEqSuggestions { get; set; } diff --git a/src/Crowdin.Api/Translations/UploadTranslationsResponse.cs b/src/Crowdin.Api/Translations/UploadTranslationsResponse.cs index ef1e6796..059e0934 100644 --- a/src/Crowdin.Api/Translations/UploadTranslationsResponse.cs +++ b/src/Crowdin.Api/Translations/UploadTranslationsResponse.cs @@ -17,6 +17,9 @@ public class UploadTranslationsResponse public string LanguageId { get; set; } [JsonProperty("fileId")] - public int FileId { get; set; } + public int? FileId { get; set; } + + [JsonProperty("branchId")] + public int? BranchId { get; set; } } } \ No newline at end of file diff --git a/tests/Crowdin.Api.Tests/Branches/BranchesCrudApiTests.cs b/tests/Crowdin.Api.Tests/Branches/BranchesCrudApiTests.cs new file mode 100644 index 00000000..77431851 --- /dev/null +++ b/tests/Crowdin.Api.Tests/Branches/BranchesCrudApiTests.cs @@ -0,0 +1,182 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +using Moq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; + +using Crowdin.Api.Branches; +using Crowdin.Api.Core; +using Crowdin.Api.Tests.Core; + +namespace Crowdin.Api.Tests.Branches +{ + public class BranchesApiTests + { + private static readonly JsonSerializerSettings JsonSettings = TestUtils.CreateJsonSerializerOptions(); + + [Fact] + public async Task ListBranches() + { + const int projectId = 1; + const string branchName = "name"; + + Mock mockClient = TestUtils.CreateMockClientWithDefaultParser(); + + var url = $"/projects/{projectId}/branches"; + + IDictionary queryParams = TestUtils.CreateQueryParamsFromPaging(); + queryParams.Add("name", branchName); + + mockClient + .Setup(client => client.SendGetRequest(url, queryParams)) + .ReturnsAsync(new CrowdinApiResult + { + StatusCode = HttpStatusCode.OK, + JsonObject = JObject.Parse(Core.Resources.Branches.Response_Common_Multi) + }); + + var executor = new BranchesApiExecutor(mockClient.Object); + ResponseList response = await executor.ListBranches(projectId, branchName); + + Branch? branch = response.Data.FirstOrDefault(); + Assert_Branch(branch); + } + + [Fact] + public async Task AddBranch() + { + const int projectId = 1; + + var request = new AddBranchRequest + { + Name = "develop-master", + Title = "Master branch" + }; + + string actualRequestJson = JsonConvert.SerializeObject(request, JsonSettings); + string expectedRequestJson = TestUtils.CompactJson(Core.Resources.Branches.Request_AddBranch); + Assert.Equal(expectedRequestJson, actualRequestJson); + + Mock mockClient = TestUtils.CreateMockClientWithDefaultParser(); + + var url = $"/projects/{projectId}/branches"; + + mockClient + .Setup(client => client.SendPostRequest(url, request, null)) + .ReturnsAsync(new CrowdinApiResult + { + StatusCode = HttpStatusCode.Created, + JsonObject = JObject.Parse(Core.Resources.Branches.Response_Common_Single) + }); + + var executor = new BranchesApiExecutor(mockClient.Object); + Branch response = await executor.AddBranch(projectId, request); + + Assert_Branch(response); + } + + [Fact] + public async Task GetBranch() + { + const int projectId = 1; + const int branchId = 2; + + Mock mockClient = TestUtils.CreateMockClientWithDefaultParser(); + + var url = $"/projects/{projectId}/branches/{branchId}"; + + mockClient + .Setup(client => client.SendGetRequest(url, null)) + .ReturnsAsync(new CrowdinApiResult + { + StatusCode = HttpStatusCode.OK, + JsonObject = JObject.Parse(Core.Resources.Branches.Response_Common_Single) + }); + + var executor = new BranchesApiExecutor(mockClient.Object); + Branch response = await executor.GetBranch(projectId, branchId); + + Assert_Branch(response); + } + + [Fact] + public async Task DeleteBranch() + { + const int projectId = 1; + const int branchId = 2; + + Mock mockClient = TestUtils.CreateMockClientWithDefaultParser(); + + var url = $"/projects/{projectId}/branches/{branchId}"; + + mockClient + .Setup(client => client.SendDeleteRequest(url, null)) + .ReturnsAsync(HttpStatusCode.NoContent); + + var executor = new BranchesApiExecutor(mockClient.Object); + await executor.DeleteBranch(projectId, branchId); + } + + [Fact] + public async Task EditBranch() + { + const int projectId = 1; + const int branchId = 2; + + var patches = new[] + { + new BranchPatch + { + Operation = PatchOperation.Replace, + Path = BranchPatchPath.Name, + Value = "develop-master" + }, + new BranchPatch + { + Operation = PatchOperation.Replace, + Path = BranchPatchPath.Title, + Value = "Master branch" + } + }; + + string actualRequestJson = JsonConvert.SerializeObject(patches, JsonSettings); + string expectedRequestJson = TestUtils.CompactJson(Core.Resources.Branches.Request_EditBranch); + Assert.Equal(expectedRequestJson, actualRequestJson); + + Mock mockClient = TestUtils.CreateMockClientWithDefaultParser(); + + var url = $"/projects/{projectId}/branches/{branchId}"; + + mockClient + .Setup(client => client.SendPatchRequest(url, patches, null)) + .ReturnsAsync(new CrowdinApiResult + { + StatusCode = HttpStatusCode.OK, + JsonObject = JObject.Parse(Core.Resources.Branches.Response_Common_Single) + }); + + var executor = new BranchesApiExecutor(mockClient.Object); + Branch response = await executor.EditBranch(projectId, branchId, patches); + + Assert_Branch(response); + } + + private static void Assert_Branch(Branch? branch) + { + ArgumentNullException.ThrowIfNull(branch); + + Assert.Equal(34, branch.Id); + Assert.Equal(2, branch.ProjectId); + Assert.Equal("develop-master", branch.Name); + Assert.Equal("Master branch", branch.Title); + Assert.Equal(DateTimeOffset.Parse("2019-09-16T13:48:04+00:00"), branch.CreatedAt); + Assert.Equal(DateTimeOffset.Parse("2019-09-19T13:25:27+00:00"), branch.UpdatedAt); + } + } +} \ No newline at end of file diff --git a/tests/Crowdin.Api.Tests/Branches/BranchesOperationsApiTests.cs b/tests/Crowdin.Api.Tests/Branches/BranchesOperationsApiTests.cs new file mode 100644 index 00000000..af76fd58 --- /dev/null +++ b/tests/Crowdin.Api.Tests/Branches/BranchesOperationsApiTests.cs @@ -0,0 +1,255 @@ + +using System; +using System.Net; +using System.Threading.Tasks; + +using Moq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; + +using Crowdin.Api.Branches; +using Crowdin.Api.Core; +using Crowdin.Api.Tests.Core; + +namespace Crowdin.Api.Tests.Branches +{ + public class BranchesOperationsApiTests + { + private static readonly JsonSerializerSettings JsonSettings = TestUtils.CreateJsonSerializerOptions(); + + [Fact] + public async Task GetClonedBranch() + { + const int projectId = 1; + const int branchId = 2; + const string cloneId = "id"; + + Mock mockClient = TestUtils.CreateMockClientWithDefaultParser(); + + var url = $"/projects/{projectId}/branches/{branchId}/clones/{cloneId}/branch"; + + mockClient + .Setup(client => client.SendGetRequest(url, null)) + .ReturnsAsync(new CrowdinApiResult + { + StatusCode = HttpStatusCode.OK, + JsonObject = JObject.Parse(Core.Resources.Branches.Response_Common_Single) + }); + + var executor = new BranchesApiExecutor(mockClient.Object); + Branch response = await executor.GetClonedBranch(projectId, branchId, cloneId); + + Assert_Branch(response); + } + + [Fact] + public async Task CloneBranch() + { + const int projectId = 1; + const int branchId = 2; + + var request = new CloneBranchRequest + { + Name = "develop-master", + Title = "Master branch" + }; + + string actualRequestJson = JsonConvert.SerializeObject(request, JsonSettings); + string expectedRequestJson = TestUtils.CompactJson(Core.Resources.Branches.Request_CloneBranch); + Assert.Equal(expectedRequestJson, actualRequestJson); + + Mock mockClient = TestUtils.CreateMockClientWithDefaultParser(); + + var url = $"/projects/{projectId}/branches/{branchId}/clones"; + + mockClient + .Setup(client => client.SendPostRequest(url, request, null)) + .ReturnsAsync(new CrowdinApiResult + { + StatusCode = HttpStatusCode.Accepted, + JsonObject = JObject.Parse(Core.Resources.Branches.Response_BranchCloneStatus) + }); + + var executor = new BranchesApiExecutor(mockClient.Object); + BranchCloneStatus response = await executor.CloneBranch(projectId, branchId, request); + + Assert_BranchCloneStatus(response); + } + + [Fact] + public async Task CheckBranchCloneStatus() + { + const int projectId = 1; + const int branchId = 2; + const string cloneId = "id"; + + Mock mockClient = TestUtils.CreateMockClientWithDefaultParser(); + + var url = $"/projects/{projectId}/branches/{branchId}/clones/{cloneId}"; + + mockClient + .Setup(client => client.SendGetRequest(url, null)) + .ReturnsAsync(new CrowdinApiResult + { + StatusCode = HttpStatusCode.OK, + JsonObject = JObject.Parse(Core.Resources.Branches.Response_BranchCloneStatus) + }); + + var executor = new BranchesApiExecutor(mockClient.Object); + BranchCloneStatus response = await executor.CheckBranchCloneStatus(projectId, branchId, cloneId); + + Assert_BranchCloneStatus(response); + } + + [Fact] + public async Task MergeBranch() + { + const int projectId = 1; + const int branchId = 2; + + var request = new MergeBranchRequest + { + DeleteAfterMerge = true, + SourceBranchId = 8, + DryRun = true + }; + + string actualRequestJson = JsonConvert.SerializeObject(request, JsonSettings); + string expectedRequestJson = TestUtils.CompactJson(Core.Resources.Branches.Request_MergeBranch); + Assert.Equal(expectedRequestJson, actualRequestJson); + + Mock mockClient = TestUtils.CreateMockClientWithDefaultParser(); + + var url = $"/projects/{projectId}/branches/{branchId}/merges"; + + mockClient + .Setup(client => client.SendPostRequest(url, request, null)) + .ReturnsAsync(new CrowdinApiResult + { + StatusCode = HttpStatusCode.Accepted, + JsonObject = JObject.Parse(Core.Resources.Branches.Response_BranchMergeStatus) + }); + + var executor = new BranchesApiExecutor(mockClient.Object); + BranchMergeStatus response = await executor.MergeBranch(projectId, branchId, request); + + Assert_BranchMergeStatus(response); + } + + [Fact] + public async Task CheckBranchMergeStatus() + { + const int projectId = 1; + const int branchId = 2; + const string mergeId = "id"; + + Mock mockClient = TestUtils.CreateMockClientWithDefaultParser(); + + var url = $"/projects/{projectId}/branches/{branchId}/merges/{mergeId}"; + + mockClient + .Setup(client => client.SendGetRequest(url, null)) + .ReturnsAsync(new CrowdinApiResult + { + StatusCode = HttpStatusCode.OK, + JsonObject = JObject.Parse(Core.Resources.Branches.Response_BranchMergeStatus) + }); + + var executor = new BranchesApiExecutor(mockClient.Object); + BranchMergeStatus response = await executor.CheckBranchMergeStatus(projectId, branchId, mergeId); + + Assert_BranchMergeStatus(response); + } + + [Fact] + public async Task GetBranchMergeSummary() + { + const int projectId = 1; + const int branchId = 2; + const string mergeId = "id"; + + Mock mockClient = TestUtils.CreateMockClientWithDefaultParser(); + + var url = $"/projects/{projectId}/branches/{branchId}/merges/{mergeId}/summary"; + + mockClient + .Setup(client => client.SendGetRequest(url, null)) + .ReturnsAsync(new CrowdinApiResult + { + StatusCode = HttpStatusCode.OK, + JsonObject = JObject.Parse(Core.Resources.Branches.Response_BranchMergeSummary) + }); + + var executor = new BranchesApiExecutor(mockClient.Object); + BranchMergeSummary response = await executor.GetBranchMergeSummary(projectId, branchId, mergeId); + + Assert_BranchMergeSummary(response); + } + + private static void Assert_Branch(Branch? branch) + { + ArgumentNullException.ThrowIfNull(branch); + + Assert.Equal(34, branch.Id); + Assert.Equal(2, branch.ProjectId); + Assert.Equal("develop-master", branch.Name); + Assert.Equal("Master branch", branch.Title); + Assert.Equal(DateTimeOffset.Parse("2019-09-16T13:48:04+00:00"), branch.CreatedAt); + Assert.Equal(DateTimeOffset.Parse("2019-09-19T13:25:27+00:00"), branch.UpdatedAt); + } + + private static void Assert_BranchCloneStatus(BranchCloneStatus? status) + { + ArgumentNullException.ThrowIfNull(status); + + Assert.Equal("50fb3506-4127-4ba8-8296-f97dc7e3e0c3", status.Identifier); + Assert.Equal(OperationStatus.Finished, status.Status); + Assert.Equal(100, status.Progress); + Assert.NotNull(status.Attributes); + + DateTimeOffset date = DateTimeOffset.Parse("2019-09-23T11:26:54+00:00"); + Assert.Equal(date, status.CreatedAt); + Assert.Equal(date, status.UpdatedAt); + Assert.Equal(date, status.StartedAt); + Assert.Equal(date, status.FinishedAt); + } + + private static void Assert_BranchMergeStatus(BranchMergeStatus? status) + { + ArgumentNullException.ThrowIfNull(status); + + Assert.Equal("50fb3506-4127-4ba8-8296-f97dc7e3e0c3", status.Identifier); + Assert.Equal(OperationStatus.Finished, status.Status); + Assert.Equal(100, status.Progress); + + BranchMergeStatus.AttributesData? attributes = status.Attributes; + ArgumentNullException.ThrowIfNull(status.Attributes); + Assert.Equal(38, attributes.SourceBranchId); + Assert.False(attributes.DeleteAfterMerge); + + DateTimeOffset date = DateTimeOffset.Parse("2019-09-23T11:26:54+00:00"); + Assert.Equal(date, status.CreatedAt); + Assert.Equal(date, status.UpdatedAt); + Assert.Equal(date, status.StartedAt); + Assert.Equal(date, status.FinishedAt); + } + + private static void Assert_BranchMergeSummary(BranchMergeSummary? summary) + { + ArgumentNullException.ThrowIfNull(summary); + + Assert.Equal(BranchMergeStatusId.Merged, summary.Status); + Assert.Equal(100, summary.SourceBranchId); + Assert.Equal(100, summary.TargetBranchId); + Assert.False(summary.DryRun); + + BranchMergeSummary.DetailsData? details = summary.Details; + ArgumentNullException.ThrowIfNull(details); + Assert.Equal(1, details.Added); + Assert.Equal(2, details.Deleted); + Assert.Equal(3, details.Updated); + Assert.Equal(7, details.Conflicted); + } + } +} \ No newline at end of file diff --git a/tests/Crowdin.Api.Tests/Core/Resources/Branches.Designer.cs b/tests/Crowdin.Api.Tests/Core/Resources/Branches.Designer.cs new file mode 100644 index 00000000..405ee77c --- /dev/null +++ b/tests/Crowdin.Api.Tests/Core/Resources/Branches.Designer.cs @@ -0,0 +1,102 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Crowdin.Api.Tests.Core.Resources { + using System; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Branches { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Branches() { + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { + get { + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("Crowdin.Api.Tests.Core.Resources.Branches", typeof(Branches).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + internal static string Response_Common_Multi { + get { + return ResourceManager.GetString("Response_Common_Multi", resourceCulture); + } + } + + internal static string Request_AddBranch { + get { + return ResourceManager.GetString("Request_AddBranch", resourceCulture); + } + } + + internal static string Response_Common_Single { + get { + return ResourceManager.GetString("Response_Common_Single", resourceCulture); + } + } + + internal static string Request_EditBranch { + get { + return ResourceManager.GetString("Request_EditBranch", resourceCulture); + } + } + + internal static string Request_CloneBranch { + get { + return ResourceManager.GetString("Request_CloneBranch", resourceCulture); + } + } + + internal static string Response_BranchCloneStatus { + get { + return ResourceManager.GetString("Response_BranchCloneStatus", resourceCulture); + } + } + + internal static string Request_MergeBranch { + get { + return ResourceManager.GetString("Request_MergeBranch", resourceCulture); + } + } + + internal static string Response_BranchMergeStatus { + get { + return ResourceManager.GetString("Response_BranchMergeStatus", resourceCulture); + } + } + + internal static string Response_BranchMergeSummary { + get { + return ResourceManager.GetString("Response_BranchMergeSummary", resourceCulture); + } + } + } +} diff --git a/tests/Crowdin.Api.Tests/Core/Resources/Branches.resx b/tests/Crowdin.Api.Tests/Core/Resources/Branches.resx new file mode 100644 index 00000000..15b9a8d4 --- /dev/null +++ b/tests/Crowdin.Api.Tests/Core/Resources/Branches.resx @@ -0,0 +1,133 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + { + "data": [ + { + "data": { + "id": 34, + "projectId": 2, + "name": "develop-master", + "title": "Master branch", + "createdAt": "2019-09-16T13:48:04+00:00", + "updatedAt": "2019-09-19T13:25:27+00:00" + } + } + ], + "pagination": { + "offset": 0, + "limit": 25 + } +} + + + { + "name": "develop-master", + "title": "Master branch" +} + + + { + "data": { + "id": 34, + "projectId": 2, + "name": "develop-master", + "title": "Master branch", + "createdAt": "2019-09-16T13:48:04+00:00", + "updatedAt": "2019-09-19T13:25:27+00:00" + } +} + + + [ + { + "path": "/name", + "op": "replace", + "value": "develop-master" + }, + { + "path": "/title", + "op": "replace", + "value": "Master branch" + } +] + + + { + "name": "develop-master", + "title": "Master branch" +} + + + { + "data": { + "identifier": "50fb3506-4127-4ba8-8296-f97dc7e3e0c3", + "status": "finished", + "progress": 100, + "attributes": {}, + "createdAt": "2019-09-23T11:26:54+00:00", + "updatedAt": "2019-09-23T11:26:54+00:00", + "startedAt": "2019-09-23T11:26:54+00:00", + "finishedAt": "2019-09-23T11:26:54+00:00" + } +} + + + { + "deleteAfterMerge": true, + "sourceBranchId": 8, + "dryRun": true +} + + + { + "data": { + "identifier": "50fb3506-4127-4ba8-8296-f97dc7e3e0c3", + "status": "finished", + "progress": 100, + "attributes": { + "sourceBranchId": 38, + "deleteAfterMerge": false + }, + "createdAt": "2019-09-23T11:26:54+00:00", + "updatedAt": "2019-09-23T11:26:54+00:00", + "startedAt": "2019-09-23T11:26:54+00:00", + "finishedAt": "2019-09-23T11:26:54+00:00" + } +} + + + { + "data": { + "status": "merged", + "sourceBranchId": 100, + "targetBranchId": 100, + "dryRun": false, + "details": { + "added": 1, + "deleted": 2, + "updated": 3, + "conflicted": 7 + } + } +} + + \ No newline at end of file diff --git a/tests/Crowdin.Api.Tests/Core/Resources/SourceStrings.Designer.cs b/tests/Crowdin.Api.Tests/Core/Resources/SourceStrings.Designer.cs index 13668b95..0618a243 100644 --- a/tests/Crowdin.Api.Tests/Core/Resources/SourceStrings.Designer.cs +++ b/tests/Crowdin.Api.Tests/Core/Resources/SourceStrings.Designer.cs @@ -87,6 +87,36 @@ internal static string CommonResponses_SingleStringInArray { } } + /// + /// Looks up a localized string similar to { + /// "data": { + /// "identifier": "50fb3506-4127-4ba8-8296-f97dc7e3e0c3", + /// "status": "finished", + /// "progress": 100, + /// "attributes": { + /// "branchId": 38, + /// "storageId": 38, + /// "fileType": "android", + /// "parserVersion": 8, + /// "labelIds": [ + /// 1, + /// 2 + /// ], + /// "importOptions": { + /// "firstLineContainsHeader": false, + /// "importTranslations": true, + /// "scheme": { + /// "identifier": 0, + /// "sourcePhrase": 1, + /// "en": 2, + /// [rest of string was truncated]";. + /// + internal static string CommonResponses_UploadStrings { + get { + return ResourceManager.GetString("CommonResponses_UploadStrings", resourceCulture); + } + } + /// /// Looks up a localized string similar to [ /// { @@ -178,5 +208,34 @@ internal static string StringBatchOperations_Response_NoPagination { return ResourceManager.GetString("StringBatchOperations_Response_NoPagination", resourceCulture); } } + + /// + /// Looks up a localized string similar to { + /// "storageId": 61, + /// "branchId": 34, + /// "type": "xliff", + /// "parserVersion": 1, + /// "labelIds": [ + /// 1, 2, 3 + /// ], + /// "updateStrings": true, + /// "cleanupMode": true, + /// "importOptions": { + /// "firstLineContainsHeader": false, + /// "importTranslations": true, + /// "scheme": { + /// "identifier": 0, + /// "sourcePhrase": 1, + /// "en": 2, + /// "de": 3 + /// } + /// } + ///}. + /// + internal static string UploadStrings_Request { + get { + return ResourceManager.GetString("UploadStrings_Request", resourceCulture); + } + } } } diff --git a/tests/Crowdin.Api.Tests/Core/Resources/SourceStrings.resx b/tests/Crowdin.Api.Tests/Core/Resources/SourceStrings.resx index 91f7cc7d..593b0880 100644 --- a/tests/Crowdin.Api.Tests/Core/Resources/SourceStrings.resx +++ b/tests/Crowdin.Api.Tests/Core/Resources/SourceStrings.resx @@ -149,6 +149,64 @@ } } ] +} + + + { + "data": { + "identifier": "50fb3506-4127-4ba8-8296-f97dc7e3e0c3", + "status": "finished", + "progress": 100, + "attributes": { + "branchId": 38, + "storageId": 38, + "fileType": "android", + "parserVersion": 8, + "labelIds": [ + 1, + 2 + ], + "importOptions": { + "firstLineContainsHeader": false, + "importTranslations": true, + "scheme": { + "identifier": 0, + "sourcePhrase": 1, + "en": 2, + "de": 3 + } + }, + "updateStrings": false, + "cleanupMode": false + }, + "createdAt": "2019-09-23T11:26:54+00:00", + "updatedAt": "2019-09-23T11:26:54+00:00", + "startedAt": "2019-09-23T11:26:54+00:00", + "finishedAt": "2019-09-23T11:26:54+00:00" + } +} + + + { + "storageId": 61, + "branchId": 34, + "type": "xliff", + "parserVersion": 1, + "labelIds": [ + 1, 2, 3 + ], + "updateStrings": true, + "cleanupMode": true, + "importOptions": { + "firstLineContainsHeader": false, + "importTranslations": true, + "scheme": { + "identifier": 0, + "sourcePhrase": 1, + "en": 2, + "de": 3 + } + } } \ No newline at end of file diff --git a/tests/Crowdin.Api.Tests/Core/Resources/Tasks.Designer.cs b/tests/Crowdin.Api.Tests/Core/Resources/Tasks.Designer.cs index 8db497e4..c56b05ea 100644 --- a/tests/Crowdin.Api.Tests/Core/Resources/Tasks.Designer.cs +++ b/tests/Crowdin.Api.Tests/Core/Resources/Tasks.Designer.cs @@ -90,13 +90,14 @@ internal static string AddTask_RightRequestJson_VendorGengo_ToneOther { /// Looks up a localized string similar to { /// "title": "My task", /// "languageId": "es", - /// "fileIds": [ - /// 1,2,3 - /// ], /// "type": 2, /// "vendor": "lingo24", /// "status": "in_progress", /// "description": "My amazing task", + /// + /// "fileIds": [ + /// 1,2,3 + /// ], /// "skipAssignedStrings": true, /// "skipUntranslatedStrings": true, /// "labelIds": [ diff --git a/tests/Crowdin.Api.Tests/Core/Resources/Tasks.resx b/tests/Crowdin.Api.Tests/Core/Resources/Tasks.resx index 125217ea..d0c13d47 100644 --- a/tests/Crowdin.Api.Tests/Core/Resources/Tasks.resx +++ b/tests/Crowdin.Api.Tests/Core/Resources/Tasks.resx @@ -31,13 +31,14 @@ { "title": "My task", "languageId": "es", - "fileIds": [ - 1,2,3 - ], "type": 2, "vendor": "lingo24", "status": "in_progress", "description": "My amazing task", + + "fileIds": [ + 1,2,3 + ], "skipAssignedStrings": true, "skipUntranslatedStrings": true, "labelIds": [ diff --git a/tests/Crowdin.Api.Tests/Crowdin.Api.Tests.csproj b/tests/Crowdin.Api.Tests/Crowdin.Api.Tests.csproj index 7c94659b..15459904 100644 --- a/tests/Crowdin.Api.Tests/Crowdin.Api.Tests.csproj +++ b/tests/Crowdin.Api.Tests/Crowdin.Api.Tests.csproj @@ -198,6 +198,26 @@ ResXFileCodeGenerator AI_Misc.Designer.cs + + ResXFileCodeGenerator + Branches.Designer.cs + + + ResXFileCodeGenerator + Branches.Designer.cs + + + ResXFileCodeGenerator + AI.Designer.cs + + + ResXFileCodeGenerator + AI_Providers.Designer.cs + + + ResXFileCodeGenerator + AI_Misc.Designer.cs + @@ -391,6 +411,11 @@ True Fields.resx + + True + True + Branches.resx + True True @@ -406,6 +431,26 @@ True AI_Misc.resx + + True + True + AI_Prompts.resx + + + True + True + AI_Providers.resx + + + True + True + AI_Misc.resx + + + True + True + Branches.resx + diff --git a/tests/Crowdin.Api.Tests/SourceStrings/SourceStringsApiTests.cs b/tests/Crowdin.Api.Tests/SourceStrings/SourceStringsApiTests.cs index 3fae33a3..f1edb307 100644 --- a/tests/Crowdin.Api.Tests/SourceStrings/SourceStringsApiTests.cs +++ b/tests/Crowdin.Api.Tests/SourceStrings/SourceStringsApiTests.cs @@ -1,12 +1,25 @@  +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; + +using Moq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; + +using Crowdin.Api.Core; +using Crowdin.Api.SourceFiles; using Crowdin.Api.SourceStrings; using Crowdin.Api.Tests.Core; -using Xunit; namespace Crowdin.Api.Tests.SourceStrings { public class SourceStringsApiTests { + private static readonly JsonSerializerSettings DefaultSettings = TestUtils.CreateJsonSerializerOptions(); + [Fact] public void ListStrings_QueryStringConstruction() { @@ -17,7 +30,121 @@ public void ListStrings_QueryStringConstruction() Scope = StringScope.Context }; - Assert.Equal(expectedQueryString, @params.ToQueryParams().ToQueryString()); + Assert.Equal(expectedQueryString, TestUtils.ToQueryString(@params.ToQueryParams())); + } + + [Fact] + public async Task UploadStringsStatus() + { + const int projectId = 1; + const string uploadId = "50fb3506-4127-4ba8-8296-f97dc7e3e0c3"; + + Mock mockClient = TestUtils.CreateMockClientWithDefaultParser(); + + var url = $"/projects/{projectId}/strings/uploads/{uploadId}"; + + mockClient + .Setup(client => client.SendGetRequest(url, null)) + .ReturnsAsync(new CrowdinApiResult + { + StatusCode = HttpStatusCode.OK, + JsonObject = JObject.Parse(Core.Resources.SourceStrings.CommonResponses_UploadStrings) + }); + + var executor = new SourceStringsApiExecutor(mockClient.Object); + StringUploadResponseModel response = await executor.UploadStringsStatus(projectId, uploadId); + + Assert_StringUploadResponseModel(response); + } + + [Fact] + public async Task UploadStrings() + { + const int projectId = 1; + + var request = new UploadStringsRequest + { + StorageId = 61, + BranchId = 34, + Type = StringBasedProjectFileType.Xliff, + ParserVersion = 1, + LabelIds = new[] { 1, 2, 3 }, + UpdateStrings = true, + CleanupMode = true, + ImportOptions = new SpreadsheetFileImportOptions + { + FirstLineContainsHeader = false, + ImportTranslations = true, + Scheme = new Dictionary + { + [ColumnType.Identifier] = 0, + [ColumnType.SourcePhrase] = 1, + ["en"] = 2, + ["de"] = 3 + } + } + }; + + string actualRequestJson = JsonConvert.SerializeObject(request, DefaultSettings); + string expectedRequestJson = TestUtils.CompactJson(Core.Resources.SourceStrings.UploadStrings_Request); + Assert.Equal(expectedRequestJson, actualRequestJson); + + Mock mockClient = TestUtils.CreateMockClientWithDefaultParser(); + + var url = $"/projects/{projectId}/strings/upload"; + + mockClient + .Setup(client => client.SendPostRequest(url, request, null)) + .ReturnsAsync(new CrowdinApiResult + { + StatusCode = HttpStatusCode.OK, + JsonObject = JObject.Parse(Core.Resources.SourceStrings.CommonResponses_UploadStrings) + }); + + var executor = new SourceStringsApiExecutor(mockClient.Object); + StringUploadResponseModel response = await executor.UploadStrings(projectId, request); + + Assert_StringUploadResponseModel(response); + } + + private static void Assert_StringUploadResponseModel(StringUploadResponseModel? response) + { + Assert.NotNull(response); + ArgumentNullException.ThrowIfNull(response); + + Assert.Equal("50fb3506-4127-4ba8-8296-f97dc7e3e0c3", response.Identifier); + Assert.Equal(OperationStatus.Finished, response.Status); + Assert.Equal(100, response.Progress); + + DateTimeOffset date = DateTimeOffset.Parse("2019-09-23T11:26:54+00:00"); + Assert.Equal(date, response.CreatedAt); + Assert.Equal(date, response.UpdatedAt); + Assert.Equal(date, response.StartedAt); + Assert.Equal(date, response.FinishedAt); + + StringUploadResponseModel.AttributesData? attributes = response.Attributes; + Assert.NotNull(attributes); + + Assert.Equal(38, attributes.BranchId); + Assert.Equal(38, attributes.StorageId); + Assert.Equal("android", attributes.FileType); + Assert.Equal(8, attributes.ParserVersion); + Assert.Equal(new[] { 1, 2 }, attributes.LabelIds); + Assert.False(attributes.UpdateStrings); + Assert.False(attributes.CleanupMode); + + SpreadsheetFileImportOptions? importOptions = attributes.ImportOptions; + Assert.NotNull(importOptions); + + Assert.False(importOptions.FirstLineContainsHeader); + Assert.True(importOptions.ImportTranslations); + Assert.Equal(new Dictionary + { + [ColumnType.Identifier] = 0, + [ColumnType.SourcePhrase] = 1, + ["en"] = 2, + ["de"] = 3 + }, importOptions.Scheme); } } } \ No newline at end of file