damus-github-export

Damus issue data exported from github
git clone git://jb55.com/damus-github-export
Log | Files | Refs | README | LICENSE

commit c7fb648c2bcd17ccfcd2f0f2b1c2c9873cc1b47b
parent 6c0cf49bdfdf87f00d1c94d0c9758b076ef5cd6e
Author: Gavin Rehkemper <gavinr@users.noreply.github.com>
Date:   Sun, 23 Aug 2020 23:12:55 -0500

Merge pull request #26 from gavinr/default-fields

Default fields
Diffstat:
MREADME.md | 14++++++++------
Mexport.js | 234+++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mindex.js | 2++
3 files changed, 166 insertions(+), 84 deletions(-)

diff --git a/README.md b/README.md @@ -28,13 +28,14 @@ githubCsvTools myFile.csv githubCsvTools ``` -| Option | Default | Notes | -| ---------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | -| -f, --exportFileName | YYYY-MM-DD-hh-mm-ss-issues.csv | The name of the CSV you'd like to export to. | -| -a, --exportAttributes | number, title, labels, state, assignees, milestone, comments, created_at, updated_at, closed_at, body | Comma-separated list of attributes (columns) in the export**. | -| -c, --exportComments | n/a | Include comments in the export. | +| Option | Default | Notes | +| ---------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| -f, --exportFileName | YYYY-MM-DD-hh-mm-ss-issues.csv | The name of the CSV you'd like to export to. | +| -a, --exportAttributes | number, title, labels, state, assignees, milestone, comments, created_at, updated_at, closed_at, body | Comma-separated list of attributes (columns) in the export**. | +| -c, --exportComments | n/a | Include comments in the export. If using in combination with `--exportAttributes`, `id` must be included. | +| -e, --exportAll | n/a | Export all possible values from the GitHub API. If not included, a subset of attributes (see `--exportAttributes` above) that are known to be compatible with GitHub *import* will be included in the export. | -** List of all possible options for `exportAttributes`: `url`, `repository_url`, `labels_url`, `comments_url`, `events_url`, `html_url`, `id`, `node_id`, `number`, `title`, `user`, `labels`, `state`, `locked`, `assignee`, `assignees`, `milestone`, `comments`, `created_at`, `updated_at`, `closed_at`, `author_association`, `body` ([more info](https://developer.github.com/v3/issues/#get-an-issue)) +** List of all possible options for `exportAttributes` includes every option in the [GitHub API Export](https://developer.github.com/v3/issues/#response-4). Values in child objects can be referenced using a "dot" - for example, a user's `avatar_url` can be accessed via `user.avatar_url`. ### Tokens @@ -54,6 +55,7 @@ For all actions, the tool will ask you to input a GitHub token. To obtain this t | -t, --token | The GitHub token. https://github.com/settings/tokens | | -o, --organization | The User or Organization slug that the repo lives under. | | -r, --repository | The repository name (slug). | +| -v, --verbose | Include additional logging information. | | -h, --help | See all the options and help. | ## Development diff --git a/export.js b/export.js @@ -24,13 +24,17 @@ const getComment = async (octokit, values, issueNumber) => { // Given the full list of issues, returns back an array of all comments, // each with the issue data also included. -const getFullCommentData = async (octokit, values, data) => { +const getFullCommentData = async (octokit, values, data, verbose = false) => { const fullComments = []; for (let i = 0; i < data.length; i++) { const issueObject = data[i]; fullComments.push({ issue: issueObject, }); + + if (verbose === true) { + console.log("getting comments for issue #: ", issueObject.number); + } const commentsData = await getComment(octokit, values, issueObject.number); commentsData.forEach((comment) => { fullComments.push({ @@ -47,11 +51,119 @@ const getFullCommentData = async (octokit, values, data) => { return fullComments; }; +const writeFile = async (data, fileName = false) => { + return new Promise((resolve, reject) => { + converter.json2csv( + data, + (err, csvString) => { + if (err) { + reject(new Error("Invalid!")); + } + + if (!fileName) { + const now = new Date(); + fileName = `${now.getFullYear()}-${twoPadNumber( + now.getMonth() + 1 + )}-${twoPadNumber(now.getDate())}-${twoPadNumber( + now.getHours() + )}-${twoPadNumber(now.getMinutes())}-${twoPadNumber( + now.getSeconds() + )}-issues.csv`; + } + fs.writeFile(fileName, csvString, "utf8", function (err) { + if (err) { + reject(new Error("Error writing the file.")); + } else { + resolve(fileName); + } + }); + }, + { + emptyFieldValue: "", + } + ); + }); +}; + const twoPadNumber = (number) => { return String(number).padStart(2, "0"); }; -const exportIssues = (octokit, values, includeComments = false) => { +const defaultExportColumns = (data) => { + return data.map((issueObject) => { + const ret = { + number: issueObject.number, + title: issueObject.title, + state: issueObject.state, + labels: "", // will be filled in below, if it exists + milestone: "", // will be filled in below, if it exists + user: "", // will be filled in below, if it exists + assignee: "", // will be filled in below, if it exists + assignees: "", // will be filled in below, if it exists + created_at: issueObject.created_at, + updated_at: issueObject.updated_at, + closed_at: issueObject.closed_at !== null ? issueObject.closed_at : "", + body: issueObject.body, + }; + if (issueObject.user) { + ret.user = issueObject.user.login; + } + if (issueObject.labels) { + ret.labels = issueObject.labels + .map((labelObject) => { + return labelObject.name; + }) + .join(","); + } + if (issueObject.assignee && issueObject.assignee.login) { + ret.assignee = issueObject.assignee.login; + } + if (issueObject.assignees && issueObject.assignees.length > 0) { + ret.assignees = issueObject.assignees + .map((assigneeObject) => { + return assigneeObject.login; + }) + .join(","); + } + if (issueObject.milestone && issueObject.milestone.title) { + ret.milestone = issueObject.milestone.title; + } + return ret; + }); +}; + +const getDataAttribute = (issueObject, attribute) => { + if (attribute.indexOf(".") > 0) { + const parts = attribute.split("."); + let currentObject = issueObject; + parts.forEach((part) => { + if ( + currentObject && + currentObject !== "" && + Object.prototype.hasOwnProperty.call(currentObject, part) + ) { + currentObject = currentObject[part]; + } else { + currentObject = ""; + } + }); + return currentObject; + } else { + return issueObject[attribute]; + } +}; + +const specificAttributeColumns = (data, attributes) => { + return data.map((issueObject) => { + const ret = {}; + attributes.forEach((attribute) => { + ret[attribute] = getDataAttribute(issueObject, attribute); + }); + return ret; + }); +}; + +const exportIssues = (octokit, values) => { // Getting all the issues: const options = octokit.issues.listForRepo.endpoint.merge({ owner: values.userOrOrganization, @@ -60,89 +172,55 @@ const exportIssues = (octokit, values, includeComments = false) => { }); octokit.paginate(options).then( async (data) => { - // Customized columns: - data.forEach(async (issueObject) => { - if (issueObject.user) { - issueObject.user = issueObject.user.login; - } - if (issueObject.assignee) { - issueObject.assignee = issueObject.assignee.login; - } - if (issueObject.labels) { - issueObject.labels = issueObject.labels - .map((labelObject) => { - return labelObject.name; - }) - .join(","); - } - if (issueObject.assignees) { - issueObject.assignees = issueObject.assignees - .map((assigneeObject) => { - return assigneeObject.login; - }) - .join(","); - } - }); - - // Data from the API that we're removing: - const defaultColumns = values.exportAttributes || [ - "number", - "title", - "labels", - "state", - "assignees", - "milestone", - "comments", - "created_at", - "updated_at", - "closed_at", - "body", - ]; - const filteredData = data.map((issueObject) => { - const tempObject = {}; - defaultColumns.forEach((propertyName) => { - tempObject[propertyName] = issueObject[propertyName]; - }); - return tempObject; - }); + // default export - columns that are compatible to be imported into GitHub + let filteredData = defaultExportColumns(data); + if (values.exportAll) { + // Just pass "data", it will flatten the JSON object we got from the API and use that (lots of data!) + filteredData = data; + } else if (values.exportAttributes) { + filteredData = specificAttributeColumns(data, values.exportAttributes); + } + // Add on comments, if requested. let csvData = filteredData; if (values.exportComments === true) { - // If we want comments, replace the data that will get pushed into - // the CSV with our full comments data: - csvData = await getFullCommentData(octokit, values, data); + if (values.exportComments === true) { + // If we want comments, replace the data that will get pushed into + // the CSV with our full comments data: + if ( + csvData[0] && + Object.prototype.hasOwnProperty.call(csvData[0], "number") + ) { + csvData = await getFullCommentData( + octokit, + values, + csvData, + values.verbose + ); + } else { + console.error( + "Error: Must include issue number when exporting comments." + ); + csvData = false; + } + } } - converter.json2csv(csvData, (err, csvString) => { - if (err) { - console.error("error converting!"); + // write the data out to file. + writeFile(csvData, values.exportFileName).then( + (fileName) => { + console.log(`Success! check ${fileName}`); + console.log( + "❤ ❗ If this project has provided you value, please ⭐ star the repo to show your support: ➡ https://github.com/gavinr/github-csv-tools" + ); + process.exit(0); + }, + (err) => { + console.log("Error writing the file. Please try again."); + console.error(err); process.exit(0); } - // console.log("csvString:", csvString); - const now = new Date(); - let fileName = `${now.getFullYear()}-${twoPadNumber( - now.getMonth() + 1 - )}-${twoPadNumber(now.getDate())}-${twoPadNumber( - now.getHours() - )}-${twoPadNumber(now.getMinutes())}-${twoPadNumber( - now.getSeconds() - )}-issues.csv`; - if (values.exportFileName) { - fileName = values.exportFileName; - } - fs.writeFile(fileName, csvString, "utf8", function (err) { - if (err) { - console.error("error writing csv!"); - process.exit(0); - } else { - console.log(`Success! check ${fileName}`); - console.log( - "❤ ❗ If this project has provided you value, please ⭐ star the repo to show your support: ➡ https://github.com/gavinr/github-csv-tools" - ); - process.exit(0); - } - }); - }); + ); }, (err) => { console.log("error", err); diff --git a/index.js b/index.js @@ -35,6 +35,7 @@ program "Comma-separated list of attributes (columns) in the export." ) .option("-c, --exportComments", "Include comments in the export.") + .option("-e, --exportAll", "Include all data in the export.") .action(function (file, options) { co(function* () { var retObject = {}; @@ -54,6 +55,7 @@ program .map((i) => i.trim()); } retObject.exportComments = options.exportComments || false; + retObject.exportAll = options.exportAll || false; retObject.userOrOrganization = options.organization || ""; if (retObject.userOrOrganization === "") {