Skip to content

Instantly share code, notes, and snippets.

@fcamblor
Last active October 16, 2018 13:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fcamblor/d21aad7b301cacaa56202a27c5c8bd58 to your computer and use it in GitHub Desktop.
Save fcamblor/d21aad7b301cacaa56202a27c5c8bd58 to your computer and use it in GitHub Desktop.
OLD JIRA to NEW JIRA Versions migrator
// 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();
// 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