3 var fs = require('fs');
4 var util = require('util');
5 var colors = require('colors');
6 var csv = require('csv');
7 var rest = require('restler');
8 var async = require('async');
9 var _ = require('lodash');
10 var argv = require('optimist')
11 .demand(['i', 'c', 'g', 'p', 't', 's'])
14 .alias('g', 'gitlaburl')
15 .alias('p', 'project')
18 .describe('i', 'CSV file exported from Mantis (Example: issues.csv)')
19 .describe('c', 'Configuration file (Example: config.json)')
20 .describe('g', 'GitLab URL hostname (Example: https://gitlab.com)')
21 .describe('p', 'GitLab project name including namespace (Example: mycorp/myproj)')
22 .describe('t', 'An admin user\'s private token (Example: a2r33oczFyQzq53t23Vj)')
23 .describe('s', 'The username performing the import (Example: bob)')
26 var inputFile = __dirname + '/' + argv.input;
27 var configFile = __dirname + '/' + argv.config;
28 var gitlabAPIURLBase = argv.gitlaburl + '/api/v3';
29 var gitlabProjectName = argv.project;
30 var gitlabAdminPrivateToken = argv.token;
31 var gitlabSudo = argv.sudo;
34 getGitLabProject(gitlabProjectName, gitlabAdminPrivateToken, function(error, project) {
36 console.error('Error: Cannot get list of projects from gitlab: ' + gitlabAPIURLBase);
41 console.error('Error: Cannot find GitLab project: ' + gitlabProjectName);
45 getGitLabUsers(gitlabAdminPrivateToken, function(error, gitlabUsers) {
47 console.error('Error: Cannot get list of users from gitlab: ' + gitlabAPIURLBase);
51 getConfig(configFile, function(error, cfg) {
53 console.error('Error: Cannot read config file: ' + configFile);
59 var users = config.users;
61 setGitLabUserIds(users, gitlabUsers);
63 readRows(inputFile, function(error, rows) {
65 console.error('Error: Cannot read input file: ' + inputFile);
69 validate(rows, users, function(missingUsernames, missingNames) {
70 if (missingUsernames.length > 0 || missingNames.length > 0) {
71 for (var i = 0; i < missingUsernames.length; i++)
72 console.error('Error: Cannot map Mantis user with username: ' + missingUsernames[i]);
74 for (var i = 0; i < missingNames.length; i++)
75 console.error('Error: Cannot map Mantis user with name: ' + missingNames[i]);
80 rows = _.sortBy(rows, function(row) { return Date.parse(row.Created); });
82 async.eachSeries(rows, function(row, callback) {
84 var title = row.Summary;
85 var description = getDescription(row);
86 var assignee = getUserByMantisUsername(users, row["Assigned To"]);
88 var labels = getLabels(row);
89 var author = getUserByMantisUsername(users, row.Reporter);
91 insertIssue(project.id, title, description, assignee && assignee.gl_id, milestoneId, labels, author.gl_username, gitlabAdminPrivateToken, function(error, issue) {
92 setTimeout(callback, 1000);
95 console.error((issueId + ': Failed to insert.').red, error);
100 closeIssue(issue, assignee.gl_private_token || gitlabAdminPrivateToken, function(error) {
102 console.warn((issueId + ': Inserted successfully but failed to close. #' + issue.iid).yellow);
104 console.error((issueId + ': Inserted and closed successfully. #' + issue.iid).green);
110 console.log((issueId + ': Inserted successfully. #' + issue.iid).green);
119 function getGitLabProject(name, privateToken, callback) {
120 var url = gitlabAPIURLBase + '/projects';
121 var data = { per_page: 100, private_token: privateToken, sudo: gitlabSudo };
123 rest.get(url, {data: data}).on('complete', function(result, response) {
124 if (util.isError(result)) {
129 if (response.statusCode != 200) {
134 for (var i = 0; i < result.length; i++) {
135 if (result[i].path_with_namespace === name) {
136 callback(null, result[i]);
141 callback(null, null);
145 function getGitLabUsers(privateToken, callback) {
146 var url = gitlabAPIURLBase + '/users';
147 var data = { per_page: 100, private_token: privateToken, sudo: gitlabSudo };
149 rest.get(url, {data: data}).on('complete', function(result, response) {
150 if (util.isError(result)) {
155 if (response.statusCode != 200) {
160 callback(null, result);
164 function getConfig(configFile, callback) {
165 fs.readFile(configFile, {encoding: 'utf8'}, function(error, data) {
171 var config = JSON.parse(data);
172 config.users = config.users || [];
174 callback(null, config);
178 function setGitLabUserIds(users, gitlabUsers) {
179 for (var i = 0; i < users.length; i++) {
180 for (var j = 0; j < gitlabUsers.length; j++) {
181 if (users[i].gl_username === gitlabUsers[j].username) {
182 users[i].gl_id = gitlabUsers[j].id;
189 function readRows(inputFile, callback) {
190 fs.readFile(inputFile, {encoding: 'utf8'}, function(error, data) {
198 csv().from(data, {delimiter: ',', escape: '"', columns: true})
199 .on('record', function(row, index) { rows.push(row) })
200 .on('end', function() { callback(null, rows) });
204 function validate(rows, users, callback) {
205 var missingUsername = [];
206 var missingNames = [];
208 for (var i = 0; i < rows.length; i++) {
209 var assignee = rows[i]["Assigned To"];
211 if (!getUserByMantisUsername(users, assignee) && missingUsername.indexOf(assignee) == -1)
212 missingUsername.push(assignee);
215 for (var i = 0; i < rows.length; i++) {
216 var reporter = rows[i].Reporter;
218 if (!getUserByMantisUsername(users, reporter) && missingNames.indexOf(reporter) == -1)
219 missingNames.push(reporter);
222 callback(missingUsername, missingNames);
225 function getUserByMantisUsername(users, username) {
226 return (username && _.find(users, {username: username || null })) || null;
229 function getDescription(row) {
231 var issueId = row.Id;
233 if (config.mantisUrl) {
234 attributes.push("[Mantis Issue " + issueId + "](" + config.mantisUrl + "/view.php?id=" + issueId + ")");
236 attributes.push("Mantis Issue " + issueId);
239 if (value = row.Reporter) {
240 attributes.push("Reported By: " + value);
243 if (value = row["Assigned To"]) {
244 attributes.push("Assigned To: " + value);
247 if (value = row.Created) {
248 attributes.push("Created: " + value);
251 if (value = row.Updated && value != row.Created) {
252 attributes.push("Updated: " + value);
255 var description = "_" + attributes.join(", ") + "_\n\n";
257 description += row.Description;
259 if (value = row.Info) {
260 description += "\n\n" + value;
266 function getLabels(row) {
268 var labels = (row.tags || []).slice(0);
270 if(label = config.category_labels[row.Category]) {
274 if(label = config.priority_labels[row.Priority]) {
278 if(label = config.severity_labels[row.Severity]) {
282 return labels.join(",");
285 function isClosed(row) {
286 return config.closed_statuses[row.Status];
289 function insertIssue(projectId, title, description, assigneeId, milestoneId, labels, creatorId, privateToken, callback) {
290 var url = gitlabAPIURLBase + '/projects/' + projectId + '/issues';
293 description: description,
294 assignee_id: assigneeId,
295 milestone_id: milestoneId,
298 private_token: privateToken
301 rest.post(url, {data: data}).on('complete', function(result, response) {
302 if (util.isError(result)) {
307 if (response.statusCode != 201) {
312 callback(null, result);
316 function closeIssue(issue, privateToken, callback) {
317 var url = gitlabAPIURLBase + '/projects/' + issue.project_id + '/issues/' + issue.id;
319 state_event: 'close',
320 private_token: privateToken,
324 rest.put(url, {data: data}).on('complete', function(result, response) {
325 if (util.isError(result)) {
330 if (response.statusCode != 200) {