diff --git a/Migrators/Importer/Client/Client.cs b/Migrators/Importer/Client/Client.cs index 7e7ba2d..d2ff402 100644 --- a/Migrators/Importer/Client/Client.cs +++ b/Migrators/Importer/Client/Client.cs @@ -2,6 +2,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Models; +using System; +using System.Xml.Linq; using TestIT.ApiClient.Api; using TestIT.ApiClient.Client; using TestIT.ApiClient.Model; @@ -15,13 +17,15 @@ public class Client : IClient private readonly ILogger _logger; private readonly AttachmentsApi _attachments; private readonly ProjectsApi _projectsApi; + private readonly ProjectAttributesApi _projectAttributesApi; private readonly ProjectSectionsApi _projectSectionsApi; private readonly SectionsApi _sectionsApi; private readonly CustomAttributesApi _customAttributes; private readonly WorkItemsApi _workItemsApi; private readonly CustomAttributesApi _customAttributesApi; private readonly ParametersApi _parametersApi; - private bool _importToExistingProject = false; + private readonly bool _importToExistingProject; + private readonly string _projectName; private const int TenMinutes = 60000; @@ -49,6 +53,13 @@ public Client(ILogger logger, IConfiguration configuration) certValidation = bool.Parse(certValidationStr); } + _projectName = tmsSection["projectName"]; + if (!string.IsNullOrEmpty(_projectName)) + { + _logger.LogInformation("Import by custom project name {Name}", _projectName); + } + + _importToExistingProject = false; var importToExistingProjectStr = tmsSection["importToExistingProject"]; if (!string.IsNullOrEmpty(importToExistingProjectStr)) { @@ -64,6 +75,7 @@ public Client(ILogger logger, IConfiguration configuration) _attachments = new AttachmentsApi(new HttpClient(), cfg, httpClientHandler); _projectsApi = new ProjectsApi(new HttpClient(), cfg, httpClientHandler); + _projectAttributesApi = new ProjectAttributesApi(new HttpClient(), cfg, httpClientHandler); _projectSectionsApi = new ProjectSectionsApi(new HttpClient(), cfg, httpClientHandler); _sectionsApi = new SectionsApi(new HttpClient(), cfg, httpClientHandler); _customAttributes = new CustomAttributesApi(new HttpClient(), cfg, httpClientHandler); @@ -74,6 +86,11 @@ public Client(ILogger logger, IConfiguration configuration) public async Task GetProject(string name) { + if (!string.IsNullOrEmpty(_projectName)) + { + name = _projectName; + } + _logger.LogInformation("Getting project {Name}", name); try @@ -111,6 +128,11 @@ public async Task GetProject(string name) public async Task CreateProject(string name) { + if (!string.IsNullOrEmpty(_projectName)) + { + name = _projectName; + } + _logger.LogInformation("Creating project {Name}", name); try @@ -372,7 +394,7 @@ public async Task ImportTestCase(Guid projectId, Guid parentSectionId, TmsTestCa WorkItemId = s.SharedStepId, TestData = s.TestData }).ToList(), - Attributes = testCase.Attributes.Where(x => x.Value != null) + Attributes = testCase.Attributes .ToDictionary(keySelector: a => a.Id.ToString(), elementSelector: a => (object)a.Value), Tags = testCase.Tags.Select(t => new TagPostModel(t)).ToList(), @@ -463,6 +485,89 @@ public async Task> GetProjectAttributes() } } + public async Task> GetRequiredProjectAttributesByProjectId(Guid projectId) + { + _logger.LogInformation("Getting required project attributes by project id {Id}", projectId); + + try + { + var attributes = await _projectAttributesApi.SearchAttributesInProjectAsync( + projectId: projectId.ToString(), searchAttributesInProjectRequest: new SearchAttributesInProjectRequest( + name: "", + isRequired: true, + types: new List() + { + CustomAttributeTypesEnum.String, + CustomAttributeTypesEnum.Options, + CustomAttributeTypesEnum.MultipleOptions, + CustomAttributeTypesEnum.User, + CustomAttributeTypesEnum.Datetime + } + )); + + var requiredAttributes = attributes + .Select(a => new TmsAttribute + { + Id = a.Id, + Name = a.Name, + Type = a.Type.ToString(), + IsEnabled = a.IsEnabled, + IsRequired = a.IsRequired, + IsGlobal = a.IsGlobal, + Options = a.Options.Select(o => new TmsAttributeOptions + { + Id = o.Id, + Value = o.Value, + IsDefault = o.IsDefault + }).ToList() + }).ToList(); + + _logger.LogDebug("Got required project attributes by project id {id}: {@Attributes}", projectId, requiredAttributes); + + return requiredAttributes; + } + catch (Exception e) + { + _logger.LogError("Could not get required project attributes by project id {Id}: {Message}", projectId, e.Message); + throw; + } + } + + public async Task GetProjectAttributeById(Guid id) + { + _logger.LogInformation("Getting project attribute by id {Id}", id); + + try + { + var attribute = await _customAttributes.ApiV2CustomAttributesIdGetAsync(id); + + var customAttribute = new TmsAttribute + { + Id = attribute.Id, + Name = attribute.Name, + Type = attribute.Type.ToString(), + IsEnabled = attribute.IsEnabled, + IsRequired = attribute.IsRequired, + IsGlobal = attribute.IsGlobal, + Options = attribute.Options.Select(o => new TmsAttributeOptions + { + Id = o.Id, + Value = o.Value, + IsDefault = o.IsDefault + }).ToList() + }; + + _logger.LogDebug("Got project attribute by id {id}: {@Attribute}", id, customAttribute); + + return customAttribute; + } + catch (Exception e) + { + _logger.LogError("Could not get project attribute by id {Id}: {Message}", id, e.Message); + throw; + } + } + public async Task AddAttributesToProject(Guid projectId, IEnumerable attributeIds) { _logger.LogInformation("Adding attributes to project"); @@ -520,6 +625,37 @@ public async Task UpdateAttribute(TmsAttribute attribute) } } + public async Task UpdateProjectAttribute(Guid projectId, TmsAttribute attribute) + { + _logger.LogInformation("Updating project attribute {Name}", attribute.Name); + + try + { + var model = new UpdateProjectsAttributeRequest(id: attribute.Id, name: attribute.Name) + { + IsEnabled = attribute.IsEnabled, + IsRequired = attribute.IsRequired, + Options = attribute.Options.Select(o => new CustomAttributeOptionModel() + { + Id = o.Id, + Value = o.Value, + IsDefault = o.IsDefault + }).ToList() + }; + + _logger.LogDebug("Updating attribute {@Model}", model); + + await _projectAttributesApi.UpdateProjectsAttributeAsync( + projectId: projectId.ToString(), updateProjectsAttributeRequest: model); + } + + catch (Exception e) + { + _logger.LogError("Could not update attribute {Name}: {Message}", attribute.Name, e.Message); + throw; + } + } + public async Task UploadAttachment(string fileName, Stream content) { _logger.LogDebug("Uploading attachment {Name}", fileName); diff --git a/Migrators/Importer/Client/IClient.cs b/Migrators/Importer/Client/IClient.cs index 1dcbe51..6f3e1af 100644 --- a/Migrators/Importer/Client/IClient.cs +++ b/Migrators/Importer/Client/IClient.cs @@ -15,8 +15,11 @@ public interface IClient Task ImportTestCase(Guid projectId, Guid parentSectionId, TmsTestCase testCase); Task GetRootSectionId(Guid projectId); Task> GetProjectAttributes(); + Task> GetRequiredProjectAttributesByProjectId(Guid projectId); + Task GetProjectAttributeById(Guid id); Task AddAttributesToProject(Guid projectId, IEnumerable attributeIds); Task UpdateAttribute(TmsAttribute attribute); + Task UpdateProjectAttribute(Guid projectId, TmsAttribute attribute); Task UploadAttachment(string fileName, Stream content); Task CreateParameter(Parameter parameter); Task> GetParameter(string name); diff --git a/Migrators/Importer/Readme.md b/Migrators/Importer/Readme.md index ce4e23b..eaf2964 100644 --- a/Migrators/Importer/Readme.md +++ b/Migrators/Importer/Readme.md @@ -17,7 +17,8 @@ You can download the latest version of the Importer from the [releases](https:// "url" : "https://testit.software/", "privateToken" : "cmZzWDkYTfBvNvVMcXhzN3Vy", "certValidation" : true, - "importToExistingProject" : false + "importToExistingProject" : false, + "projectName" : "CustomProjectName" } } ``` @@ -28,7 +29,8 @@ Where: - tms.url - url to the Test IT server - tms.privateToken - token for access to the Test IT server - tms.certValidation - enable/disable certificate validation (Default value - true) -- tms.importToExistingProject - enable/disable import to existing project in Test IT server (Default value - false) +- tms.importToExistingProject - enable/disable import to existing project in the Test IT server (Default value - false) +- tms.projectName - custom name of the project in the Test IT server (Default value - name of the project in the export system) 2. Run the Importer with the following command: diff --git a/Migrators/Importer/Services/AttributeService.cs b/Migrators/Importer/Services/AttributeService.cs index ec070f9..c98463b 100644 --- a/Migrators/Importer/Services/AttributeService.cs +++ b/Migrators/Importer/Services/AttributeService.cs @@ -21,11 +21,19 @@ public async Task> ImportAttributes(Guid projectI _logger.LogInformation("Importing attributes"); var projectAttributes = await _client.GetProjectAttributes(); + var unusedRequiredProjectAttributes = await _client.GetRequiredProjectAttributesByProjectId(projectId); var attributesMap = new Dictionary(); foreach (var attribute in attributes) { + var requiredProjectAttribute = unusedRequiredProjectAttributes.FirstOrDefault(x => x.Name == attribute.Name); + + if (requiredProjectAttribute != null && requiredProjectAttribute.Type == attribute.Type.ToString()) + { + unusedRequiredProjectAttributes.Remove(requiredProjectAttribute); + } + var attributeIsNotImported = true; do @@ -49,7 +57,7 @@ public async Task> ImportAttributes(Guid projectI { _logger.LogInformation("Attribute {Name} already exists with id {Id}", attribute.Name, - attribute.Id); + projectAttribute.Id); if (string.Equals(projectAttribute.Type, "options", StringComparison.InvariantCultureIgnoreCase) || string.Equals(projectAttribute.Type, "multipleOptions", StringComparison.InvariantCultureIgnoreCase)) @@ -68,7 +76,8 @@ public async Task> ImportAttributes(Guid projectI } } - projectAttribute = await _client.UpdateAttribute(projectAttribute); + await _client.UpdateAttribute(projectAttribute); + projectAttribute = await _client.GetProjectAttributeById(projectAttribute.Id); } attributesMap.Add(attribute.Id, projectAttribute); @@ -85,6 +94,15 @@ public async Task> ImportAttributes(Guid projectI while (attributeIsNotImported); } + foreach (var unusedRequiredProjectAttribute in unusedRequiredProjectAttributes) + { + _logger.LogInformation("Required project attribute {Name} is not used when importing test cases. Set as optional", unusedRequiredProjectAttribute.Name); + + unusedRequiredProjectAttribute.IsRequired = false; + + await _client.UpdateProjectAttribute(projectId, unusedRequiredProjectAttribute); + } + _logger.LogInformation("Importing attributes finished"); _logger.LogDebug("Attributes map: {@AttributesMap}", attributesMap);