commit cc8bbeacb49fbb72d4cbffa68e4f5ef5a7c5ba31
parent 9da15c658bf6b36cac9deeefc1345eee19513264
Author: Gavin Rehkemper <gavinr@users.noreply.github.com>
Date: Sat, 18 Apr 2020 21:09:41 -0500
Export Issues (#13)
export functionality
Diffstat:
M | CHANGELOG.md | | | 8 | +++++++- |
M | README.md | | | 22 | +++++++++++++++++++--- |
A | export.js | | | 166 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | import.js | | | 102 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | index.js | | | 125 | +++++++++++++------------------------------------------------------------------ |
M | package-lock.json | | | 19 | +++++++++++++++++++ |
M | package.json | | | 5 | +++-- |
7 files changed, 336 insertions(+), 111 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
@@ -4,6 +4,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
+## [1.0.0] - 2020-04-18
+
+## Added
+- Exporting issues is now supported. Just call `githubCsvTools` with no file input. See `githubCsvTools --help` for more info.
+
## [0.4.0] - 2020-04-18
## Changed
@@ -36,7 +41,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Basic CSV import functionality.
- A few basic tests
-[Unreleased]: https://github.com/gavinr/github-csv-tools/compare/v0.4.0...HEAD
+[Unreleased]: https://github.com/gavinr/github-csv-tools/compare/v1.0.0...HEAD
+[1.0.0]: https://github.com/gavinr/github-csv-tools/compare/v0.4.0...v1.0.0
[0.4.0]: https://github.com/gavinr/github-csv-tools/compare/V0.3.0...v0.4.0
[0.3.0]: https://github.com/gavinr/github-csv-tools/compare/v0.2.0...V0.3.0
[0.2.0]: https://github.com/gavinr/github-csv-tools/compare/v0.1.0...v0.2.0
diff --git a/README.md b/README.md
@@ -5,10 +5,26 @@ Currently imports title, description, labels, and milestones.
## Usage
- 1. `npm install -g github-csv-tools`
- 2. `githubCsvTools myFile.csv`
+`githubCsvTools --help` for info on how to use the command line tool.
-`githubCsvTools --help` for info on how to use the command line tool
+First run `npm install -g github-csv-tools` to install. Then:
+
+### To Import Issues
+
+`githubCsvTools myFile.csv`
+
+### To Export Issues
+
+`githubCsvTools`
+
+### Tokens
+
+For all actions, you'll need a GitHub token:
+
+1. Go to https://github.com/settings/tokens
+2. Click "Generate New Token"
+3. Check on `repo`
+4. Copy the token provided - this tool will ask for it.
## Development
diff --git a/export.js b/export.js
@@ -0,0 +1,166 @@
+// const csv = require("csv");
+const fs = require("fs");
+const converter = require("json-2-csv");
+
+// Gets a single comment
+const getComment = async (octokit, values, issueNumber) => {
+ return new Promise((resolve, reject) => {
+ const issueOptions = octokit.issues.listComments.endpoint.merge({
+ owner: values.userOrOrganization,
+ repo: values.repo,
+ issue_number: issueNumber,
+ });
+ octokit.paginate(issueOptions).then(
+ (commentsData) => {
+ resolve(commentsData);
+ },
+ (err) => {
+ console.error(err);
+ reject(err);
+ }
+ );
+ });
+};
+
+// 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 fullComments = [];
+ for (let i = 0; i < data.length; i++) {
+ const issueObject = data[i];
+ fullComments.push({
+ issue: issueObject,
+ });
+ const commentsData = await getComment(octokit, values, issueObject.number);
+ commentsData.forEach((comment) => {
+ fullComments.push({
+ issue: issueObject,
+ comment: {
+ user: comment.user.login,
+ created_at: comment.created_at,
+ updated_at: comment.updated_at,
+ body: comment.body,
+ },
+ });
+ });
+ }
+ return fullComments;
+};
+
+const twoPadNumber = (number) => {
+ return String(number).padStart(2, "0");
+};
+
+const exportIssues = (octokit, values, includeComments = false) => {
+ // Getting all the issues:
+ const options = octokit.issues.listForRepo.endpoint.merge({
+ owner: values.userOrOrganization,
+ repo: values.repo,
+ state: "all",
+ });
+ 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 columnsToRemove = [
+ "url",
+ "repository_url",
+ "labels_url",
+ "comments_url",
+ "events_url",
+ "html_url",
+ "id",
+ "node_id",
+ // "number",
+ // "title",
+
+ // "labels",
+ // "state",
+ "locked",
+ "assignee",
+ // "assignees",
+ // "milestone",
+ // "comments",
+ // "created_at",
+ // "updated_at",
+ // "closed_at",
+ "author_association",
+ "body",
+ "pull_request",
+ "milestone",
+ ];
+ data.forEach((issueObject) => {
+ columnsToRemove.forEach((param) => {
+ delete issueObject[param];
+ });
+ });
+
+ let csvData = data;
+ 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);
+ }
+
+ converter.json2csv(csvData, (err, csvString) => {
+ if (err) {
+ console.error("error converting!");
+ process.exit(0);
+ }
+ // console.log("csvString:", csvString);
+ const now = new Date();
+ let fileName = `${now.getFullYear()}-${twoPadNumber(
+ now.getMonth()
+ )}-${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);
+ process.exit(0);
+ }
+ );
+};
+
+module.exports = { exportIssues };
diff --git a/import.js b/import.js
@@ -0,0 +1,102 @@
+const csv = require("csv");
+const fs = require("fs");
+
+const { createIssue } = require("./helpers.js");
+
+const importFile = (octokit, file, values) => {
+ fs.readFile(file, "utf8", (err, data) => {
+ if (err) {
+ console.error("Error reading file.");
+ process.exit(1);
+ }
+ csv.parse(
+ data,
+ {
+ trim: true,
+ },
+ (err, csvRows) => {
+ if (err) throw err;
+ var cols = csvRows[0];
+ csvRows.shift();
+
+ // get indexes of the fields we need
+ var titleIndex = cols.indexOf("title");
+ var bodyIndex = cols.indexOf("description");
+ var labelsIndex = cols.indexOf("labels");
+ var milestoneIndex = cols.indexOf("milestone");
+ var assigneeIndex = cols.indexOf("assignee");
+ var stateIndex = cols.indexOf("state");
+
+ if (titleIndex === -1) {
+ console.error("Title required by GitHub, but not found in CSV.");
+ process.exit(1);
+ }
+ const createPromises = csvRows.map((row) => {
+ var sendObj = {
+ owner: values.userOrOrganization,
+ repo: values.repo,
+ title: row[titleIndex],
+ };
+
+ // if we have a body column, pass that.
+ if (bodyIndex > -1) {
+ sendObj.body = row[bodyIndex];
+ }
+
+ // if we have a labels column, pass that.
+ if (labelsIndex > -1 && row[labelsIndex] !== "") {
+ sendObj.labels = row[labelsIndex].split(",");
+ }
+
+ // if we have a milestone column, pass that.
+ if (milestoneIndex > -1 && row[milestoneIndex] !== "") {
+ sendObj.milestone = row[milestoneIndex];
+ }
+
+ // if we have an assignee column, pass that.
+ if (assigneeIndex > -1 && row[assigneeIndex] !== "") {
+ sendObj.assignees = row[assigneeIndex].replace(/ /g, "").split(",");
+ }
+
+ // console.log("sendObj", sendObj);
+ let state = false;
+ if (stateIndex > -1 && row[stateIndex] === "closed") {
+ state = row[stateIndex];
+ }
+ return createIssue(octokit, sendObj, state);
+ });
+
+ Promise.all(createPromises).then(
+ (res) => {
+ const successes = res.filter((cr) => {
+ return cr.status === 200 || cr.status === 201;
+ });
+ const fails = res.filter((cr) => {
+ return cr.status !== 200 && cr.status !== 201;
+ });
+
+ console.log(
+ `Created ${successes.length} issues, and had ${fails.length} failures.`
+ );
+ console.log(
+ "❤ ❗ If this project has provided you value, please ⭐ star the repo to show your support: ➡ https://github.com/gavinr/github-csv-tools"
+ );
+
+ if (fails.length > 0) {
+ console.log(fails);
+ }
+
+ process.exit(0);
+ },
+ (err) => {
+ console.error("Error");
+ console.error(err);
+ process.exit(0);
+ }
+ );
+ }
+ );
+ });
+};
+
+module.exports = { importFile };
diff --git a/index.js b/index.js
@@ -7,22 +7,25 @@ const prompt = require("co-prompt");
const { Octokit } = require("@octokit/rest");
const { throttling } = require("@octokit/plugin-throttling");
-const { createIssue } = require("./helpers.js");
-
-const csv = require("csv");
-const fs = require("fs");
+const { importFile } = require("./import.js");
+const { exportIssues } = require("./export.js");
program
- .version("0.4.0")
- .arguments("<file>")
+ .version("1.0.0")
+ .arguments("[file]")
.option(
- "--github_enterprise [https://api.github.my-company.com]",
+ "-g, --github_enterprise [https://api.github.my-company.com]",
"Your GitHub Enterprise URL."
)
.option(
- "--token [token]",
+ "-t, --token [token]",
"The GitHub token. https://github.com/settings/tokens"
)
+ .option(
+ "-f, --exportFileName [export.csv]",
+ "The name of the CSV you'd like to export to."
+ )
+ .option("-c, --exportComments", "Include comments in the export.")
.action(function (file, options) {
co(function* () {
var retObject = {};
@@ -34,6 +37,8 @@ program
"token (get from https://github.com/settings/tokens): "
);
}
+ retObject.exportFileName = options.exportFileName || false;
+ retObject.exportComments = options.exportComments || false;
retObject.userOrOrganization = yield prompt("user or organization: ");
retObject.repo = yield prompt("repo: ");
return retObject;
@@ -65,103 +70,13 @@ program
},
});
- fs.readFile(file, "utf8", (err, data) => {
- if (err) {
- console.error("Error reading file.");
- process.exit(1);
- }
- csv.parse(
- data,
- {
- trim: true,
- },
- (err, csvRows) => {
- if (err) throw err;
- var cols = csvRows[0];
- csvRows.shift();
-
- // get indexes of the fields we need
- var titleIndex = cols.indexOf("title");
- var bodyIndex = cols.indexOf("description");
- var labelsIndex = cols.indexOf("labels");
- var milestoneIndex = cols.indexOf("milestone");
- var assigneeIndex = cols.indexOf("assignee");
- var stateIndex = cols.indexOf("state");
-
- if (titleIndex === -1) {
- console.error(
- "Title required by GitHub, but not found in CSV."
- );
- process.exit(1);
- }
- const createPromises = csvRows.map((row) => {
- var sendObj = {
- owner: values.userOrOrganization,
- repo: values.repo,
- title: row[titleIndex],
- };
-
- // if we have a body column, pass that.
- if (bodyIndex > -1) {
- sendObj.body = row[bodyIndex];
- }
-
- // if we have a labels column, pass that.
- if (labelsIndex > -1 && row[labelsIndex] !== "") {
- sendObj.labels = row[labelsIndex].split(",");
- }
-
- // if we have a milestone column, pass that.
- if (milestoneIndex > -1 && row[milestoneIndex] !== "") {
- sendObj.milestone = row[milestoneIndex];
- }
-
- // if we have an assignee column, pass that.
- if (assigneeIndex > -1 && row[assigneeIndex] !== "") {
- sendObj.assignees = row[assigneeIndex]
- .replace(/ /g, "")
- .split(",");
- }
-
- // console.log("sendObj", sendObj);
- let state = false;
- if (stateIndex > -1 && row[stateIndex] === "closed") {
- state = row[stateIndex];
- }
- return createIssue(octokit, sendObj, state);
- });
-
- Promise.all(createPromises).then(
- (res) => {
- const successes = res.filter((cr) => {
- return cr.status === 200 || cr.status === 201;
- });
- const fails = res.filter((cr) => {
- return cr.status !== 200 && cr.status !== 201;
- });
-
- console.log(
- `Created ${successes.length} issues, and had ${fails.length} failures.`
- );
- console.log(
- "❤ ❗ If this project has provided you value, please ⭐ star the repo to show your support: ➡ https://github.com/gavinr/github-csv-tools"
- );
-
- if (fails.length > 0) {
- console.log(fails);
- }
-
- process.exit(0);
- },
- (err) => {
- console.error("Error");
- console.error(err);
- process.exit(0);
- }
- );
- }
- );
- });
+ if (file) {
+ // This is an import!
+ importFile(octokit, file, values);
+ } else {
+ // this is an export!
+ exportIssues(octokit, values);
+ }
},
function (err) {
console.error("ERROR", err);
diff --git a/package-lock.json b/package-lock.json
@@ -415,6 +415,11 @@
"ms": "^2.1.1"
}
},
+ "deeks": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/deeks/-/deeks-2.2.4.tgz",
+ "integrity": "sha512-T8HtfilZeYvVRmTx9J6Nk4e0ayb/RqHx/XBOJpUybQyedRRtVw0I21pezA+OjXoCnL5YkkjCAM2TFvSJt0NwWw=="
+ },
"deep-is": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
@@ -435,6 +440,11 @@
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
},
+ "doc-path": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/doc-path/-/doc-path-2.0.2.tgz",
+ "integrity": "sha512-7KAEFeGh+xlqATVLXywN6g2U6RF7CW1la80BdtRF3BaiP1rz3r1UCewUeOnwpJ9A44LX66u32dYaU9zIqC1/TQ=="
+ },
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -1255,6 +1265,15 @@
"esprima": "^4.0.0"
}
},
+ "json-2-csv": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/json-2-csv/-/json-2-csv-3.6.2.tgz",
+ "integrity": "sha512-aJGagjPIZj9iqAO8UVcNggVuRpxbMvG3WspGntcGm1Kxb3TFCwWC4U36qoybVkkm7n4ivDaVjwBd4PPlQJkP3Q==",
+ "requires": {
+ "deeks": "2.2.4",
+ "doc-path": "2.0.2"
+ }
+ },
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
diff --git a/package.json b/package.json
@@ -1,6 +1,6 @@
{
"name": "github-csv-tools",
- "version": "0.4.0",
+ "version": "1.0.0",
"description": "Tools to import and export, via CSV, from GitHub.",
"main": "index.js",
"scripts": {
@@ -24,7 +24,8 @@
"co": "^4.6.0",
"co-prompt": "^1.0.0",
"commander": "^5.0.0",
- "csv": "^5.3.2"
+ "csv": "^5.3.2",
+ "json-2-csv": "^3.6.2"
},
"devDependencies": {
"eslint": "^6.8.0",