internal WiItem Map(JiraItem issue) { var wiItem = new WiItem(); if (_config.TypeMap.Types != null) { var type = (from t in _config.TypeMap.Types where t.Source == issue.Type select t.Target).FirstOrDefault(); if (type != null) { var revisions = issue.Revisions.Select(r => MapRevision(r)).ToList(); MapLastDescription(revisions, issue); wiItem.OriginId = issue.Key; wiItem.Type = type; wiItem.Revisions = revisions; } else { Logger.Log(LogLevel.Warning, $"Type mapping missing for {issue.Key} with Jira type {issue.Type}. Item not exported which may cause missing links in related issues."); return(null); } } return(wiItem); }
private bool CorrectDescription(WorkItem wi, WiItem wiItem, WiRevision rev) { string description = wi.Type.Name == "Bug" ? wi.Fields[WiFieldReference.ReproSteps].Value.ToString() : wi.Description; if (string.IsNullOrWhiteSpace(description)) { return(false); } bool descUpdated = false; CorrectImagePath(wi, wiItem, rev, ref description, ref descUpdated); if (descUpdated) { if (wi.Type.Name == "Bug") { wi.Fields[WiFieldReference.ReproSteps].Value = description; } else { wi.Fields[WiFieldReference.Description].Value = description; } } return(descUpdated); }
internal WiItem Map(JiraItem issue) { var wiItem = new WiItem(); if (_config.TypeMap.Types != null) { var type = (from t in _config.TypeMap.Types where t.Source == issue.Type select t.Target).FirstOrDefault(); if (type != null) { var revisions = issue.Revisions.Select(r => MapRevision(r)).ToList(); MapLastDescription(revisions, issue); wiItem.OriginId = issue.Key; wiItem.Type = type; wiItem.Revisions = revisions; wiItem.WikiURL = issue.ConfluenceLink; } else { Logger.Log(LogLevel.Error, $"Type mapping missing for '{issue.Key}' with Jira type '{issue.Type}'. Item was not exported which may cause missing links in issues referencing this item."); return(null); } } return(wiItem); }
private bool CorrectDescription(WorkItem wi, WiItem wiItem, WiRevision rev) { string currentDescription = wi.Type.Name == "Bug" ? wi.Fields["Microsoft.VSTS.TCM.ReproSteps"].Value.ToString() : wi.Description; if (string.IsNullOrWhiteSpace(currentDescription)) { return(false); } bool descUpdated = false; foreach (var att in wiItem.Revisions.SelectMany(r => r.Attachments.Where(a => a.Change == ReferenceChangeType.Added))) { if (currentDescription.Contains(att.FilePath)) { var tfsAtt = IdentifyAttachment(att, wi); descUpdated = true; if (tfsAtt != null) { currentDescription = currentDescription.Replace(att.FilePath, tfsAtt.Uri.AbsoluteUri); descUpdated = true; } else { Logger.Log(LogLevel.Warning, $"Attachment '{att.ToString()}' referenced in description but is missing from work item {wiItem.OriginId}/{wi.Id}."); } } } if (descUpdated) { DateTime changedDate; if (wiItem.Revisions.Count > rev.Index + 1) { changedDate = RevisionUtility.NextValidDeltaRev(rev.Time, wiItem.Revisions[rev.Index + 1].Time); } else { changedDate = RevisionUtility.NextValidDeltaRev(rev.Time); } wi.Fields["System.ChangedDate"].Value = changedDate; wi.Fields["System.ChangedBy"].Value = rev.Author; if (wi.Type.Name == "Bug") { wi.Fields["Microsoft.VSTS.TCM.ReproSteps"].Value = currentDescription; } else { wi.Fields["System.Description"].Value = currentDescription; } } return(descUpdated); }
private void CorrectComment(WorkItem wi, WiItem wiItem, WiRevision rev) { var currentComment = wi.History; var commentUpdated = false; CorrectImagePath(wi, wiItem, rev, ref currentComment, ref commentUpdated); if (commentUpdated) { wi.Fields[CoreField.History].Value = currentComment; } }
internal WiItem Map(JiraItem issue) { var wiItem = new WiItem(); if (_config.TypeMap.Types != null) { var type = (from t in _config.TypeMap.Types where t.Source == issue.Type select t.Target).FirstOrDefault(); if (type != null) { var revisions = issue.Revisions.Select(r => MapRevision(r)).ToList(); MapLastDescription(revisions, issue); wiItem.OriginId = issue.Key; wiItem.Type = type; wiItem.Revisions = revisions; } } return(wiItem); }
private void CorrectImagePath(WorkItem wi, WiItem wiItem, WiRevision rev, ref string textField, ref bool isUpdated) { foreach (var att in wiItem.Revisions.SelectMany(r => r.Attachments.Where(a => a.Change == ReferenceChangeType.Added))) { var fileName = att.FilePath.Split('\\')?.Last() ?? string.Empty; if (textField.Contains(fileName)) { var tfsAtt = IdentifyAttachment(att, wi); if (tfsAtt != null) { string imageSrcPattern = $"src.*?=.*?\"([^\"])(?=.*{att.AttOriginId}).*?\""; textField = Regex.Replace(textField, imageSrcPattern, $"src=\"{tfsAtt.Uri.AbsoluteUri}\""); isUpdated = true; } else { Logger.Log(LogLevel.Warning, $"Attachment '{att.ToString()}' referenced in text but is missing from work item {wiItem.OriginId}/{wi.Id}."); } } } if (isUpdated) { DateTime changedDate; if (wiItem.Revisions.Count > rev.Index + 1) { changedDate = RevisionUtility.NextValidDeltaRev(rev.Time, wiItem.Revisions[rev.Index + 1].Time); } else { changedDate = RevisionUtility.NextValidDeltaRev(rev.Time); } wi.Fields[WiFieldReference.ChangedDate].Value = changedDate; wi.Fields[WiFieldReference.ChangedBy].Value = rev.Author; } }
private void ExecuteMigration(CommandOption user, CommandOption password, CommandOption useOfflineBackup, CommandOption url, CommandOption configFile, bool forceFresh, CommandOption continueOnCritical) { var itemsCount = 0; var exportedItemsCount = 0; var sw = new Stopwatch(); sw.Start(); try { string configFileName = configFile.Value(); ConfigReaderJson configReaderJson = new ConfigReaderJson(configFileName); var config = configReaderJson.Deserialize(); InitSession(config, continueOnCritical.Value()); // Migration session level settings // where the logs and journal will be saved, logs aid debugging, journal is for recovery of interupted process string migrationWorkspace = config.Workspace; var downloadOptions = (DownloadOptions)config.DownloadOptions; var jiraSettings = new JiraSettings(user.Value(), password.Value(), url.Value(), config.SourceProject) { BatchSize = config.BatchSize, UserMappingFile = config.UserMappingFile != null ? Path.Combine(migrationWorkspace, config.UserMappingFile) : string.Empty, AttachmentsDir = Path.Combine(migrationWorkspace, config.AttachmentsFolder), JQL = config.Query, UsingJiraCloud = config.UsingJiraCloud }; IJiraProvider jiraProvider = null; if (useOfflineBackup.HasValue()) { jiraProvider = OfflineJiraProvider.Initialize(useOfflineBackup.Value(), jiraSettings); } else { jiraProvider = JiraProvider.Initialize(jiraSettings); } itemsCount = jiraProvider.GetItemCount(jiraSettings.JQL); BeginSession(configFileName, config, forceFresh, jiraProvider, itemsCount); jiraSettings.EpicLinkField = jiraProvider.GetCustomId(config.EpicLinkField); if (string.IsNullOrEmpty(jiraSettings.EpicLinkField)) { Logger.Log(LogLevel.Warning, $"Epic link field missing for config field '{config.EpicLinkField}'."); } jiraSettings.SprintField = jiraProvider.GetCustomId(config.SprintField); if (string.IsNullOrEmpty(jiraSettings.SprintField)) { Logger.Log(LogLevel.Warning, $"Sprint link field missing for config field '{config.SprintField}'."); } var mapper = new JiraMapper(jiraProvider, config); var localProvider = new WiItemProvider(migrationWorkspace); var exportedKeys = new HashSet<string>(Directory.EnumerateFiles(migrationWorkspace, "*.json").Select(f => Path.GetFileNameWithoutExtension(f))); var skips = forceFresh ? new HashSet<string>(Enumerable.Empty<string>()) : exportedKeys; var issues = jiraProvider.EnumerateIssues(jiraSettings.JQL, skips, downloadOptions); foreach (var issue in issues) { if (issue == null) continue; WiItem wiItem = mapper.Map(issue); if (wiItem != null) { localProvider.Save(wiItem); exportedItemsCount++; Logger.Log(LogLevel.Debug, $"Exported as type '{wiItem.Type}'."); } } } catch (CommandParsingException e) { Logger.Log(LogLevel.Error, $"Invalid command line option(s): {e}"); } catch (Exception e) { Logger.Log(e, $"Unexpected migration error."); } finally { EndSession(itemsCount, sw); } }
private void ExecuteMigration(CommandOption user, CommandOption password, CommandOption url, CommandOption configFile, bool forceFresh) { ConfigJson config = null; try { string configFileName = configFile.Value(); ConfigReaderJson configReaderJson = new ConfigReaderJson(configFileName); config = configReaderJson.Deserialize(); // Migration session level settings // where the logs and journal will be saved, logs aid debugging, journal is for recovery of interupted process string migrationWorkspace = config.Workspace; // level of log messages that will be let through to console LogLevel logLevel; switch (config.LogLevel) { case "Info": logLevel = LogLevel.Info; break; case "Debug": logLevel = LogLevel.Debug; break; case "Warning": logLevel = LogLevel.Warning; break; case "Error": logLevel = LogLevel.Error; break; case "Critical": logLevel = LogLevel.Critical; break; default: logLevel = LogLevel.Debug; break; } var downloadOptions = JiraProvider.DownloadOptions.IncludeParentEpics | JiraProvider.DownloadOptions.IncludeSubItems | JiraProvider.DownloadOptions.IncludeParents; Logger.Init(migrationWorkspace, logLevel); var jiraSettings = new JiraSettings(user.Value(), password.Value(), url.Value(), config.SourceProject) { BatchSize = config.BatchSize, UserMappingFile = config.UserMappingFile != null?Path.Combine(migrationWorkspace, config.UserMappingFile) : string.Empty, AttachmentsDir = Path.Combine(migrationWorkspace, config.AttachmentsFolder), JQL = config.Query, DomainMapping = config.DomainMapping }; JiraProvider jiraProvider = JiraProvider.Initialize(jiraSettings); // Get the custom field names for epic link field and sprint field jiraSettings.EpicLinkField = jiraProvider.GetCustomId(config.EpicLinkField); jiraSettings.SprintField = jiraProvider.GetCustomId(config.SprintField); var mapper = new JiraMapper(jiraProvider, config); var localProvider = new WiItemProvider(migrationWorkspace); var exportedKeys = new HashSet <string>(Directory.EnumerateFiles(migrationWorkspace, "*.json").Select(f => Path.GetFileNameWithoutExtension(f))); var skips = forceFresh ? new HashSet <string>(Enumerable.Empty <string>()) : exportedKeys; foreach (var issue in jiraProvider.EnumerateIssues(jiraSettings.JQL, skips, downloadOptions)) { WiItem wiItem = mapper.Map(issue); localProvider.Save(wiItem); Logger.Log(LogLevel.Info, $"Exported {wiItem.ToString()}"); } } catch (CommandParsingException e) { Logger.Log(LogLevel.Error, $"Invalid command line option(s): {e}"); } catch (Exception e) { Logger.Log(LogLevel.Error, $"Unexpected error: {e}"); } }
internal WiItem Map(JiraItem issue) { var wiItem = new WiItem(); if (_config.TypeMap.Types != null) { var type = (from t in _config.TypeMap.Types where t.Source == issue.Type select t.Target).FirstOrDefault(); if (type != null) { var revisions = issue.Revisions.Select(r => MapRevision(r)).ToList(); // TODO: Add revision for snapshot fields const string pfPrefix = "Preserve."; var pfContent = new StringBuilder(); pfContent.AppendLine($"<h3>Migration Preserved Fields</h3>"); pfContent.AppendLine($"<table><tr><th>Field</th><th>Value</th></tr>"); var hasPreserveFields = false; foreach (var r in revisions) { var preserveFields = from f in r.Fields where f.ReferenceName.StartsWith(pfPrefix) select f; hasPreserveFields = hasPreserveFields || preserveFields.Count() > 0; foreach (var pf in preserveFields.ToList()) { pfContent.AppendLine($"<tr><td>{pf.ReferenceName.Replace(pfPrefix, "")}</td><td>{pf.Value?.ToString().Trim()}</td></tr>"); r.Fields.Remove(pf); } } pfContent.AppendLine($"</table>"); if (hasPreserveFields) { revisions.Add(new WiRevision() { ParentOriginId = issue.Key, Index = revisions.Count, Author = MapUser("*****@*****.**"), Fields = new List <WiField>() { new WiField() { ReferenceName = "System.History", Value = pfContent.ToString() } } }); } wiItem.OriginId = issue.Key; wiItem.Type = type; wiItem.Revisions = revisions; } else { Logger.Log(LogLevel.Error, $"Type mapping missing for '{issue.Key}' with Jira type '{issue.Type}'. Item was not exported which may cause missing links in issues referencing this item."); return(null); } } return(wiItem); }
private void ExecuteMigration(CommandOption user, CommandOption password, CommandOption url, CommandOption configFile, bool forceFresh) { var itemsCount = 0; var exportedItemsCount = 0; var sw = new Stopwatch(); sw.Start(); try { string configFileName = configFile.Value(); var assemblyPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); ConfigReaderJson configReaderJson = new ConfigReaderJson($"{assemblyPath}\\{configFileName}"); var config = configReaderJson.Deserialize(); InitSession(config); // Migration session level settings // where the logs and journal will be saved, logs aid debugging, journal is for recovery of interupted process string migrationWorkspace = config.Workspace; var downloadOptions = (DownloadOptions)config.DownloadOptions; var jiraSettings = new JiraSettings(user.Value(), password.Value(), url.Value(), config.SourceProject) { BatchSize = config.BatchSize, UserMappingFile = config.UserMappingFile != null?Path.Combine(assemblyPath, config.UserMappingFile) : string.Empty, AttachmentsDir = Path.Combine(migrationWorkspace, config.AttachmentsFolder), JQL = config.Query }; JiraProvider jiraProvider = JiraProvider.Initialize(jiraSettings); itemsCount = jiraProvider.GetItemCount(jiraSettings.JQL); BeginSession(configFileName, config, forceFresh, jiraProvider, itemsCount); //change to retrive from fields jiraSettings.EpicLinkField = jiraProvider.Fields.FirstOrDefault(f => f.Name == "Epic Link")?.Id; if (string.IsNullOrEmpty(jiraSettings.EpicLinkField)) { Logger.Log(LogLevel.Warning, $"Epic link field missing for config field '{config.EpicLinkField}'."); } jiraSettings.SprintField = jiraProvider.Fields.FirstOrDefault(f => f.Name == "Sprint")?.Id; if (string.IsNullOrEmpty(jiraSettings.SprintField)) { Logger.Log(LogLevel.Warning, $"Sprint link field missing for config field '{config.SprintField}'."); } jiraSettings.ConfluenceLinkField = config.ConfluenceLinkField; if (string.IsNullOrEmpty(jiraSettings.ConfluenceLinkField)) { Logger.Log(LogLevel.Warning, $"Confluence link field missing for config field '{config.ConfluenceLinkField}'."); } var mapper = new JiraMapper(jiraProvider, config); var localProvider = new WiItemProvider(migrationWorkspace); var exportedKeys = new HashSet <string>(Directory.EnumerateFiles(migrationWorkspace, "*.json").Select(f => Path.GetFileNameWithoutExtension(f))); var skips = forceFresh ? new HashSet <string>(Enumerable.Empty <string>()) : exportedKeys; var query = jiraSettings.JQL; //Debugging id ex: query = "project = PL AND id = PL-2449"; var issues = jiraProvider.EnumerateIssues(query, skips, downloadOptions); foreach (var issue in issues) { WiItem wiItem = mapper.Map(issue); if (wiItem != null) { localProvider.Save(wiItem); exportedItemsCount++; Logger.Log(LogLevel.Debug, $"Exported as type '{wiItem.Type}'."); } } } catch (CommandParsingException e) { Logger.Log(LogLevel.Error, $"Invalid command line option(s): {e}"); } catch (Exception e) { Logger.Log(e, $"Unexpected migration error."); } finally { EndSession(itemsCount, sw); } }