Last active
October 16, 2018 13:02
-
-
Save fcamblor/d21aad7b301cacaa56202a27c5c8bd58 to your computer and use it in GitHub Desktop.
OLD JIRA to NEW JIRA Versions migrator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Navigate on "Project Version Administration" screen in OLD JIRA | |
// (https://jira.4sh.fr/plugins/servlet/project-config/<PROJECT KEY>/versions) | |
// and execute cote below in GOOGLE CHROME console | |
function copyEveryOldVersionInClipboard(){ | |
var versions = jQuery("#project-config-versions-table tbody tr").map((idx, trEl) => { | |
var $tr = jQuery(trEl); | |
var releaseDate = $tr.find("[data-field-name='userReleaseDate']").text(); | |
if(releaseDate === 'Add release date'){ releaseDate = ''; } | |
var description = $tr.find("[data-field-name='description']").text(); | |
if(description === 'Add description'){ description = ''; } | |
return { | |
released: $tr.hasClass("project-config-version-released"), | |
archived: $tr.hasClass("project-config-version-archived"), | |
name: $tr.find("[data-field-name='name']").text(), | |
releaseDate: releaseDate, | |
description: description | |
}; | |
}); | |
versions = _.filter(versions, (version) => !!version.name); | |
copy(JSON.stringify(versions)); | |
console.log(versions.length+" versions have been copied in your clipboard !"); | |
} | |
copyEveryOldVersionInClipboard(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Navigate on "Project Versions" screen in NEW JIRA | |
// (https://issues.4sh.fr/projects/<PROJECT KEY>?selectedItem=com.atlassian.jira.jira-projects-plugin%3Arelease-page&status=all) | |
// On Versions screen, be sure to enable every "quick filters" and scroll to the bottom of the page UNTIL EVERY VERSION | |
// IS SHOW ON SCREEN (because the script is based on existing versions to avoid importing twice the same versions) | |
// Execute cote below in GOOGLE CHROME console | |
// Once done, I advise you to create a new variable containing the OLD JIRA exported JSON, example : | |
// var oldJIRAVersions = <Paste your JSON content here>; | |
// Then call fillVEveryVersions() with this variable | |
// fillVEveryVersions(oldJIRAVersions) | |
// Note that if you encounter some issues when updating versions date (HTTP error 400), this is likely to be done to month formatting of dates. | |
// In that case, try calling fillVEveryVersions(oldJIRAVersions, true) (latest param to true is important) in order to try to workaround this. | |
function fillVEveryVersions(versions, createOrUpdateVersion) { | |
// Removing empty versions | |
versions = _.filter(versions, (version) => !!version.name); | |
var newVersionsByName = readNewJIRAVersionsByName(); | |
return createOrUpdateAllVersions(versions, newVersionsByName, createOrUpdateVersion).then(() => { | |
console.info("Version create/update finished successfully !.. Now, reording things..."); | |
// Now, moving every versions to reflect versions ordering... | |
return moveNewVersionsToReflectOldVersionOrder(versions, newVersionsByName, createOrUpdateVersion); | |
}).then(() => { | |
console.info("Filling every versions in NEW JIRA is finished ! Have a good day :-)"); | |
}); | |
} | |
function guessCurrentProjectFromURL() { | |
var currentProject = window.location.href.replace(/https:\/\/issues.4sh.fr\/projects\/(.*)\?selectedItem=com.atlassian.jira.jira-projects-plugin%3Arelease-page&status=all/gi, "$1"); | |
if(!currentProject) { | |
throw new Error("Cannot guess current project from URL ... are you on a URL like : https://issues.4sh.fr/projects/<PROJECT KEY>?selectedItem=com.atlassian.jira.jira-projects-plugin%3Arelease-page&status=all ?"); | |
} | |
return currentProject; | |
} | |
function moveNewVersionsToReflectOldVersionOrder(oldVersions, newVersionsByName, dateConversionRequired) { | |
console.info("Starting to reorder versions to reflect OLD JIRA ordering"); | |
return createOrUpdateVersion({ released: false, archived: false, name: "__TEMP VERSION A SUPPRIMER__", description: "" }, dateConversionRequired).then((tempVersion) => { | |
return Promise.resolve().then(() => { | |
// Moving every NEW JIRA issues not found in versions to the top | |
// (it's likely that those issues have been created in NEW JIRA while not existing in OLD JIRA) | |
var newJIRAVersionsNotExistingInOldJIRA = _.difference(_.keys(newVersionsByName), _.pluck(oldVersions, 'name')); | |
if(newJIRAVersionsNotExistingInOldJIRA.length) { | |
console.info("Found some issues in NEW JIRA which were not existing in OLD JIRA : "+newJIRAVersionsNotExistingInOldJIRA.join(", ")); | |
console.info("=> Keeping those issues at the top of the versions list !"); | |
} | |
return _.reduce(newJIRAVersionsNotExistingInOldJIRA, (previousPromise, newJIRAVersionName) => { | |
return previousPromise.then(() => { | |
return moveNewJiraVersionAfter(tempVersion.id, newVersionsByName[newJIRAVersionName].id); | |
}); | |
}, Promise.resolve()); | |
}).then(() => { | |
// Moving every versions | |
return _.reduce(oldVersions, (previousPromise, oldVersion) => { | |
var newVersion = newVersionsByName[oldVersion.name]; | |
var newVersionId = newVersion.id; | |
return previousPromise.then(() => { | |
if(!newVersionId) { | |
console.error("Error while resolving new version for old version : "+JSON.stringify(oldVersion)); | |
return Promise.resolve(); | |
} | |
return moveNewJiraVersionAfter(newVersionId, tempVersion.id); | |
}); | |
}, Promise.resolve(tempVersion.id)); | |
}).then(() => { | |
// Cleaning temporary first version | |
return removeAndSwapNewJiraVersion(tempVersion.id); | |
}) | |
}); | |
} | |
function createOrUpdateAllVersions(versions, newVersionsByName, createOrUpdateVersion) { | |
console.info("Starting to import "+versions.length+" versions into NEW JIRA ..."); | |
return _.reduce(versions, function(previousPromise, version) { | |
return previousPromise.then((previousVersion) => { | |
return createOrUpdateVersion(version, createOrUpdateVersion).then((jiraVersion) => { | |
var newVersion = { id: jiraVersion.id, name: jiraVersion.name, previousVersionId: previousVersion?previousVersion.id:null }; | |
newVersionsByName[jiraVersion.name] = newVersion; | |
console.log("Version ["+jiraVersion.name+"] imported successfully !"); | |
return newVersion; | |
}); | |
}); | |
}, Promise.resolve(null)); | |
} | |
var MONTH_MAP = { | |
'Jan': 'janv.', | |
'Feb': 'févr.', | |
'Mar': 'mars', | |
'Apr': 'avr.', | |
'May': 'mai', | |
'Jun': 'juin', | |
'Jul': 'juil.', | |
'Aug': 'août', | |
'Sep': 'sept.', | |
'Oct': 'oct.', | |
'Nov': 'nov.', | |
'Dec': 'déc.' | |
}; | |
function convertDate(oldJiraFormattedDate) { | |
if(!oldJiraFormattedDate) { | |
return ""; | |
} | |
var chunks = oldJiraFormattedDate.split("/"); | |
return chunks[0]+"/"+MONTH_MAP[chunks[1]]+"/"+chunks[2]; | |
} | |
function readNewJIRAVersionsByName() { | |
var newVersions = jQuery("#versions-table tbody tr").map((idx, trEl) => { | |
var $tr = jQuery(trEl); | |
return { | |
id: $tr.attr('data-version-id'), | |
name: $tr.find("td.versions-table__name").text() | |
}; | |
}); | |
newVersions = _.filter(newVersions, (newVersion) => !!newVersion.name); | |
return _.indexBy(newVersions, 'name'); | |
} | |
function createOrUpdateVersion(version, dateConversionRequired) { | |
var $trEl = $("#versions-table tbody tr td.versions-table__name").filter((idx, el) => $(el).text().trim() === version.name.trim()).parent('tr'); | |
var versionId = $trEl.attr('data-version-id'); | |
var issueCreatedPromise; | |
if(!versionId) { | |
console.warn("Version ["+version.name+"] not found in DOM !.. creating new version..."); | |
issueCreatedPromise = createNewJiraVersion({ | |
"project": guessCurrentProjectFromURL(), | |
"name":version.name, | |
"userStartDate":"", | |
"userReleaseDate":"", | |
"description":version.description | |
}).then((response) => { | |
if(response.ok) { | |
// For unknown reasons, we need to wait a bit after a create to be able to update issue afterward | |
return wait(1000).then(() => { | |
return response.json(); | |
}).then((version) => { | |
return version.id; | |
}); | |
} else { | |
console.error("Error after create for version : "+JSON.stringify(version)); | |
return null; | |
} | |
}); | |
} else { | |
issueCreatedPromise = Promise.resolve(versionId); | |
} | |
return issueCreatedPromise.then((versionId) => { | |
return updateNewJiraVersion(versionId, { | |
"id":versionId, | |
"name":version.name, | |
"released":version.released, | |
"userStartDate":"", | |
"userReleaseDate": dateConversionRequired?convertDate(version.releaseDate):version.releaseDate, | |
"description":version.description | |
}).then((issueUpdatedResponse) => { | |
if(!issueUpdatedResponse.ok) { | |
console.error("Fetch Error during version update : "+JSON.stringify(version)); | |
return null; | |
} else { | |
if(version.archived) { | |
// If archived, making an additionnal call (because archived flag cannot be taken into consideration at the same time with released flag above) | |
return updateNewJiraVersion(versionId, { | |
"archived": true, | |
}).then(() => issueUpdatedResponse.json()); | |
} else { | |
return issueUpdatedResponse.json(); | |
} | |
} | |
}, () => console.error("Fatal Error during version creation : "+JSON.stringify(version))); | |
}); | |
} | |
function callNewJIRAAPI(url, method, bodyJSON) { | |
return fetch(url, { | |
"credentials":"include", | |
"headers":{ | |
'Accept': 'application/json', | |
'Content-Type': 'application/json' | |
}, | |
"referrer": window.location.href, | |
"referrerPolicy":"no-referrer-when-downgrade", | |
"body":JSON.stringify(bodyJSON), | |
"method":method, | |
"mode":"cors" | |
}); | |
} | |
function createNewJiraVersion(bodyJSON) { | |
return callNewJIRAAPI("https://issues.4sh.fr/rest/api/2/version", "POST", bodyJSON); | |
} | |
function updateNewJiraVersion(versionId, bodyJSON) { | |
return callNewJIRAAPI("https://issues.4sh.fr/rest/api/2/version/"+versionId, "PUT", bodyJSON); | |
} | |
function moveNewJiraVersionAfter(versionIdToMove, moveAboveThanVersionId) { | |
return callNewJIRAAPI("https://issues.4sh.fr/rest/api/2/version/"+versionIdToMove+"/move", "POST", { | |
"after": "/rest/api/2/version/"+moveAboveThanVersionId | |
}); | |
} | |
function removeAndSwapNewJiraVersion(versionId) { | |
return callNewJIRAAPI("https://issues.4sh.fr/rest/api/2/version/"+versionId+"/removeAndSwap", "POST", {}); | |
} | |
function wait(time) { | |
return new Promise(function(resolve, reject) { | |
setTimeout(resolve, time || 300); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment