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:
M | README.md | | | 14 | ++++++++------ |
M | export.js | | | 234 | +++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------- |
M | index.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 === "") {