|
/* By Bo Ericsson, https://www.linkedin.com/in/boeric00/ */ |
|
|
|
/* eslint-disable no-console, func-names, no-bitwise, no-nested-ternary, no-restricted-syntax */ |
|
|
|
// References to elements already in the DOM |
|
const buttonGithub = document.querySelector('button.github'); |
|
const buttonGist = document.querySelector('button.gist'); |
|
const avatar = document.querySelector('.avatar'); |
|
const ownerMeta = document.querySelector('.owner-meta'); |
|
const outputContainer = document.querySelector('.output-container'); |
|
const input = document.querySelector('input'); |
|
|
|
// Set focus on the text field |
|
input.focus(); |
|
|
|
// Generates a table with the repos/gists and displays the response headers |
|
function generateTable(tableData, headers) { |
|
// Create table element |
|
const table = document.createElement('table'); |
|
table.className = 'table table-striped table-sm'; |
|
|
|
// Create the thead element |
|
const thead = document.createElement('thead'); |
|
|
|
// Create the thead contents |
|
const headerRow = document.createElement('tr'); |
|
Object.keys(tableData[0]).forEach((d) => { |
|
const th = document.createElement('th'); |
|
th.setAttribute('scope', 'col'); |
|
const textNode = document.createTextNode(d); |
|
th.appendChild(textNode); |
|
headerRow.appendChild(th); |
|
}); |
|
|
|
// Append the header row to thead |
|
thead.appendChild(headerRow); |
|
|
|
// Append the thead to the table |
|
table.appendChild(thead); |
|
|
|
// Create the tbody element |
|
const tbody = document.createElement('tbody'); |
|
|
|
tableData.forEach((rowData) => { |
|
// Create a table row for this row data |
|
const tr = document.createElement('tr'); |
|
|
|
// For each prop in rowData, create td cells and append to tr (order is not guaranteed) |
|
Object.keys(rowData).forEach((cellProp) => { |
|
const cellValue = rowData[cellProp]; |
|
const td = document.createElement('td'); |
|
const textNode = document.createTextNode(cellValue); |
|
td.appendChild(textNode); |
|
tr.appendChild(td); |
|
}); |
|
|
|
// Append the table row to tbody |
|
tbody.appendChild(tr); |
|
}); |
|
|
|
// Append the tbody to the table |
|
table.appendChild(tbody); |
|
|
|
// Append the table to the container |
|
outputContainer.appendChild(table); |
|
|
|
// Create a p element to hold the title for the header json string |
|
const title = document.createElement('p'); |
|
title.innerHTML = 'Response Headers'; |
|
|
|
// Inspect the headers |
|
const { |
|
'x-ratelimit-limit': limit, |
|
'x-ratelimit-remaining': remaining, |
|
'x-ratelimit-reset': reset, |
|
} = headers; |
|
|
|
// Get current time |
|
const now = Date.now() / 1000; |
|
// Compute when the Github API rate limit will be reset |
|
const resetWhenSec = ~~(+reset - now); |
|
const resetWhenMin = ~~(resetWhenSec / 60); |
|
// Create string that explains that |
|
const explainStr = `Request limit: ${limit}, Remaining: ${remaining}, Reset time: ${reset} (in ${resetWhenSec} sec, ${resetWhenMin} min)`; |
|
// Then create a p elem to hold the string |
|
const explain = document.createElement('p'); |
|
explain.innerHTML = explainStr; |
|
|
|
// Create a pre element to hold the stringified headers |
|
const json = document.createElement('pre'); |
|
json.innerHTML = JSON.stringify(headers, null, 2); |
|
|
|
// Create a container for the header info and add the just-created children |
|
const headerContainer = document.createElement('div'); |
|
headerContainer.appendChild(title); |
|
headerContainer.appendChild(json); |
|
headerContainer.appendChild(explain); |
|
|
|
// Append the header container to the DOM |
|
outputContainer.appendChild(headerContainer); |
|
} |
|
|
|
// Create data array of selected fields of each Github Repo, then call table renderer |
|
function createGithubTable(data, headers) { |
|
// Prep data |
|
const tableData = data.slice() |
|
// Sort data in newest-to-oldest order |
|
.sort((a, b) => (a.updated_at < b.updated_at ? 1 : a.updated_at > b.updated_at ? -1 : 0)) |
|
// Generate array of renderable table data |
|
.map((d, i) => { |
|
const { |
|
// A wealth of information is provided in the API response. Here are some of them |
|
// (and we're only using a few...) |
|
/* eslint-disable no-multi-spaces, indent */ |
|
// "archived": false, |
|
// "clone_url": "https://github.com/boeric/airbnb-to-eslintrc.git", |
|
// "created_at": "2018-11-22T02:12:10Z", |
|
// "default_branch": "master" |
|
// "description": "Converts Airbnb's style guide to single .eslintrc file", |
|
// "disabled": false, |
|
// "fork": false, |
|
forks, // "forks": 0, |
|
// "forks_count": 0, |
|
// "full_name": "boeric/airbnb-to-eslintrc", |
|
// "has_issues": true, |
|
// "has_pages": false, |
|
// "has_wiki": true, |
|
// "homepage": null, |
|
// "id": 158630182, |
|
// "language": "JavaScript", |
|
// "license": null, |
|
// "mirror_url": null, |
|
name, // "name": "airbnb-to-eslintrc", |
|
// "open_issues": 0, |
|
// "open_issues_count": 0, |
|
// "owner": Object with owner info |
|
// "private": false, |
|
// "pushed_at": "2018-11-25T19:23:58Z", |
|
// "size": 29, |
|
// "ssh_url": "[email protected]:boeric/airbnb-to-eslintrc.git", |
|
// "stargazers_count": 0, |
|
updated_at: updatedAt, // "updated_at": "2018-11-25T19:25:39Z", |
|
url, // "url": "https://api.github.com/repos/boeric/airbnb-to-eslintrc", |
|
// "watchers": 0, |
|
// "watchers_count": 0, |
|
} = d; |
|
/* eslint-enable no-multi-spaces, indent */ |
|
|
|
return { |
|
Repo: i + 1, |
|
'Repo Name': name, |
|
'Last Updated': updatedAt, |
|
Url: url, |
|
Forks: forks, |
|
}; |
|
}); |
|
|
|
// Generate the table |
|
generateTable(tableData, headers); |
|
} |
|
|
|
// Create data array of selected fields of each Github Gist, then call table renderer |
|
function createGistTable(data, headers) { |
|
// Prep data |
|
const tableData = data.slice() |
|
// Sort data in newest-to-oldest order |
|
.sort((a, b) => (a.updated_at < b.updated_at ? 1 : a.updated_at > b.updated_at ? -1 : 0)) |
|
// Generate array of renderable table data |
|
.map((d, i) => { |
|
const { |
|
// A wealth of information is provided in the API response. Here are some of them |
|
// (and we're only using a few...) |
|
/* eslint-disable no-multi-spaces, indent */ |
|
// "created_at": "2020-05-16T17:43:30Z", |
|
description, // "description": "Description...", |
|
files, // "files": Object with file references |
|
// "id": 4950f26655187c33bedba9728e98a3c2", |
|
// "owner": Object with owner info |
|
// "public": true, |
|
updated_at, // "updated_at": 2020-05-16T19:02:08Z", |
|
url, // "url": https://api.github.com/gists/4950f26655187c33bedba9728e98a3c2", |
|
} = d; |
|
/* eslint-enable no-multi-spaces, indent */ |
|
|
|
return { |
|
Gist: i + 1, |
|
Description: description, |
|
Files: Object.keys(files).length, |
|
'Last Updated': updated_at, |
|
Url: url, |
|
}; |
|
}); |
|
|
|
// Generate the table |
|
generateTable(tableData, headers); |
|
} |
|
|
|
// Fetch json from the github API |
|
function fetchData(url) { |
|
// This is an unusual construct as it combines the json returned from the API call with |
|
// the headers of said call |
|
return fetch(url) |
|
.then((response) => { |
|
const { |
|
headers, |
|
ok, |
|
status, |
|
statusText, |
|
} = response; |
|
|
|
// Test if fetch had error, if so throw |
|
if (!ok) { |
|
const errorStr = `${statusText} (${status})`; |
|
// This error will be handled by the caller's catch block |
|
throw Error(errorStr); |
|
} |
|
|
|
// Response header accumulator |
|
const responseHeaders = {}; |
|
// Fill the responseHeader object with each header |
|
for (const header of headers) { |
|
const prop = header[0]; |
|
const value = header[1]; |
|
responseHeaders[prop] = value; |
|
} |
|
|
|
// Add the response header object to the response promise |
|
response.responseHeaders = responseHeaders; |
|
// Return the response promise |
|
return response; |
|
}) |
|
.then((response) => { |
|
// Get the response header object from the response promise object |
|
const { responseHeaders } = response; |
|
// Return the settled promise |
|
return response.json() |
|
// Resolve the promise and return data and headers in a wrapped object |
|
.then((data) => ({ data, headers: responseHeaders })); |
|
}); |
|
} |
|
|
|
// Async function to obtain the Repo/Gist data from the Github API |
|
async function getData(type, user) { |
|
if (user.length === 0) { |
|
return; |
|
} |
|
|
|
// Clear containers |
|
avatar.innerHTML = ''; |
|
ownerMeta.innerHTML = ''; |
|
outputContainer.innerHTML = ''; |
|
|
|
// Build url |
|
const url = type === 'github' |
|
? `https://api.github.com/users/${user}/repos` |
|
: `https://api.github.com/users/${user}/gists`; |
|
|
|
try { |
|
// Disable the buttons while the data fetch is in flight |
|
buttonGithub.disabled = true; |
|
buttonGist.disabled = true; |
|
|
|
// Await the completion of the API call |
|
const wrapper = await fetchData(url); |
|
|
|
// Unwrap data and headers |
|
const { data, headers } = wrapper; |
|
|
|
// Log the data and headers |
|
console.log('data', data); |
|
console.log('headers', headers); |
|
|
|
// Enable the buttons |
|
buttonGithub.disabled = false; |
|
buttonGist.disabled = false; |
|
|
|
// Set focus on the text field |
|
input.focus(); |
|
|
|
// If an empty array cames back, show message to user and log empty array |
|
if (data.length === 0) { |
|
ownerMeta.innerHTML = 'Not found'; |
|
console.error(`Empty response, headers: ${headers}`); |
|
return; |
|
} |
|
|
|
// Get first item |
|
const item = data[0]; |
|
const { owner } = item; |
|
const { avatar_url: avatarUrl, id, login } = owner; |
|
|
|
// Create the owner avatar img |
|
const img = document.createElement('img'); |
|
img.src = avatarUrl; |
|
img.alt = login; |
|
avatar.appendChild(img); |
|
|
|
// Set owner metadata |
|
// Determine type |
|
const typeStr = type === 'github' ? 'Repos' : 'Gists'; |
|
// Determine Repo/Gist count (Github's response limit is 30 items, but more Repos/Gists |
|
// could be present...) |
|
const countStr = data.length === 30 ? `At least ${data.length}` : `${data.length}`; |
|
ownerMeta.innerHTML = `User: ${login}, Id: ${id}, ${typeStr}: ${countStr}`; |
|
|
|
// Create tables |
|
if (type === 'github') { |
|
createGithubTable(data, headers); |
|
} else { |
|
createGistTable(data, headers); |
|
} |
|
} catch (e) { |
|
// Show error |
|
ownerMeta.innerHTML = e; |
|
|
|
// Enable the buttons |
|
buttonGithub.disabled = false; |
|
buttonGist.disabled = false; |
|
} |
|
} |
|
|
|
// Github button click handler |
|
buttonGithub.addEventListener('click', function () { |
|
this.blur(); |
|
getData('github', input.value.trim()); |
|
}); |
|
|
|
// Gist button click handler |
|
buttonGist.addEventListener('click', function () { |
|
this.blur(); |
|
getData('gist', input.value.trim()); |
|
}); |