export.js (6232B)
1 const fs = require("fs"); 2 3 // Gets a single comment 4 const getComment = async (octokit, values, issueNumber) => { 5 return new Promise((resolve, reject) => { 6 const issueOptions = octokit.issues.listComments.endpoint.merge({ 7 owner: values.userOrOrganization, 8 repo: values.repo, 9 issue_number: issueNumber, 10 }); 11 octokit.paginate(issueOptions).then( 12 (commentsData) => { 13 resolve(commentsData); 14 }, 15 (err) => { 16 console.error(err); 17 reject(err); 18 } 19 ); 20 }); 21 }; 22 23 // Given the full list of issues, returns back an array of all comments, 24 // each with the issue data also included. 25 const getFullCommentData = async (octokit, values, data, verbose = false) => { 26 const fullComments = []; 27 for (let i = 0; i < data.length; i++) { 28 const issueObject = data[i]; 29 fullComments.push({ 30 issue: issueObject, 31 }); 32 33 if (verbose === true) { 34 console.log("getting comments for issue #: ", issueObject.number); 35 } 36 const commentsData = await getComment(octokit, values, issueObject.number); 37 commentsData.forEach((comment) => { 38 fullComments.push({ 39 issue: issueObject, 40 comment: { 41 user: comment.user.login, 42 created_at: comment.created_at, 43 updated_at: comment.updated_at, 44 body: comment.body, 45 }, 46 }); 47 }); 48 } 49 return fullComments; 50 } 51 52 const writeFile = async (data, fileName = "export.json") => { 53 return new Promise((resolve, reject) => { 54 fs.writeFile(fileName, JSON.stringify(data), "utf8", function (err) { 55 if (err) { 56 reject(new Error("Error writing the file.")); 57 } else { 58 resolve(fileName); 59 } 60 }); 61 }) 62 } 63 64 const twoPadNumber = (number) => { 65 return String(number).padStart(2, "0"); 66 } 67 68 const defaultExportColumns = (data) => { 69 return data.map((issueObject) => { 70 const ret = { 71 number: issueObject.number, 72 title: issueObject.title, 73 state: issueObject.state, 74 labels: "", // will be filled in below, if it exists 75 milestone: "", // will be filled in below, if it exists 76 user: "", // will be filled in below, if it exists 77 assignee: "", // will be filled in below, if it exists 78 assignees: "", // will be filled in below, if it exists 79 created_at: issueObject.created_at, 80 updated_at: issueObject.updated_at, 81 closed_at: issueObject.closed_at !== null ? issueObject.closed_at : "", 82 body: issueObject.body, 83 }; 84 if (issueObject.user) { 85 ret.user = issueObject.user.login; 86 } 87 if (issueObject.labels) { 88 ret.labels = issueObject.labels 89 .map((labelObject) => { 90 return labelObject.name; 91 }) 92 .join(","); 93 } 94 if (issueObject.assignee && issueObject.assignee.login) { 95 ret.assignee = issueObject.assignee.login; 96 } 97 if (issueObject.assignees && issueObject.assignees.length > 0) { 98 ret.assignees = issueObject.assignees 99 .map((assigneeObject) => { 100 return assigneeObject.login; 101 }) 102 .join(","); 103 } 104 if (issueObject.milestone && issueObject.milestone.title) { 105 ret.milestone = issueObject.milestone.title; 106 } 107 return ret; 108 }); 109 }; 110 111 const getDataAttribute = (issueObject, attribute) => { 112 if (attribute.indexOf(".") > 0) { 113 const parts = attribute.split("."); 114 let currentObject = issueObject; 115 parts.forEach((part) => { 116 if ( 117 currentObject && 118 currentObject !== "" && 119 Object.prototype.hasOwnProperty.call(currentObject, part) 120 ) { 121 currentObject = currentObject[part]; 122 } else { 123 currentObject = ""; 124 } 125 }); 126 return currentObject; 127 } else { 128 return issueObject[attribute]; 129 } 130 }; 131 132 const specificAttributeColumns = (data, attributes) => { 133 return data.map((issueObject) => { 134 const ret = {}; 135 attributes.forEach((attribute) => { 136 ret[attribute] = getDataAttribute(issueObject, attribute); 137 }); 138 return ret; 139 }); 140 }; 141 142 const exportIssues = (octokit, values) => { 143 // Getting all the issues: 144 const options = octokit.issues.listForRepo.endpoint.merge({ 145 owner: values.userOrOrganization, 146 repo: values.repo, 147 state: "all", 148 }); 149 octokit.paginate(options).then( 150 async (data) => { 151 // default export - columns that are compatible to be imported into GitHub 152 let filteredData = defaultExportColumns(data); 153 if (values.exportAll) { 154 // Just pass "data", it will flatten the JSON object we got from the API and use that (lots of data!) 155 filteredData = data; 156 } else if (values.exportAttributes) { 157 filteredData = specificAttributeColumns(data, values.exportAttributes); 158 } 159 160 // Add on comments, if requested. 161 let csvData = filteredData; 162 if (values.exportComments === true) { 163 if (values.exportComments === true) { 164 // If we want comments, replace the data that will get pushed into 165 // the CSV with our full comments data: 166 if ( 167 csvData[0] && 168 Object.prototype.hasOwnProperty.call(csvData[0], "number") 169 ) { 170 csvData = await getFullCommentData( 171 octokit, 172 values, 173 csvData, 174 values.verbose 175 ); 176 } else { 177 console.error( 178 "Error: Must include issue number when exporting comments." 179 ); 180 csvData = false; 181 } 182 } 183 } 184 185 // write the data out to file. 186 writeFile(csvData, values.exportFileName || `${values.repo}.json`).then( 187 (fileName) => { 188 console.log(`Success! check ${fileName}`); 189 console.log( 190 "❤ ❗ If this project has provided you value, please ⭐ star the repo to show your support: ➡ https://github.com/gavinr/github-csv-tools" 191 ); 192 process.exit(0); 193 }, 194 (err) => { 195 console.log("Error writing the file. Please try again."); 196 console.error(err); 197 process.exit(0); 198 } 199 ); 200 }, 201 (err) => { 202 console.log("error", err); 203 process.exit(0); 204 } 205 ); 206 }; 207 208 module.exports = { exportIssues };