{"id":876,"date":"2021-01-08T14:28:51","date_gmt":"2021-01-08T04:28:51","guid":{"rendered":"http:\/\/www.nickdu.com\/?p=876"},"modified":"2021-01-08T14:28:51","modified_gmt":"2021-01-08T04:28:51","slug":"jira-2-azure-devops-in-c","status":"publish","type":"post","link":"https:\/\/nickdu.com\/?p=876","title":{"rendered":"Jira 2 Azure Devops migration tool in C#"},"content":{"rendered":"\n<p>I have been assigned a task to migrate <a rel=\"noreferrer noopener\" href=\"https:\/\/www.atlassian.com\/software\/jira\" target=\"_blank\">Jira<\/a>, our internal bug tracking system, to <a rel=\"noreferrer noopener\" href=\"https:\/\/azure.microsoft.com\/\" target=\"_blank\">Azure DevOps<\/a> cloud. There is a Jira plugin available, called <a rel=\"noreferrer noopener\" href=\"https:\/\/marketplace.atlassian.com\/apps\/42296\/tfs4jira-azure-devops-integration?hosting=cloud&amp;tab=overview\" target=\"_blank\">TFS4JIAR<\/a>, but it costs lots of money.<\/p>\n\n\n\n<p>Here is my simple .Net C# code (if you want to use, you need modify to suit your own working environment)<\/p>\n\n\n\n<p>Complete project can be found my <a href=\"https:\/\/github.com\/nickdu088\/Jira2Azure\" target=\"_blank\" rel=\"noreferrer noopener\">github<\/a>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>using Newtonsoft.Json;\nusing System;\nusing System.Collections.Generic;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace Jira2Azure\n{\n    class Program\n    {\n        static string JIRAIssueQuery = \"http:\/\/jiraserver:8080\/rest\/api\/latest\/search?jql={0}&amp;fields=*all\";\n        static string TFSBugUrl = \"https:\/\/dev.azure.com\/Test\/SampleProject\/_apis\/wit\/workitems\/$Bug?api-version=6.0\";\n        static string TFSAttachmentUrl = \"https:\/\/dev.azure.com\/Test\/SampleProject\/_apis\/wit\/attachments?fileName={0}&amp;api-version=6.0\";\n\n        static async Task&lt;dynamic&gt; JIRAGetIssues(string jql)\n        {\n            using (var client = new HttpClient())\n            {\n                \/\/your Jira login credential\n                client.DefaultRequestHeaders.Add(\"Authorization\", \"Basic XXXXXXXXXX\");\n                var msg = await client.GetStringAsync(string.Format(JIRAIssueQuery, jql));\n                dynamic issues = JsonConvert.DeserializeObject(msg);\n                return issues;\n            }\n        }\n\n        static async Task JIRA2TFS(string jql)\n        {\n            var issues = await JIRAGetIssues(jql);\n            foreach (var issue in issues.issues)\n            {\n                var tfs = ConvertJIRA2TFS(issue);\n                await TFSCreateIssue(tfs);\n            }\n        }\n\n        static async Task&lt;dynamic&gt; TFSUploadFile(string file, byte&#91;] byteData)\n        {\n            file = Uri.EscapeUriString(file);\n            using (var client = new HttpClient())\n            {\n                \/\/your Azure login credential\n                client.DefaultRequestHeaders.Add(\"Authorization\", \"Basic XXXXXXXXX\");\n                using (var content = new ByteArrayContent(byteData))\n                {\n                    content.Headers.ContentType = new MediaTypeHeaderValue(\"application\/octet-stream\");\n\n                    var msg = await client.PostAsync(string.Format(TFSAttachmentUrl, file), content);\n                    var responseBody = msg.Content.ReadAsStringAsync().Result;\n                    return JsonConvert.DeserializeObject(responseBody);\n                }\n            }\n        }\n        static byte&#91;] JIRADownloadFile(dynamic url)\n        {\n            using (var client = new HttpClient())\n            {\n                \/\/your Jira login credential\n                client.DefaultRequestHeaders.Add(\"Authorization\", \"Basic XXXXXXXX\");\n                var content = client.GetByteArrayAsync((string)url).Result;\n                return content;\n            }\n        }\n        static string ConvertJIRA2TFS(dynamic issue)\n        {\n            \/\/Additional JIRA link for documentation\n            string json = $\"&#91;{{\\\"op\\\": \\\"add\\\",\\\"path\\\": \\\"\/relations\/-\\\",\\\"value\\\": {{\\\"rel\\\": \\\"Hyperlink\\\",\\\"url\\\": \\\"{issue.self}\\\", \\\"attributes\\\": {{\\\"comment\\\": \\\"{issue.key}\\\"}}}}}},\";\n            if (issue.fields.attachment != null)\n            {\n                foreach (var attach in issue.fields.attachment)\n                {\n                    var content = JIRADownloadFile(attach.content);\n                    var attchUrl = TFSUploadFile((string)attach.filename, content).Result;\n                    var attachedJson = $\"{{\\\"rel\\\":\\\"AttachedFile\\\",\\\"url\\\":\\\"{(string)attchUrl.url}\\\",\\\"attributes\\\":{{\\\"resourceSize\\\":{content.Length},\\\"name\\\":\\\"{(string)attach.filename}\\\"}}}}\";\n                    json += string.Format(\"{{\\\"op\\\":\\\"add\\\",\\\"path\\\":\\\"{0}\\\",\\\"from\\\": null,\\\"value\\\":{1}}},\", \"\/relations\/-\", attachedJson);\n                }\n            }\n            string operation = \"{{\\\"op\\\":\\\"add\\\",\\\"path\\\":\\\"{0}\\\",\\\"from\\\": null,\\\"value\\\":\\\"{1}\\\"}},\";\n            \/\/######### more field mapping\n            var fieldMapping = new Dictionary&lt;string, dynamic&gt;()\n            {\n                { \"\/fields\/System.Title\",issue.fields.summary },\n                { \"\/fields\/Microsoft.VSTS.TCM.ReproSteps\", issue.fields.description},\n                { \"\/fields\/Microsoft.VSTS.Common.Priority\", issue.fields.priority.id},\n            };\n\n            foreach (var field in fieldMapping)\n            {\n                if (field.Value != null)\n                {\n                    var str = ((string)field.Value).Replace(\"\\\"\", \"\\\\\\\"\");\n                    json += string.Format(operation, field.Key, str);\n                }\n            }\n\n            json = json.Replace(\"\\r\\n\", \"&lt;br\/&gt;\");\n            json = json.TrimEnd(',');\n            json += \"]\";\n            return json;\n        }\n\n        static async Task TFSCreateIssue(string json)\n        {\n            using (var client = new HttpClient())\n            {\n                \/\/your Azure login credential\n                client.DefaultRequestHeaders.Add(\"Authorization\", \"Basic XXXXXXXXXX\");\n                var content = new StringContent(json, Encoding.UTF8, \"application\/json-patch+json\");\n                var msg = await client.PostAsync(TFSBugUrl, content);\n            }\n        }\n\n        static async Task Main(string&#91;] args)\n        {\n            await JIRA2TFS(\"project = TAP\");\n        }\n    }\n}\n\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>I have been assigned a task to migrate Jira, our internal bug tracking system, to Azure DevOps cloud. There is a Jira plugin available, called TFS4JIAR, but it costs lots of money. Here is my simple .Net C# code (if you want to use, you need modify to suit your own working environment) Complete project &hellip; <a href=\"https:\/\/nickdu.com\/?p=876\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Jira 2 Azure Devops migration tool in C#&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9,2],"tags":[],"class_list":["post-876","post","type-post","status-publish","format-standard","hentry","category-net","category-it"],"_links":{"self":[{"href":"https:\/\/nickdu.com\/index.php?rest_route=\/wp\/v2\/posts\/876","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nickdu.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nickdu.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nickdu.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/nickdu.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=876"}],"version-history":[{"count":0,"href":"https:\/\/nickdu.com\/index.php?rest_route=\/wp\/v2\/posts\/876\/revisions"}],"wp:attachment":[{"href":"https:\/\/nickdu.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=876"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nickdu.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=876"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nickdu.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=876"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}