From 32684405456bc49eee6422492f7f0679e7fcddb6 Mon Sep 17 00:00:00 2001 From: Hugo SUBTIL <ext.sopra.husubtil@grandlyon.com> Date: Thu, 5 Aug 2021 07:47:48 +0000 Subject: [PATCH] feat: add cicid build verification, sonar test and build step --- .gitlab-ci.yml | 61 +++++++++ src/App.tsx | 28 +---- src/components/Editing/Editing.tsx | 3 +- src/components/Login/Login.tsx | 15 +-- src/components/Menu/Menu.tsx | 15 +-- src/components/MonthlyNews/MonthlyNews.tsx | 6 +- src/components/Navbar/Navbar.tsx | 22 ++-- src/components/Navbar/navbar.scss | 12 +- src/components/Poll/Poll.tsx | 139 ++++++++++----------- src/components/Routes/PrivateRoute.tsx | 4 +- src/components/Routes/Routes.tsx | 4 +- src/hooks/useAuth.ts | 55 ++++---- src/hooks/useFindUser.ts | 27 ++++ src/hooks/userContext.ts | 11 +- src/services/monthlyNews.service.ts | 12 +- 15 files changed, 226 insertions(+), 188 deletions(-) create mode 100644 .gitlab-ci.yml create mode 100644 src/hooks/useFindUser.ts diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..01e4a4c2 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,61 @@ +image: docker:git + +services: + - docker:dind + +variables: + DOCKER_DRIVER: overlay2 + DOCKER_TLS_CERTDIR: '' + +stages: + - build + - quality + +build-test: + stage: build + image: node:14.15.4-alpine + before_script: + - apk add git + - apk add bash + script: + - yarn + - yarn build + only: + - dev + - merge_requests + +build: + image: docker:18.09 + services: + - docker:18.09-dind + stage: build + only: + - master + - dev + script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" --build-arg conf=prod . + - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" + +sonarqube: + stage: quality + only: + - dev + - merge_requests + image: registry.forge.grandlyon.com/apoyen2/sonnar-scanner-gl:master + before_script: + - export NODE_PATH=$NODE_PATH:`npm root -g` + - npm install -g typescript + script: + - > + sonar-scanner + -Dsonar.projectName="ecolyo-backoffice-front" + -Dsonar.projectVersion=1.0 + -Dsonar.sourceEncoding=UTF-8 + -Dsonar.projectBaseDir=. + -Dsonar.host.url=${SONAR_URL} + -Dsonar.projectKey=${CI_PROJECT_PATH_SLUG} + -Dsonar.login=${SONAR_TOKEN} + -Dsonar.cpd.exclusions=tests/**,src/**/*.spec.ts* + -Dsonar.qualitygate.wait=true + diff --git a/src/App.tsx b/src/App.tsx index 9829f71b..2495ec0a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,37 +1,17 @@ -import { useState, useEffect } from 'react' import { BrowserRouter } from 'react-router-dom' import Layout from './components/Layout/Layout' import Routes from './components/Routes/Routes' -import { useAuth } from './hooks/useAuth' import { UserContext } from './hooks/userContext' -import { User } from './models/user.model' import { ToastContainer } from 'react-toastify' import 'react-toastify/dist/ReactToastify.css' -function App() { - const { loginUser, error, isUserLogged, getUser, logoutUser } = useAuth() - const [isLogged, setisLogged] = useState<boolean>(false) - const [user, setuser] = useState<User>() +import useFindUser from './hooks/useFindUser' - useEffect(() => { - let subscribed = true - async function checkIsLogged() { - const userLogged = await isUserLogged() - if (userLogged) { - setisLogged(true) - setuser(getUser() as User) - } else setisLogged(false) - } - if (subscribed) checkIsLogged() - return () => { - subscribed = false - } - }, [isLogged, setisLogged]) +function App() { + const { user, setUser, isLoading } = useFindUser() return ( <BrowserRouter> - <UserContext.Provider - value={{ user, loginUser, error, isLogged, logoutUser, setisLogged }} - > + <UserContext.Provider value={{ user, setUser, isLoading }}> <Layout> <Routes /> </Layout> diff --git a/src/components/Editing/Editing.tsx b/src/components/Editing/Editing.tsx index 5af1ff69..43634f7f 100644 --- a/src/components/Editing/Editing.tsx +++ b/src/components/Editing/Editing.tsx @@ -142,7 +142,6 @@ const Editing: React.FC = () => { setIsTouched(false) } } - setisLoading(false) } if (subscribed) { @@ -152,7 +151,7 @@ const Editing: React.FC = () => { subscribed = false setRefreshData(false) } - }, [date, user, refreshData]) + }, [date, user, refreshData, resetFields]) return ( <> diff --git a/src/components/Login/Login.tsx b/src/components/Login/Login.tsx index 275aad44..9bb9f893 100644 --- a/src/components/Login/Login.tsx +++ b/src/components/Login/Login.tsx @@ -1,24 +1,15 @@ -import React, { useContext } from 'react' +import React from 'react' import { useAuth } from '../../hooks/useAuth' -import { UserContext } from '../../hooks/userContext' import './login.scss' const Login: React.FC = () => { - const { loginUser, setisLogged } = useContext(UserContext) - - const handleClickLogin = async () => { - if (loginUser && setisLogged) { - await loginUser() - - setisLogged(true) - } - } + const { loginUser } = useAuth() return ( <div className="login"> <div className="container"> <h1>Bienvenue sur le Backoffice d'Ecolyo !</h1> - <button className="btnValid" onClick={handleClickLogin}> + <button className="btnValid" onClick={loginUser}> Login </button> </div> diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx index 26383e99..7c8c1c99 100644 --- a/src/components/Menu/Menu.tsx +++ b/src/components/Menu/Menu.tsx @@ -4,16 +4,11 @@ import logo from '../../assets/icons/ecolyo-logo.svg' import './menu.scss' import { NavLink } from 'react-router-dom' import { UserContext } from '../../hooks/userContext' +import { useAuth } from '../../hooks/useAuth' const Menu: React.FC = () => { - const { isLogged, logoutUser, setisLogged } = useContext(UserContext) - - const handleLogout = () => { - if (logoutUser && setisLogged) { - logoutUser() - setisLogged(false) - } - } + const { user } = useContext(UserContext) + const { logoutUser } = useAuth() return ( <nav className={'menu'}> <div className="logo-container"> @@ -29,8 +24,8 @@ const Menu: React.FC = () => { })} </div> <div className="administration"> - {isLogged ? ( - <button className="btnValid logButton" onClick={handleLogout}> + {user ? ( + <button className="btnValid logButton" onClick={logoutUser}> Logout </button> ) : ( diff --git a/src/components/MonthlyNews/MonthlyNews.tsx b/src/components/MonthlyNews/MonthlyNews.tsx index a5befe0f..b3481458 100644 --- a/src/components/MonthlyNews/MonthlyNews.tsx +++ b/src/components/MonthlyNews/MonthlyNews.tsx @@ -34,7 +34,9 @@ const MonthlyNews: React.FC<MonthlyNewsProps> = ({ 'undo redo | bold italic underline | alignleft aligncenter alignright | code | ecolyoLink', }} value={header} - onEditorChange={(newHeader) => handleChange(newHeader, 'header')} + onEditorChange={(newHeader: string) => + handleChange(newHeader, 'header') + } /> <div className="subtitle"> <p className="title">Citation du mois</p> @@ -102,7 +104,7 @@ const MonthlyNews: React.FC<MonthlyNewsProps> = ({ }, }} value={quote} - onEditorChange={(newQuote) => handleChange(newQuote, 'quote')} + onEditorChange={(newQuote: string) => handleChange(newQuote, 'quote')} /> <div className="buttons"> <button className="btnCancel" onClick={onCancel}> diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx index f12bfa4e..a41458c3 100644 --- a/src/components/Navbar/Navbar.tsx +++ b/src/components/Navbar/Navbar.tsx @@ -1,19 +1,16 @@ import React, { useContext } from 'react' import { NavLink } from 'react-router-dom' -import logo from '../../assets/icons/ecolyo-logo.svg' -import login from '../../assets/icons/login.svg' import editing from '../../assets/icons/editing.svg' import settings from '../../assets/icons/settings.svg' - import './navbar.scss' import { UserContext } from '../../hooks/userContext' +import { useAuth } from '../../hooks/useAuth' const Navbar: React.FC = () => { - const { isLogged, logoutUser } = useContext(UserContext) + const { user } = useContext(UserContext) + const { logoutUser } = useAuth() return ( <div className="navbar"> - <img src={logo} alt="Ecolyo logo" className="logo" /> - <div className="menu-list"> <NavLink to={'/editing'} activeClassName="active"> <img src={editing} className="navbar-icon" alt="Editing icon" /> @@ -23,15 +20,12 @@ const Navbar: React.FC = () => { <img src={settings} className="navbar-icon" alt="Settings icon" /> Paramètres </NavLink> - {isLogged ? ( - <p onClick={logoutUser}>Logout</p> - ) : ( - <NavLink to="/login" activeClassName="active"> - <img src={login} className="navbar-icon" alt="Login icon" /> - Login - </NavLink> - )} </div> + {user && ( + <button className="btnValid logButton" onClick={logoutUser}> + Logout + </button> + )} </div> ) } diff --git a/src/components/Navbar/navbar.scss b/src/components/Navbar/navbar.scss index b39e8805..78f8bc89 100644 --- a/src/components/Navbar/navbar.scss +++ b/src/components/Navbar/navbar.scss @@ -16,12 +16,6 @@ align-items: center; justify-content: center; } - .burger, - .logo { - width: 2rem; - left: 1rem; - position: absolute; - } a { text-decoration: none; display: flex; @@ -41,4 +35,10 @@ .navbar-icon { width: 1.3rem; } + .logButton { + width: 100px; + min-width: 0; + margin-left: auto; + margin-top: 0; + } } diff --git a/src/components/Poll/Poll.tsx b/src/components/Poll/Poll.tsx index 4ed6d85b..25c71fb0 100644 --- a/src/components/Poll/Poll.tsx +++ b/src/components/Poll/Poll.tsx @@ -1,70 +1,69 @@ -import { Editor } from '@tinymce/tinymce-react' -import React, { ChangeEvent } from 'react' -import { ContentItems } from '../Editing/Editing' -import './poll.scss' - -interface PollProps { - question: string - link: string - handleChange: ( - value: string, - type: 'header' | 'quote' | 'question' | 'link' - ) => void - onSave: () => Promise<void> - onCancel: () => void - onDelete: (target: ContentItems) => void -} -const Poll: React.FC<PollProps> = ({ - question, - link, - handleChange, - onSave, - onCancel, - onDelete, -}: PollProps) => { - const handleChangeLink = (e: ChangeEvent<HTMLInputElement>) => { - handleChange(e.target.value, 'link') - } - - return ( - <div className="poll"> - <h2>Ajouter un sondage</h2> - - <p className="title">Lien</p> - <input - type="text" - className="input-dark" - value={link} - onChange={handleChangeLink} - /> - <div> - <p className="title">Question</p> - - <Editor - init={{ - menubar: false, - toolbar: - 'undo redo | bold italic underline | alignleft aligncenter alignright | code | ecolyoLink', - }} - value={question} - onEditorChange={(newQuestion, editor) => - handleChange(newQuestion, 'question') - } - /> - <div className="buttons"> - <button className="btnCancel" onClick={onCancel}> - Annuler - </button> - <button className="btnValid" onClick={onSave}> - Sauvegarder - </button> - <button className="btnDelete" onClick={() => onDelete('poll')}> - Supprimer - </button> - </div> - </div> - </div> - ) -} - -export default Poll +import { Editor } from '@tinymce/tinymce-react' +import React, { ChangeEvent } from 'react' +import { ContentItems } from '../Editing/Editing' +import './poll.scss' + +interface PollProps { + question: string + link: string + handleChange: ( + value: string, + type: 'header' | 'quote' | 'question' | 'link' + ) => void + onSave: () => Promise<void> + onCancel: () => void + onDelete: (target: ContentItems) => void +} +const Poll: React.FC<PollProps> = ({ + question, + link, + handleChange, + onSave, + onCancel, + onDelete, +}: PollProps) => { + const handleChangeLink = (e: ChangeEvent<HTMLInputElement>) => { + handleChange(e.target.value, 'link') + } + + return ( + <div className="poll"> + <h2>Ajouter un sondage</h2> + <p className="title">Lien</p> + <input + type="text" + className="input-dark" + value={link} + onChange={handleChangeLink} + /> + <div> + <p className="title">Question</p> + + <Editor + init={{ + menubar: false, + toolbar: + 'undo redo | bold italic underline | alignleft aligncenter alignright | code | ecolyoLink', + }} + value={question} + onEditorChange={(newQuestion: string) => + handleChange(newQuestion, 'question') + } + /> + <div className="buttons"> + <button className="btnCancel" onClick={onCancel}> + Annuler + </button> + <button className="btnValid" onClick={onSave}> + Sauvegarder + </button> + <button className="btnDelete" onClick={() => onDelete('poll')}> + Supprimer + </button> + </div> + </div> + </div> + ) +} + +export default Poll diff --git a/src/components/Routes/PrivateRoute.tsx b/src/components/Routes/PrivateRoute.tsx index a8147f06..0f2fc3e4 100644 --- a/src/components/Routes/PrivateRoute.tsx +++ b/src/components/Routes/PrivateRoute.tsx @@ -13,9 +13,9 @@ const PrivateRoute: React.FC<PrivateRouteProps> = ({ path, exact, }: PrivateRouteProps) => { - const { isLogged } = useContext(UserContext) + const { user } = useContext(UserContext) - return isLogged ? ( + return user ? ( <Route path={path} exact={exact} component={component} /> ) : ( <Redirect to="/login" /> diff --git a/src/components/Routes/Routes.tsx b/src/components/Routes/Routes.tsx index adb12ee3..a5c1b43a 100644 --- a/src/components/Routes/Routes.tsx +++ b/src/components/Routes/Routes.tsx @@ -7,11 +7,11 @@ import Settings from '../Settings/Settings' import PrivateRoute from './PrivateRoute' const Routes: React.FC = () => { - const { isLogged } = useContext(UserContext) + const { user } = useContext(UserContext) return ( <Switch> - {isLogged && <Redirect path="/login" to="/editing" />} + {user && <Redirect path="/login" to="/editing" />} <Route path="/login" component={Login} /> <PrivateRoute path="/editing" component={Editing} exact /> <PrivateRoute path="/settings" component={Settings} exact /> diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts index f9d7e532..aa4e1c1b 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAuth.ts @@ -1,59 +1,52 @@ -import React, { useState } from 'react' +import { useContext, useState } from 'react' import axios from 'axios' -import { User } from '../models/user.model' +import { UserContext } from './userContext' +import { useHistory } from 'react-router-dom' const _apiUrl: string = 'https://localhost:1443/' export interface Auth { loginUser: () => Promise<void> error: null - isUserLogged: () => Promise<boolean> - getUser: () => User | undefined logoutUser: () => void } export const useAuth = (): Auth => { const [error, setError] = useState(null) + const { setUser } = useContext(UserContext) + const history = useHistory() //login user const loginUser = async (): Promise<void> => { try { await axios.get(`${_apiUrl}OAuth2Login`) - const { data } = await axios.get(`${_apiUrl}api/common/WhoAmI`) - if (data) { - console.log('whomi', data) - localStorage.setItem('user', JSON.stringify(data)) - } + await setUserContext() } catch (e) { setError(e) } } - const logoutUser = (): void => { - localStorage.removeItem('user') + const logoutUser = async (): Promise<void> => { + try { + if (setUser) setUser(null) + await axios.get(`${_apiUrl}Logout`) + } catch (e) { + setError(e) + } } - const isUserLogged = async (): Promise<boolean> => { - const user = localStorage.getItem('user') - if (user) { - try { - const { data } = await axios.get(`${_apiUrl}api/common/WhoAmI`) - if (data) { - console.log(data) - return true - } - return false - } catch (e) { - setError(e) - return false + //set user in context and push them home + const setUserContext = async (): Promise<void> => { + try { + const { data } = await axios.get(`${_apiUrl}api/common/WhoAmI`) + if (data && setUser) { + setUser(data) + console.log('usertoContext', data) + history.push('/editing') } - } else { - return false + } catch (e) { + setError(e) } } - const getUser = (): User | undefined => { - const user = localStorage.getItem('user') - if (user) return JSON.parse(user) as User - } - return { loginUser, error, isUserLogged, getUser, logoutUser } + return { loginUser, error, logoutUser } } diff --git a/src/hooks/useFindUser.ts b/src/hooks/useFindUser.ts new file mode 100644 index 00000000..5a3c38ee --- /dev/null +++ b/src/hooks/useFindUser.ts @@ -0,0 +1,27 @@ +import { useState, useEffect } from 'react' +import axios from 'axios' +import { User } from '../models/user.model' + +const useFindUser = () => { + const [user, setUser] = useState<User | null>(null) + const [isLoading, setLoading] = useState<boolean>(true) + const _apiUrl: string = 'https://localhost:1443/' + + useEffect(() => { + async function findUser() { + const { data } = await axios.get(`${_apiUrl}api/common/WhoAmI`) + if (data) { + setUser(data) + setLoading(false) + } + } + findUser() + }, []) + return { + user, + setUser, + isLoading, + } +} + +export default useFindUser diff --git a/src/hooks/userContext.ts b/src/hooks/userContext.ts index 895920b5..e2f69367 100644 --- a/src/hooks/userContext.ts +++ b/src/hooks/userContext.ts @@ -1,12 +1,9 @@ -import React, { createContext, Dispatch, SetStateAction } from 'react' +import { createContext, Dispatch, SetStateAction } from 'react' import { User } from '../models/user.model' export interface UserContextProps { - loginUser: () => Promise<void> - error: null - isLogged: boolean - user: User - logoutUser: () => void - setisLogged: Dispatch<SetStateAction<boolean>> + user: User | null + setUser: Dispatch<SetStateAction<User | null>> + isLoading: boolean } export const UserContext = createContext<Partial<UserContextProps>>({}) diff --git a/src/services/monthlyNews.service.ts b/src/services/monthlyNews.service.ts index eca0a334..920018e7 100644 --- a/src/services/monthlyNews.service.ts +++ b/src/services/monthlyNews.service.ts @@ -58,9 +58,9 @@ export class MonthlyNewsService { }, } ) - if (data == {}) { - return null - } + // if (data == {}) { + // return null + // } return data as IMonthlyNews } catch (e) { @@ -86,9 +86,9 @@ export class MonthlyNewsService { }, } ) - if (data === {}) { - return null - } + // if (data === {}) { + // return null + // } return data as IPoll } catch (e) { console.log('error', e) -- GitLab