From 8f4437e17e443942011af9d70c1cf997a906fcd7 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Wed, 2 Jan 2019 02:11:01 -0500 Subject: [PATCH] initialize and pass api client to subviews --- src/App.jsx | 9 +++ src/api.js | 74 ++++++++++++------- src/index.jsx | 10 ++- src/pages/Login.jsx | 4 +- src/pages/Organizations/index.jsx | 31 ++++++-- .../Organizations/views/Organization.view.jsx | 13 ++-- .../views/Organizations.list.jsx | 6 +- 7 files changed, 99 insertions(+), 48 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 854ab61733..624242384d 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -38,8 +38,17 @@ class App extends Component { config: {}, error: false, }; + + this.onLogout = this.onLogout.bind(this); }; + async onLogout () { + const { api } = this.props; + + await api.logout(); + window.location.replace('/#/login') + } + onNavToggle = () => { this.setState(({ isNavOpen }) => ({ isNavOpen: !isNavOpen })); }; diff --git a/src/api.js b/src/api.js index 9e07c3bac5..007fa37f5c 100644 --- a/src/api.js +++ b/src/api.js @@ -1,33 +1,35 @@ import axios from 'axios'; -import { - API_CONFIG, - API_LOGIN, - API_ROOT, -} from './endpoints'; +const API_ROOT = '/api/'; +const API_LOGIN = `${API_ROOT}login/`; +const API_LOGOUT = `${API_ROOT}logout/`; +const API_V2 = `${API_ROOT}v2/`; +const API_CONFIG = `${API_V2}config/`; +const API_ORGANIZATIONS = `${API_V2}organizations/`; const CSRF_COOKIE_NAME = 'csrftoken'; const CSRF_HEADER_NAME = 'X-CSRFToken'; - const LOGIN_CONTENT_TYPE = 'application/x-www-form-urlencoded'; -class APIClient { - constructor () { - this.http = axios.create({ - xsrfCookieName: CSRF_COOKIE_NAME, - xsrfHeaderName: CSRF_HEADER_NAME, - }); - } +const defaultHttpAdapter = axios.create({ + xsrfCookieName: CSRF_COOKIE_NAME, + xsrfHeaderName: CSRF_HEADER_NAME, +}); - /* eslint class-methods-use-this: ["error", { "exceptMethods": ["getCookie"] }] */ - getCookie () { +class APIClient { + static getCookie () { return document.cookie; } - isAuthenticated () { - let authenticated = false; + constructor (httpAdapter = defaultHttpAdapter) { + this.http = httpAdapter; + } - const parsed = (`; ${this.getCookie()}`).split('; userLoggedIn='); + isAuthenticated () { + const cookie = this.constructor.getCookie(); + const parsed = (`; ${cookie}`).split('; userLoggedIn='); + + let authenticated = false; if (parsed.length === 2) { authenticated = parsed.pop().split(';').shift() === 'true'; @@ -36,10 +38,6 @@ class APIClient { return authenticated; } - getRoot () { - return this.http.get(API_ROOT); - } - async login (username, password, redirect = API_CONFIG) { const un = encodeURIComponent(username); const pw = encodeURIComponent(password); @@ -49,12 +47,36 @@ class APIClient { const headers = { 'Content-Type': LOGIN_CONTENT_TYPE }; await this.http.get(API_LOGIN, { headers }); - await this.http.post(API_LOGIN, data, { headers }); + const response = await this.http.post(API_LOGIN, data, { headers }); + + return response; } - get = (endpoint, params = {}) => this.http.get(endpoint, { params }); + logout () { + return this.http.get(API_LOGOUT); + } - post = (endpoint, data) => this.http.post(endpoint, data); + getRoot () { + return this.http.get(API_ROOT); + } + + getConfig () { + return this.http.get(API_CONFIG); + } + + getOrganizations (params = {}) { + return this.http.get(API_ORGANIZATIONS, { params }); + } + + createOrganization (data) { + return this.http.post(API_ORGANIZATIONS, data); + } + + getOrganizationDetails (id) { + const endpoint = `${API_ORGANIZATIONS}${id}/`; + + return this.http.get(endpoint); + } } -export default new APIClient(); +export default APIClient; diff --git a/src/index.jsx b/src/index.jsx index 04cb57b6c8..5c63f5163f 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -18,7 +18,8 @@ import './app.scss'; import './components/Pagination/styles.scss'; import './components/DataListToolbar/styles.scss'; -import api from './api'; +import APIClient from './api'; + import App from './App'; import Background from './components/Background'; import Applications from './pages/Applications'; @@ -55,7 +56,7 @@ const language = (navigator.languages && navigator.languages[0]) || navigator.userLanguage; const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0]; -export async function main () { +export async function main (api) { const el = document.getElementById('app'); // fetch additional config from server const { data } = await api.getRoot(); @@ -67,6 +68,7 @@ export async function main () { path="/login" render={() => ( @@ -92,6 +94,7 @@ export async function main () { ( ( )} @@ -262,4 +266,4 @@ export async function main () { , el); }; -export default main(); +export default main(new APIClient()); diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index 20690940b2..207aa1491d 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -8,7 +8,6 @@ import { } from '@patternfly/react-core'; import towerLogo from '../../images/tower-logo-header.svg'; -import api from '../api'; class AtLogin extends Component { constructor (props) { @@ -34,6 +33,7 @@ class AtLogin extends Component { handleSubmit = async event => { const { username, password, loading } = this.state; + const { api } = this.props; event.preventDefault(); @@ -54,7 +54,7 @@ class AtLogin extends Component { render () { const { username, password, isValidPassword } = this.state; - const { logo, alt } = this.props; + const { api, alt, logo } = this.props; const logoSrc = logo ? `data:image/jpeg;${logo}` : towerLogo; if (api.isAuthenticated()) { diff --git a/src/pages/Organizations/index.jsx b/src/pages/Organizations/index.jsx index 09299873df..b3a13a0161 100644 --- a/src/pages/Organizations/index.jsx +++ b/src/pages/Organizations/index.jsx @@ -5,12 +5,31 @@ import OrganizationAdd from './views/Organization.add'; import OrganizationView from './views/Organization.view'; import OrganizationsList from './views/Organizations.list'; -const Organizations = ({ match }) => ( +export default ({ api, match }) => ( - - - + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> ); - -export default Organizations; diff --git a/src/pages/Organizations/views/Organization.view.jsx b/src/pages/Organizations/views/Organization.view.jsx index dbfb42c4b4..18c7066afa 100644 --- a/src/pages/Organizations/views/Organization.view.jsx +++ b/src/pages/Organizations/views/Organization.view.jsx @@ -2,16 +2,13 @@ import React, { Component, Fragment } from 'react'; import { i18nMark } from '@lingui/react'; import { Switch, - Route + Route, + withRouter, } from 'react-router-dom'; - import OrganizationBreadcrumb from '../components/OrganizationBreadcrumb'; import OrganizationDetail from '../components/OrganizationDetail'; import OrganizationEdit from '../components/OrganizationEdit'; -import api from '../../../api'; -import { API_ORGANIZATIONS } from '../../../endpoints'; - class OrganizationView extends Component { constructor (props) { super(props); @@ -47,13 +44,15 @@ class OrganizationView extends Component { async fetchOrganization () { const { mounted } = this.state; + const { api } = this.props; + if (mounted) { this.setState({ error: false, loading: true }); const { match } = this.props; const { parentBreadcrumbObj, organization } = this.state; try { - const { data } = await api.get(`${API_ORGANIZATIONS}${match.params.id}/`); + const { data } = await api.getOrganizationDetails(match.params.id); if (organization === 'loading') { this.setState({ organization: data }); } @@ -118,4 +117,4 @@ class OrganizationView extends Component { } } -export default OrganizationView; +export default withRouter(OrganizationView); diff --git a/src/pages/Organizations/views/Organizations.list.jsx b/src/pages/Organizations/views/Organizations.list.jsx index 5ca2809911..0f14bf21ff 100644 --- a/src/pages/Organizations/views/Organizations.list.jsx +++ b/src/pages/Organizations/views/Organizations.list.jsx @@ -17,9 +17,6 @@ import DataListToolbar from '../../../components/DataListToolbar'; import OrganizationListItem from '../components/OrganizationListItem'; import Pagination from '../../../components/Pagination'; -import api from '../../../api'; -import { API_ORGANIZATIONS } from '../../../endpoints'; - import { encodeQueryString, parseQueryString, @@ -132,6 +129,7 @@ class Organizations extends Component { } async fetchOrganizations (queryParams) { + const { api } = this.props; const { page, page_size, order_by } = queryParams; let sortOrder = 'ascending'; @@ -145,7 +143,7 @@ class Organizations extends Component { this.setState({ error: false, loading: true }); try { - const { data } = await api.get(API_ORGANIZATIONS, queryParams); + const { data } = await api.getOrganizations(queryParams); const { count, results } = data; const pageCount = Math.ceil(count / page_size);