diff --git a/README.md b/README.md index 1613a3b0..8cada0e7 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,11 @@ # GitHub Tracker -Replace this readme with your own information about your project. - -Start by briefly describing the assignment in a sentence or two. Keep it short and to the point. +Week 7 of bootcamp, this is a github tracker tracking projects from the Technigo bootcamp using github api. The site is built mainly using JavaScript, its a heading with the github user avatars and a project list with all repos linked to the Technigo bootcamp. The project card shows some info regarding the status of the project. At the bottom there is a pie chart showing the progress in the bootcamp. The project/repo list os possible to sort and filter. ## The problem -Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next? +The challenge this week was to get the asynchronous nature of fetch to work with multiple fetches within a fetch, for this I used Promise.All. It was also a challenge to get the filters and sort working. ## View it live -Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about. +https://gustav-week7-github-tracker.netlify.app/ diff --git a/code/chart.js b/code/chart.js index 92e85a30..f78e7ec3 100644 --- a/code/chart.js +++ b/code/chart.js @@ -2,3 +2,50 @@ const ctx = document.getElementById('chart').getContext('2d') //"Draw" the chart here 👇 + +const pieData = { + labels: ['Projects to go', 'Projects done'], + datasets: [ + { + label: 'Technigo project progress', + data: [19, 0], + backgroundColor: ['RGB(22, 27, 33)', 'RGB(37, 166, 65)'], + borderColor: '#21262c', + hoverOffset: 4, + }, + ], +} + +const pieConfig = { + type: 'doughnut', + data: pieData, +} + +const pieChart = new Chart(ctx, pieConfig) + +const updatePieChart = (chart, newData) => { + chart.data.datasets.forEach(dataset => { + dataset.data.pop() + dataset.data.push(newData) + }) + chart.update() +} + +// const lineData = { +// // labels: ['Soon', 'Done'], +// datasets: [ +// { +// label: 'Technigo project progress', +// data: [19, 0], +// backgroundColor: ['rgb(255, 99, 132)', 'rgb(255, 205, 86)'], +// hoverOffset: 4, +// }, +// ], +// }; + +// const lineConfig = { +// type: 'line', +// data: lineData, +// }; + +// let lineChart = (ctx) => new Chart(ctx, lineConfig); diff --git a/code/index.html b/code/index.html index 2fb5e0ae..94ce4b69 100644 --- a/code/index.html +++ b/code/index.html @@ -1,21 +1,43 @@ - - - - - Project GitHub Tracker - - - -

GitHub Tracker

-

Projects:

-
+ + + + + Project GitHub Tracker + + + + + + +

GitHub Tracker

+
+

Projects:

+
+

Sort by:

+ + + + + +
+
+

Filter by:

