Skip to content

Commit

Permalink
⚡ Fetch anonymous email from GitHub
Browse files Browse the repository at this point in the history
By default you will not need a PAT to query GitHub to fetch user details because it will generate the users anonymous email.

#101
  • Loading branch information
rkotze committed Jul 31, 2022
1 parent ad5e1f1 commit dfb063b
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 39 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Follows [Semantic Versioning](https://semver.org/).

- Global Git Mob means that you will have the same selected co-authors when switching between projects. Read more on [migrating from local commit template](https://github.com/rkotze/git-mob-vs-code/discussions/120).
- Reload icon added to project folder item.
- By default fetch anonymous email from GitHub [issue 101](https://github.com/rkotze/git-mob-vs-code/issues/101)

## 1.16.0 -- 2020-03-03

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,9 @@ Add a new author directly to 'Co-authoring' from 'More Authors'.

### Github -> Personal Access Token

Search for co-authors on GitHub you will need to generate a PAT.
*Optional*: if you want the users public email then you will need the PAT.

Default will return the anonymous GitHub email.

1. Visit [GitHub > settings > tokens](https://github.com/settings/tokens)
2. Click "generate new token"
Expand Down
51 changes: 33 additions & 18 deletions src/commands/github-authors.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
const vscode = require("vscode");
const { get } = require("../github/github-api");
const { getGitHubPat } = require("../ext-config/config");
const { saveNewCoAuthors } = require("../git/git-mob-api");
const { RepoAuthor } = require("../co-author-tree-provider/repo-authors");

function searchGithubAuthors() {
return vscode.commands.registerCommand(
"gitmob.searchGithubAuthors",
async function () {
const gitHubPat = getGitHubPat();
if (!gitHubPat) {
vscode.window.showErrorMessage(
"Missing GitHub PAT. Update settings with valid PAT."
);
return;
}
const searchText = await vscode.window.showInputBox({
placeHolder: "Try the name of person, email or username",
validateInput(value) {
Expand All @@ -27,9 +20,17 @@ function searchGithubAuthors() {
if (typeof searchText === "undefined") return null;

const searchUsers = await get("search/users?q=" + searchText);
const users = await Promise.all(
searchUsers.data.items.map((item) => get(item.url))
);
let users = [];
if (searchUsers.statusCode <= 400) {
users = await Promise.all(
searchUsers.data.items.map((item) => get(item.url))
);
} else {
vscode.window.showErrorMessage(
"Request to GitHub failed: " + searchUsers.data.message
);
return;
}

if (searchUsers.data.total_count === 0) {
vscode.window.showInformationMessage("No users found!");
Expand All @@ -38,8 +39,7 @@ function searchGithubAuthors() {
const messageUnder30 = `Git Mob: Showing ${searchUsers.data.total_count} GitHub users.`;
const messageOver30 = `Git Mob: Can only showing 30 of ${searchUsers.data.total_count} GitHub users.`;
vscode.window.showInformationMessage(
(searchUsers.data.total_count > 30 ? messageOver30 : messageUnder30) +
" Please select users with an email."
searchUsers.data.total_count > 30 ? messageOver30 : messageUnder30
);

const selectedAuthor = await quickPickAuthors(users);
Expand All @@ -56,14 +56,29 @@ function searchGithubAuthors() {
}

async function quickPickAuthors(repoAuthors) {
const authorTextArray = repoAuthors.map(({ data }) => ({
label: `${data.name} ${data.login}`,
description: `<${data.email ? data.email : "no email"}>`,
repoAuthor: { ...data, key: data.login },
}));
const authorTextArray = repoAuthors.map(({ data }) => {
const repoAuthor = new RepoAuthor(
data.name,
composeEmail(data.email, data.id, data.login),
data.login
);

return {
label: `${data.name} ${data.login}`,
description: `<${repoAuthor.email}>`,
repoAuthor: { ...repoAuthor, key: repoAuthor.commandKey },
};
});
return await vscode.window.showQuickPick(authorTextArray, {
matchOnDescription: true,
});
}

function composeEmail(email, id, username) {
if (email) {
return email;
}
return `${id}+${username}@users.noreply.github.com`;
}

exports.searchGithubAuthors = searchGithubAuthors;
5 changes: 4 additions & 1 deletion src/github/fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ function fetch(url, options) {
});

response.on("end", () => {
fulfil(data);
fulfil({
statusCode: response.statusCode,
data,
});
});
})
.on("error", (error) => {
Expand Down
18 changes: 10 additions & 8 deletions src/github/github-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@ const GITHUB_API = "https://api.github.com/";

async function get(url) {
const pat = getGitHubPat();
if (!pat) {
throw new Error("No GitHub personal access token found");
let headers = {
Accept: "application/vnd.github.v3+json",
};
if (pat) {
headers.Authorization = `token ${pat}`;
}

const gitUrl = new URL(url, GITHUB_API);
const result = await fetch(gitUrl, {
const { statusCode, data } = await fetch(gitUrl, {
method: "GET",
headers: {
Accept: "application/vnd.github.v3+json",
Authorization: `token ${pat}`,
},
headers,
});

return {
data: JSON.parse(result),
statusCode,
data: JSON.parse(data),
};
}

Expand Down
30 changes: 19 additions & 11 deletions src/github/github-api.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ const { get } = require("./github-api");
jest.mock("./fetch");

describe("requests to github api", function () {
it("fetch data successfully", async function () {
workspace.getConfiguration.mockReturnValue({
it("fetch data successfully with access token", async function () {
workspace.getConfiguration.mockReturnValueOnce({
get() {
return "abc";
},
});
fetch.mockResolvedValue(`{"items":[1]}`);
fetch.mockResolvedValue({ statusCode: 200, data: `{"items":[1]}` });
const result = await get("search/users");
expect(fetch).toHaveBeenCalled();
expect(fetch.mock.calls[0][0].toString()).toEqual(
Expand All @@ -25,18 +25,26 @@ describe("requests to github api", function () {
method: "GET",
});

expect(result).toEqual({ data: { items: [1] } });
expect(result).toEqual({ statusCode: 200, data: { items: [1] } });
});

it("no api key found", async function () {
workspace.getConfiguration.mockReturnValue({
get() {
return null;
it("fetch data successfully without access token", async function () {
workspace.getConfiguration.mockReturnValueOnce({
get() {},
});
fetch.mockResolvedValue({ statusCode: 200, data: `{"items":[1]}` });
const result = await get("search/users");
expect(fetch).toHaveBeenCalled();
expect(fetch.mock.calls[0][0].toString()).toEqual(
"https://api.github.com/search/users"
);
expect(fetch.mock.calls[0][1]).toEqual({
headers: {
Accept: "application/vnd.github.v3+json",
},
method: "GET",
});

await expect(get("search/users")).rejects.toThrow(
"No GitHub personal access token found"
);
expect(result).toEqual({ statusCode: 200, data: { items: [1] } });
});
});

0 comments on commit dfb063b

Please sign in to comment.