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);
        }
예제 #3
0
        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);
        }
예제 #4
0
        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);
            }
        }
예제 #9
0
        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);
        }
예제 #11
0
        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);
            }
        }