+ + + +
+
- - + + - - - - \ No newline at end of file + + + + + diff --git a/code/script.js b/code/script.js index e69de29b..6f0dc829 100644 --- a/code/script.js +++ b/code/script.js @@ -0,0 +1,222 @@ +const projects = document.getElementById('projects') +const userData = document.getElementById('userData') +const sortSizeBtn = document.getElementById('sortSizeBtn') +const sortUpdateBtn = document.getElementById('sortUpdateBtn') +const sortCreatedBtn = document.getElementById('sortCreatedBtn') +const sortCommitsBtn = document.getElementById('sortCommitsBtn') +const sortNameBtn = document.getElementById('sortNameBtn') +const filterAllBtn = document.getElementById('filterAllBtn') +const filterLangJsBtn = document.getElementById('filterLangJsBtn') +const filterLangHtmlBtn = document.getElementById('filterLangHtmlBtn') + +const USER = 'gustavfrid' +const PARENT_OWNER = 'Technigo' +const REPOS_URL = `https://api.github.com/users/${USER}/repos` +const USER_URL = `https://api.github.com/users/${USER}` + +let reposArr = [] +let reposData = {} + +const pullReqData = { + total: 19, + done: 0, +} + +const fetchAllReposFromUser = () => { + // fetch all repos from user + fetch(REPOS_URL) + .then(res => res.json()) + .then(allRepos => { + // filter forked repos + let filteredRepos = allRepos.filter( + repo => repo.name.includes('project-') && repo.fork + // || (repo.name.includes('portfolio') && repo.fork) + ) + sortRepos(filteredRepos, 'pushed_at', true) + fetchFullRepo(filteredRepos) + }) + .catch(err => alert('fetchAllReposFromUser error: ', err)) +} + +const fetchFullRepo = repos => { + // fetch all data from each repo + Promise.all(repos.map(repo => fetch(repo.url))) + .then(res => Promise.all(res.map(res => res.json()))) + .then(repos => { + repos.forEach(repo => { + if (repo.parent.owner.login === PARENT_OWNER) { + // push data to store for use when sorting projects + reposArr.push(repo) + reposData[repo.name] = { pullRequest: '', authors: '', commits: '' } + + generateProjectCard(repo) + const COMMITS_URL = `https://api.github.com/repos/${USER}/${repo.name}/commits?per_page=100` + fetchCommits(COMMITS_URL, repo) + } + }) + }) + .catch(err => alert('fetchFullRepo error:', err)) +} + +const generateProjectCard = repo => { + // generating project cards + projects.innerHTML += /*html*/ ` +
+
+

${repo.name}

+

${repo.language}

+
+

From ${repo.parent.owner.login}, default branch: ${repo.default_branch}

+

Pull request

+
+

Commits:

+

Size: ${repo.size}

+
+

Updated: ${new Date(repo.pushed_at).toDateString()}

+

Commit authors:

+
+ ` +} + +const fetchCommits = (myCommitsUrl, repo) => { + // fetch all commits from repo + fetch(myCommitsUrl) + .then(res => res.json()) + .then(data => { + // filter only commits done after repo was forked + const commitsSinceFork = data.filter(commit => commit.commit.author.date > repo.created_at) + // storing necessary data for later sorting + reposData[repo.name].commits = commitsSinceFork + populateCommits(repo, commitsSinceFork) + getCollaborators(commitsSinceFork, repo) + }) + .catch(err => alert('fetchCommits error: ', repo.name, err)) +} + +const populateCommits = (repo, commits) => { + document.getElementById(`commit-${repo.name}`).innerHTML += ` ${commits.length}, ` +} + +const getCollaborators = (commits, repo) => { + // filter out each commit author and stor for later sort + let authors = {} + commits.forEach(commit => { + if (!Object.keys(authors).includes(commit.author.login)) { + authors[commit.author.login] = { + avatar_url: commit.author.avatar_url, + html_url: commit.author.html_url, + } + } + }) + reposData[repo.name].authors = authors + populateCollaborators(authors, repo) + fetchPullRequestsArray(repo, Object.keys(authors)) +} + +const populateCollaborators = (authors, repo) => { + for (const author in authors) { + if (Object.keys(authors).length > 1) { + document.getElementById( + `collaborators-${repo.name}` + ).innerHTML += /*html*/ `` + } else { + document.getElementById(`collaborators-${repo.name}`).innerHTML = + /*html*/ 'Individual project' + } + } +} + +const fetchPullRequestsArray = (repo, authors) => { + // fetch all pull requests from repo + const PULL_URL = `https://api.github.com/repos/${PARENT_OWNER}/${repo.name}/pulls?per_page=100` + fetch(PULL_URL) + .then(res => res.json()) + .then(data => { + // only pick pull requests connected to user + const myPullReq = data.find(pull => authors.includes(pull.user.login)) + if (myPullReq) { + pullReqData.done++ + reposData[repo.name].pullRequest = myPullReq + updatePieChart(pieChart, pullReqData.done) + } + populatePullRequest(myPullReq, repo) + }) + .catch(err => alert('fetchPullRequestsArray error:', err)) +} + +const populatePullRequest = (myPullReq, repo) => { + if (myPullReq) { + document.getElementById( + `pull-${repo.name}` + ).innerHTML = /*html*/ `Pull request` + } else { + document.getElementById(`pull-${repo.name}`).innerHTML = /*html*/ 'No pull request done :(' + } +} + +const fetchUser = () => { + fetch(USER_URL) + .then(res => res.json()) + .then(data => { + userData.innerHTML += /*html*/ `

${data.login}

` + }) + .catch(err => alert('fetchCommits error: ', err)) +} + +const sortRepos = (array, param, init) => { + array.sort((a, b) => { + if (a[param] > b[param]) { + return -1 + } else if (a[param] < b[param]) { + return 1 + } else { + return 0 + } + }) + if (param === 'name') array.reverse() + if (!init) regenerateProjectCards(reposArr) +} + +const sortReposByCommit = (array, param) => { + array.sort((a, b) => { + if (reposData[a[param]].commits > reposData[b[param]].commits) { + return -1 + } else if (reposData[a[param]].commits < reposData[b[param]].commits) { + return 1 + } else { + return 0 + } + }) + regenerateProjectCards(reposArr) +} + +const filterRepos = (array, lang) => { + if (lang !== 'All') { + const filteredReposArr = array.filter(repo => repo.language === lang) + regenerateProjectCards(filteredReposArr) + } else { + regenerateProjectCards(array) + } +} + +const regenerateProjectCards = repos => { + projects.innerHTML = '' + repos.forEach(repo => { + generateProjectCard(repo) + populateCommits(repo, reposData[repo.name].commits) + populateCollaborators(reposData[repo.name].authors, repo) + populatePullRequest(reposData[repo.name].pullRequest, repo) + }) +} + +sortSizeBtn.addEventListener('click', () => sortRepos(reposArr, 'size', false)) +sortUpdateBtn.addEventListener('click', () => sortRepos(reposArr, 'updated_at', false)) +sortCreatedBtn.addEventListener('click', () => sortRepos(reposArr, 'created_at', false)) +sortCommitsBtn.addEventListener('click', () => sortReposByCommit(reposArr, 'name')) +sortNameBtn.addEventListener('click', () => sortRepos(reposArr, 'name', false)) +filterLangJsBtn.addEventListener('click', () => filterRepos(reposArr, 'JavaScript')) +filterLangHtmlBtn.addEventListener('click', () => filterRepos(reposArr, 'HTML')) +filterAllBtn.addEventListener('click', () => filterRepos(reposArr, 'All')) + +fetchAllReposFromUser() +fetchUser() diff --git a/code/style.css b/code/style.css index 7c8ad447..a8171b55 100644 --- a/code/style.css +++ b/code/style.css @@ -1,3 +1,182 @@ -body { - background: #FFECE9; -} \ No newline at end of file +.body { + display: flex; + flex-direction: column; + align-items: center; + font-family: 'Montserrat', sans-serif; + background-color: #0d1017; + color: white; + max-width: 400px; + margin: auto; +} + +.header { + text-align: center; +} + +.user-data { + display: flex; + flex-direction: column; + gap: 4px; + align-items: center; +} +.user-name { + font-size: 30px; + margin: 0; +} +.avatar-user { + width: 100%; + border-radius: 50%; +} +.avatar-user:hover { + border: 3px solid #21262c; +} + +.project-header { + text-align: center; +} + +.avatar-collaborator { + box-sizing: border-box; + height: 30px; + border-radius: 50%; + padding: 0 2px; +} + +.avatar-collaborator:hover { + border: 1px solid #21262c; +} + +.filters-sorting-heading { + margin: 0; +} + +.filters-sorting { + display: flex; + gap: 5px; + align-items: center; + margin: 5px 0; +} + +.filters-sorting button { + height: 30px; + border: 1px solid #21262c; + border-radius: 10px; + margin: 0; + background-color: #21262c; + color: white; +} + +.projects { + display: flex; + flex-direction: column; + justify-content: center; + gap: 10px; +} + +.chart { + margin: 40px; +} + +.repo-info { + display: flex; + flex-wrap: wrap; + flex-direction: column; + border: 2px solid #30353b; + border-radius: 5px; + padding: 10px; + margin: 0; +} +.repo-heading { + display: flex; + justify-content: space-between; + align-items: center; +} +.numbers { + display: flex; + gap: 4px; +} + +.repo { + font-size: 20px; + margin: 5px 0; +} + +.lang { + border-radius: 10px; + color: black; + padding: 0 4px; +} +.JavaScript { + background-color: #f1e15a; + border: 1px solid #f1e15a; +} +.HTML, +.CSS { + background-color: #e44c27; + border: 1px solid #e44c27; +} + +.info, +.update, +.commit, +.size, +.pull, +.collaboration { + margin: 2px 0; + display: flex; + align-items: center; +} + +/* unvisited link */ +a:link { + color: #579def; + text-decoration: none; +} + +/* visited link */ +a:visited { + color: #579def; + text-decoration: none; +} + +/* mouse over link */ +a:hover { + color: #579def; + text-decoration: underline; +} + +/* selected link */ +/* a:active { + color: blue; +} */ + +@media (min-width: 768px) { + .body { + max-width: 990px; + } + .projects { + flex-direction: row; + flex-wrap: wrap; + } + .repo-info { + width: 45%; + } +} + +@media (min-width: 992px) { + .body { + max-width: 1100px; + } + .repo-info { + width: 30%; + } +} + +@media (min-width: 1200px) { + .body { + max-width: 1500px; + } + .repo-info { + width: 22%; + } +}