-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
First merge if Backend branch vs v.2.x #22
base: v.2.x
Are you sure you want to change the base?
Changes from all commits
8dfee0f
1c6c27f
08a679f
051bc60
4d53feb
43ad358
b60baa7
60fc209
442731c
2ae910a
b834e05
26b0764
be9fdce
657c5f7
43c044a
89cf812
71f9ce4
3513793
d760a02
5490e7e
b0c97f5
48e5911
8f2f509
b6e5e6e
1cf184a
f89f0ca
071d978
cb8c7e8
5361434
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
.vscode | ||
/server | ||
.DS_Store | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2,39 +2,70 @@ import React, { useState, useEffect } from 'react'; | |||||||||||||||||
import './App.css'; | ||||||||||||||||||
import Dashboard from './Components/Dashboard/Dashboard'; | ||||||||||||||||||
import LoginPage from './Components/LoginPage/LoginPage'; | ||||||||||||||||||
import PrivateRoute from './Components/PrivateRoute/PrivateRoute'; | ||||||||||||||||||
import { | ||||||||||||||||||
BrowserRouter as Switch, | ||||||||||||||||||
Route, | ||||||||||||||||||
BrowserRouter, | ||||||||||||||||||
Redirect, | ||||||||||||||||||
} from 'react-router-dom'; | ||||||||||||||||||
import { getIdFromLocation } from './helperFunc'; | ||||||||||||||||||
|
||||||||||||||||||
function App() { | ||||||||||||||||||
const [token, setToken] = useState(''); | ||||||||||||||||||
const [username, setUsername] = useState(''); | ||||||||||||||||||
const [offline, setOffline] = useState(!navigator.onLine); | ||||||||||||||||||
const [token, setToken] = useState(localStorage.getItem('token') || ''); | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can see a very common pattern where you want to Load/Save state from the local storage and set it in the state. I'd think about having a custom hook for that. What do you think? |
||||||||||||||||||
const [username, setUsername] = useState( | ||||||||||||||||||
localStorage.getItem('username') || '' | ||||||||||||||||||
); | ||||||||||||||||||
|
||||||||||||||||||
/* the user can access the app through two different options: | ||||||||||||||||||
1. indicating username and token manually. In the case of GitHub Enterprise the user also needs to indicate the endpoint. | ||||||||||||||||||
2. Github authentication */ | ||||||||||||||||||
Comment on lines
+20
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a very common practice to use block comment like this:
Suggested change
But this is totally up to you :) |
||||||||||||||||||
|
||||||||||||||||||
useEffect(() => { | ||||||||||||||||||
if (localStorage.getItem('token')) { | ||||||||||||||||||
setToken(localStorage.getItem('token')); | ||||||||||||||||||
setUsername(localStorage.getItem('username')); | ||||||||||||||||||
// in case that the user is authenticated through github, an id will be attached to the url. | ||||||||||||||||||
const userId = getIdFromLocation(); | ||||||||||||||||||
if (userId) { | ||||||||||||||||||
// if there's id, we need to look for the token stored in the ddbb | ||||||||||||||||||
fetch(`http://localhost:8080/users/${userId}`) | ||||||||||||||||||
martacolombas marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you thought about using environment variables to set the API_URL? I think it could be a good step to upload your code without needing to app another PR to change this :)
Suggested change
|
||||||||||||||||||
.then(res => { | ||||||||||||||||||
return res.text(); | ||||||||||||||||||
}) | ||||||||||||||||||
Comment on lines
+30
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we use arrow return syntax instead? I think we can save a couple of lines :)
Suggested change
|
||||||||||||||||||
.then(data => { | ||||||||||||||||||
try { | ||||||||||||||||||
const parsedData = JSON.parse(data); | ||||||||||||||||||
// save the token in localstorage | ||||||||||||||||||
localStorage.setItem('token', parsedData.token); | ||||||||||||||||||
localStorage.setItem('username', parsedData.username); | ||||||||||||||||||
// we change the state of the component since now the user is authenticated | ||||||||||||||||||
assignCredentials(parsedData.username, parsedData.token); | ||||||||||||||||||
// we redirect to the dashboard | ||||||||||||||||||
window.location.href = '/dashboard'; | ||||||||||||||||||
} catch (e) { | ||||||||||||||||||
console.error( | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This process is quite important since we're setting the session for the user. It could be interesting to have error tracking on this part of the code since could be a source of error. New Relic allow you to manually send caught errors to the browser agent: // `newrelic` is a global variable declared by the New Relic snippet loaded in the HTML.
newrelic && newrelic.noticeError(error) I recommend you to "safely" call import {noticeError} from '../utils/monitor';
noticeError(error) |
||||||||||||||||||
`Error in parsing json data while requesting user by Id : ${e}` | ||||||||||||||||||
); | ||||||||||||||||||
} | ||||||||||||||||||
}); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
window.addEventListener('online', setOfflineStatus); | ||||||||||||||||||
window.addEventListener('offline', setOfflineStatus); | ||||||||||||||||||
return function cleanup() { | ||||||||||||||||||
window.removeEventListener('online', setOfflineStatus); | ||||||||||||||||||
window.removeEventListener('offline', setOfflineStatus); | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👏 |
||||||||||||||||||
}; | ||||||||||||||||||
}, []); | ||||||||||||||||||
|
||||||||||||||||||
function setOfflineStatus() { | ||||||||||||||||||
setOffline(!navigator.onLine); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
function assignCredentials(usernameVal, tokenVal) { | ||||||||||||||||||
setUsername(usernameVal); | ||||||||||||||||||
setToken(tokenVal); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
return token ? ( | ||||||||||||||||||
<Dashboard token={token} username={username} offline={offline} /> | ||||||||||||||||||
) : ( | ||||||||||||||||||
<LoginPage assignCredentials={assignCredentials} offline={offline} /> | ||||||||||||||||||
return ( | ||||||||||||||||||
<BrowserRouter> | ||||||||||||||||||
<Switch> | ||||||||||||||||||
<Redirect from='/' to='/dashboard' /> | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏆 |
||||||||||||||||||
<PrivateRoute path='/dashboard'> | ||||||||||||||||||
<Dashboard token={token} username={username} /> | ||||||||||||||||||
</PrivateRoute> | ||||||||||||||||||
<Route path='/login'> | ||||||||||||||||||
<LoginPage assignCredentials={assignCredentials} /> | ||||||||||||||||||
</Route> | ||||||||||||||||||
</Switch> | ||||||||||||||||||
</BrowserRouter> | ||||||||||||||||||
); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ import Button from '../Button/Button'; | |
import cx from 'classnames'; | ||
|
||
function Assign({ userId, prId, className, isAssigned, currentAssignees }) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is the button, what do you think about be more specific with the name of the component? I didn't guess the first time what the component was about 😄 Something like |
||
// if there's previous assigness we keep their id | ||
let prevAssignees = []; | ||
if (currentAssignees.length) { | ||
prevAssignees = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to spread the map? Map returns a new array so probably the spread is not needed |
||
|
@@ -22,13 +23,16 @@ function Assign({ userId, prId, className, isAssigned, currentAssignees }) { | |
|
||
function handleAssignment(event) { | ||
if (!isAssigned) { | ||
// if the pr is not yet assigned to the user, we add the userId to the previous assignees' ids | ||
assignId.push(userId); | ||
event.preventDefault(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we move the |
||
// perform mutation | ||
assignToMe({ variables: { pullRequestId: prId, assigneeIds: assignId } }); | ||
if (data) { | ||
console.log('Succesfully assigned to you'); | ||
} | ||
} else { | ||
// otherwsise we filter all the ids except the user's and we perform the mutation | ||
event.preventDefault(); | ||
assignToMe({ | ||
variables: { | ||
|
@@ -47,7 +51,7 @@ function Assign({ userId, prId, className, isAssigned, currentAssignees }) { | |
icon='user-check' | ||
title='Assign to me' | ||
onClick={handleAssignment} | ||
className={isAssigned ? `${classnames}--isFavorite` : classnames} | ||
className={isAssigned ? `${classnames}--isFavorite` : classnames} // isFavorite needs renaming as the same modifications are applied for the favoriting feature | ||
/> | ||
); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,7 +15,7 @@ import NavBar from '../NavBar/NavBar'; | |
|
||
library.add(fas); | ||
|
||
function Dashboard({ className, username }) { | ||
function Dashboard({ className, username, ...props }) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need the rest of the prods here? |
||
// STATES | ||
const [pinnedItems, setPinnedItems] = useState( | ||
localStorage.getItem('pinnedItems') | ||
|
@@ -35,11 +35,13 @@ function Dashboard({ className, username }) { | |
const [isOpen, toggleSidebar] = useState(false); | ||
let uid; | ||
|
||
// helper Function | ||
function toggleBar() { | ||
toggleSidebar(!isOpen); | ||
} | ||
|
||
// API CALLS | ||
// PR Data | ||
const { loading, data, error } = useQuery(GET_PRS, { | ||
variables: { | ||
login: `${username}`, | ||
|
@@ -53,6 +55,7 @@ function Dashboard({ className, username }) { | |
uid = data.user.id; | ||
} | ||
|
||
// Repos data | ||
const { | ||
loading: reposLoading, | ||
data: reposData, | ||
|
@@ -62,7 +65,7 @@ function Dashboard({ className, username }) { | |
login: `${username}`, | ||
}, | ||
}); | ||
|
||
// PR authors data | ||
const { | ||
loading: authorsLoading, | ||
data: authorsData, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,17 @@ | ||
// groups prs from the user | ||
function getPRs(repos) { | ||
return repos.reduce((acc, element) => { | ||
return acc.concat(element.pullRequests.nodes); | ||
}, []); | ||
} | ||
|
||
// gets repos from org | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sometimes when a function is self-descriptive, the comments are not very useful. On the other hand, I personally, when I have a collection on utils, I usually write documentation on them. |
||
function getReposFromOrgs(orgs) { | ||
return orgs.reduce( | ||
(acc, element) => acc.concat(element.repositories.nodes), | ||
[] | ||
); | ||
} | ||
|
||
// gets als prs together | ||
export function groupPRs(queryData) { | ||
const userReposPRs = getPRs(queryData.user.repositories.nodes); | ||
const orgsReposPRs = getPRs( | ||
|
@@ -19,6 +20,7 @@ export function groupPRs(queryData) { | |
|
||
return [...userReposPRs, ...orgsReposPRs]; | ||
} | ||
//helper function to filter by repos | ||
export function filterByRepos(allPRs, repos, authors) { | ||
const firstFiltering = | ||
Array.isArray(repos) && repos.length > 0 | ||
|
@@ -32,7 +34,7 @@ export function filterByRepos(allPRs, repos, authors) { | |
) | ||
: firstFiltering; | ||
} | ||
|
||
//groups all repos | ||
export function groupAllRepos(queryData) { | ||
const userRepos = queryData.user.repositories.nodes; | ||
const orgRepos = getReposFromOrgs(queryData.user.organizations.nodes); | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -3,6 +3,8 @@ import './Login.css'; | |||||
import Button from '../Button/Button'; | ||||||
import cx from 'classnames'; | ||||||
import Checkbox from '../Checkbox/Checkbox'; | ||||||
import { GithubLoginButton } from 'react-social-login-buttons'; | ||||||
import { useHistory, useLocation } from 'react-router-dom'; | ||||||
|
||||||
//TODO(marta) create a form component | ||||||
|
||||||
|
@@ -14,7 +16,12 @@ function Login({ className, assignCredentials }) { | |||||
const [enterpriseUrl, setEnterpriseUrl] = useState(''); | ||||||
const [isFormEnabled, setEnableForm] = useState(false); | ||||||
|
||||||
useEffect(() => {}); | ||||||
// gets access to the browser history object | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I'm not wrong, this access to the React Router History object, not the one from the browser. I remember to see it here |
||||||
let history = useHistory(); | ||||||
// returns the current location object | ||||||
let location = useLocation(); | ||||||
|
||||||
let { from } = location.state || { from: { pathname: '/dashboard' } }; | ||||||
Comment on lines
+20
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can use |
||||||
|
||||||
function handleTokenChange({ target }) { | ||||||
setToken(target.value); | ||||||
|
@@ -33,6 +40,8 @@ function Login({ className, assignCredentials }) { | |||||
} | ||||||
|
||||||
function handleSubmit(event) { | ||||||
/* if the user accesses the app through the form, | ||||||
all the data will be handled by the above functions and stored in localstorage*/ | ||||||
event.preventDefault(); | ||||||
localStorage.setItem('username', username); | ||||||
localStorage.setItem('token', token); | ||||||
|
@@ -41,6 +50,8 @@ function Login({ className, assignCredentials }) { | |||||
localStorage.setItem('enterpriseUrl', JSON.stringify(enterpriseUrl)); | ||||||
} | ||||||
assignCredentials(username, token); | ||||||
// we redirect to the dashboard | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I'm not wrong, I think this will redirect you to the previous route that has been redirected to the login. |
||||||
history.replace(from); | ||||||
} | ||||||
|
||||||
useEffect(() => { | ||||||
|
@@ -55,7 +66,7 @@ function Login({ className, assignCredentials }) { | |||||
} else if (isFormEnabled) { | ||||||
setEnableForm(false); | ||||||
} | ||||||
}, [username, token, isEnterprise, enterpriseUrl]); | ||||||
}, [username, token, isEnterprise, enterpriseUrl, isFormEnabled]); | ||||||
|
||||||
return ( | ||||||
<div className={classnames}> | ||||||
|
@@ -64,6 +75,10 @@ function Login({ className, assignCredentials }) { | |||||
className='pic' | ||||||
alt='login femalecodercat' | ||||||
></img> | ||||||
<a href='http://localhost:8080/oauth/github'> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know you plan to move this to the production URL once you deploy the backend but I'd like to propose you a way I usually do it to avoid a second PR:
Suggested change
|
||||||
<GithubLoginButton /> | ||||||
</a> | ||||||
<p className='Login-link'>or</p> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
<form onSubmit={handleSubmit} className='Login-form'> | ||||||
<input | ||||||
placeholder='Username' | ||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,17 @@ | ||||||||||||||||||||||||||||||||||||||
export const isLoggedIn = () => { | ||||||||||||||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||||||||||||||
localStorage.getItem('isEnterprise') && | ||||||||||||||||||||||||||||||||||||||
localStorage.getItem('enterpriseURL') | ||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||
if (localStorage.getItem('token') && localStorage.getItem('username')) { | ||||||||||||||||||||||||||||||||||||||
return true; | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
} else if ( | ||||||||||||||||||||||||||||||||||||||
localStorage.getItem('token') && | ||||||||||||||||||||||||||||||||||||||
localStorage.getItem('username') | ||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||
return true; | ||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+2
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we're importing
BrowserRouter
twice. Maybe what we wanted is something like the basic example of React RouterIn that case, we should modify: