Jaime Ramírez
2020-06-03 1c786929ee61c40d4353fb361d88e5f36cc7ee6c
Adding loaders and data loading error handling
2 files deleted
2 files added
13 files modified
437 ■■■■■ changed files
adopt-a-pup/web-app/.eslintrc.json 3 ●●●● patch | view | raw | blame | history
adopt-a-pup/web-app/Dockerfile 6 ●●●● patch | view | raw | blame | history
adopt-a-pup/web-app/package-lock.json 26 ●●●●● patch | view | raw | blame | history
adopt-a-pup/web-app/package.json 8 ●●●●● patch | view | raw | blame | history
adopt-a-pup/web-app/server.js 53 ●●●● patch | view | raw | blame | history
adopt-a-pup/web-app/src/App.tsx 25 ●●●●● patch | view | raw | blame | history
adopt-a-pup/web-app/src/Components/AnimalDetails.tsx 44 ●●●●● patch | view | raw | blame | history
adopt-a-pup/web-app/src/Components/LoadingData.tsx 49 ●●●●● patch | view | raw | blame | history
adopt-a-pup/web-app/src/Components/NavList.tsx 4 ●●●● patch | view | raw | blame | history
adopt-a-pup/web-app/src/Components/SheltersList.tsx 71 ●●●● patch | view | raw | blame | history
adopt-a-pup/web-app/src/Config.ts 21 ●●●●● patch | view | raw | blame | history
adopt-a-pup/web-app/src/Services/AnimalRESTService.ts 2 ●●● patch | view | raw | blame | history
adopt-a-pup/web-app/src/Services/Environment.ts 14 ●●●●● patch | view | raw | blame | history
adopt-a-pup/web-app/src/Services/RESTService.ts 6 ●●●● patch | view | raw | blame | history
adopt-a-pup/web-app/src/Views/AnimalDetailsView.tsx 73 ●●●● patch | view | raw | blame | history
adopt-a-pup/web-app/src/Views/AnimalsView.tsx 29 ●●●● patch | view | raw | blame | history
adopt-a-pup/web-app/src/Views/SheltersView.tsx 3 ●●●●● patch | view | raw | blame | history
adopt-a-pup/web-app/.eslintrc.json
@@ -68,7 +68,8 @@
        },
        {
            "files": [
                "server.js"
                "server.js",
                "src/Config.js"
            ],
            "env": {
                "node": true
adopt-a-pup/web-app/Dockerfile
@@ -3,8 +3,8 @@
ENV REACT_APP_ADOPTION_SERVICE_URL=http://envoy-gateway-adopt-a-pup.apps.ocp-jaime.do328.dev.nextcle.com/ \
    REACT_APP_ANIMAL_SERVICE_URL=http://envoy-gateway-adopt-a-pup.apps.ocp-jaime.do328.dev.nextcle.com/ \
    REACT_APP_SHELTER_SERVICE_URL=http://envoy-gateway-adopt-a-pup.apps.ocp-jaime.do328.dev.nextcle.com/ \
    REACT_APP_NEWS_ENABLED=1
    # REACT_APP_NEWS_SERVICE_URL=""
    REACT_APP_NEWS_ENABLED=1 \
    REACT_APP_NEWS_SERVICE_URL=http://locahost:5000/
# Install dependencies
COPY package.json package-lock.json ./
@@ -12,7 +12,7 @@
RUN npm ci --production
# Copy app
COPY . ./
COPY build server.js ./
EXPOSE 8080
adopt-a-pup/web-app/package-lock.json
@@ -5539,8 +5539,7 @@
    "dotenv": {
      "version": "8.2.0",
      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
      "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==",
      "dev": true
      "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
    },
    "dotenv-expand": {
      "version": "5.1.0",
@@ -14943,9 +14942,9 @@
      "dev": true
    },
    "typescript": {
      "version": "3.7.5",
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz",
      "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==",
      "version": "3.9.3",
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz",
      "integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==",
      "dev": true
    },
    "unicode-canonical-property-names-ecmascript": {
@@ -15551,6 +15550,15 @@
            "ajv-keywords": "^3.1.0"
          }
        },
        "serialize-javascript": {
          "version": "3.1.0",
          "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz",
          "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==",
          "dev": true,
          "requires": {
            "randombytes": "^2.1.0"
          }
        },
        "ssri": {
          "version": "6.0.1",
          "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
@@ -15561,16 +15569,16 @@
          }
        },
        "terser-webpack-plugin": {
          "version": "1.4.3",
          "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
          "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
          "version": "1.4.4",
          "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz",
          "integrity": "sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA==",
          "dev": true,
          "requires": {
            "cacache": "^12.0.2",
            "find-cache-dir": "^2.1.0",
            "is-wsl": "^1.1.0",
            "schema-utils": "^1.0.0",
            "serialize-javascript": "^2.1.2",
            "serialize-javascript": "^3.1.0",
            "source-map": "^0.6.1",
            "terser": "^4.1.2",
            "webpack-sources": "^1.4.0",
adopt-a-pup/web-app/package.json
@@ -3,11 +3,10 @@
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "dotenv": "^8.2.0",
    "express": "^4.17.1"
  },
  "devDependencies": {
    "@types/react-router-dom": "^5.1.5",
    "install": "^0.13.0",
    "@patternfly/react-core": "^3.158.1",
    "@patternfly/react-table": "^2.28.47",
    "@testing-library/jest-dom": "^4.2.4",
@@ -17,19 +16,22 @@
    "@types/node": "^12.0.0",
    "@types/react": "^16.9.0",
    "@types/react-dom": "^16.9.0",
    "@types/react-router-dom": "^5.1.5",
    "axios": "^0.19.2",
    "dockerfile-generator": "^4.0.3",
    "install": "^0.13.0",
    "react": "^16.13.1",
    "react-core": "0.0.0",
    "react-dom": "^16.13.1",
    "react-router-dom": "^5.1.2",
    "react-scripts": "3.4.1",
    "typescript": "~3.7.2"
    "typescript": "^3.7.5"
  },
  "scripts": {
    "start": "react-scripts start",
    "start:prod": "node server.js",
    "build": "react-scripts build",
    "build:container": "npm run build && docker build -t quay.io/redhattraining/ossm-adopt-a-pup-webapp:1.0 .",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "lint": "eslint --ext ts,tsx src --fix"
adopt-a-pup/web-app/server.js
@@ -2,45 +2,50 @@
Code to serve the web app in PRODUCTION.
Injects environment variables at runtime in build/index.html
*/
require("dotenv").config();
const express = require("express");
const path = require("path");
const fs = require("fs").promises;
const app = express();
const BUILD_PATH = path.join(__dirname, "build");
app.use(express.static(BUILD_PATH));
// Return index.html, with injected env variables
const indexFilePath = path.join(__dirname, "build", "index.html");
console.log(`Using: ${indexFilePath}`);
app.get("/*", async(request, response) => {
    const content = await injectEnvironmentInHTml(indexFilePath);
    response.send(content);
    log(request);
});
app.use(express.static(path.join(__dirname, "build")));
app.get("/*", async(req, res) => {
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
    console.log(`Server listening at ${PORT}`);
});
function log(req) {
    console.log(`${(new Date()).toISOString()} - GET ${req.url}`);
    let content;
    content = await render(indexFilePath, {
        REACT_APP_ADOPTION_SERVICE_URL: process.env.REACT_APP_ADOPTION_SERVICE_URL,
        REACT_APP_ANIMAL_SERVICE_URL: process.env.REACT_APP_ANIMAL_SERVICE_URL,
        REACT_APP_SHELTER_SERVICE_URL: process.env.REACT_APP_SHELTER_SERVICE_URL,
        REACT_APP_NEWS_SERVICE_URL: process.env.REACT_APP_NEWS_SERVICE_URL,
        REACT_APP_NEWS_ENABLED: ["true", "1", "True"].includes(
            process.env.REACT_APP_NEWS_ENABLED
        )
    });
    res.send(content);
});
const port = process.env.PORT || 8080;
app.listen(port, () => {
    console.log(`Server listening at ${port}`);
});
}
async function render(filePath, data) {
    console.log(data);
async function injectEnvironmentInHTml(filePath) {
    // Only consider variables starting with REACT_APP
    const environment = {};
    Object.keys(process.env)
        .filter(key => key.startsWith("REACT_APP_"))
        .forEach(key => {
            environment[key] = process.env[key];
        });
    const content = await fs.readFile(filePath);
    return content
        .toString()
        .replace(
            "__PRODUCTION_ENV__",
            JSON.stringify(data)
            JSON.stringify(environment)
        );
}
adopt-a-pup/web-app/src/App.tsx
@@ -23,7 +23,7 @@
import { ShelterService } from "./Services/ShelterService";
import AnimalDetailsView from "./Views/AnimalDetailsView";
import ShelterDetailsView from "./Views/ShelterDetailsView";
import Environment from "./Services/Environment";
import Config from "./Config";
import NewsRESTService from "./Services/NewsRESTService";
@@ -32,35 +32,30 @@
let adoptionService: AdoptionService;
let shelterService: ShelterService;
let newsService: NewsService;
const ADOPTION_SERVICE_URL = Environment.get("REACT_APP_ADOPTION_SERVICE_URL");
const ANIMAL_SERVICE_URL = Environment.get("REACT_APP_ANIMAL_SERVICE_URL");
const SHELTER_SERVICE_URL = Environment.get("REACT_APP_SHELTER_SERVICE_URL");
const NEWS_ENABLED = Environment.get("REACT_APP_NEWS_ENABLED");
const NEWS_SERVICE_URL = Environment.get("REACT_APP_NEWS_SERVICE_URL");
if (ADOPTION_SERVICE_URL) {
    adoptionService = new AdoptionRESTService(ADOPTION_SERVICE_URL);
if (Config.ADOPTION_SERVICE_URL) {
    adoptionService = new AdoptionRESTService(Config.ADOPTION_SERVICE_URL);
} else {
    console.log("Warning: No adoption service url provided. Using AdoptionFakeService");
    adoptionService = new AdoptionFakeService();
}
if (ANIMAL_SERVICE_URL) {
    animalService = new AnimalRESTService(ANIMAL_SERVICE_URL);
if (Config.ANIMAL_SERVICE_URL) {
    animalService = new AnimalRESTService(Config.ANIMAL_SERVICE_URL);
} else {
    console.log("Warning: No animal service url provided. Using AnimalFakeService");
    animalService = new AnimalFakeService();
}
if (SHELTER_SERVICE_URL) {
    shelterService = new ShelterRESTService(SHELTER_SERVICE_URL);
if (Config.SHELTER_SERVICE_URL) {
    shelterService = new ShelterRESTService(Config.SHELTER_SERVICE_URL);
} else {
    console.log("Warning: No shelter service url provided. Using ShelterFakeService");
    shelterService = new ShelterFakeService();
}
if (NEWS_ENABLED && NEWS_SERVICE_URL) {
    newsService = new NewsRESTService(NEWS_SERVICE_URL);
if (Config.NEWS_ENABLED && Config.NEWS_SERVICE_URL) {
    newsService = new NewsRESTService(Config.NEWS_SERVICE_URL);
} else {
    console.log("Warning: No news service url provided. Using NewsFakeService");
    newsService = new NewsFakeService();
@@ -87,7 +82,7 @@
                                adoptionService={adoptionService}
                            />
                        </Route>
                        {NEWS_ENABLED &&
                        {Config.NEWS_ENABLED &&
                        <Route path="/news" exact>
                            <NewsView newsService={newsService} />
                        </Route>
adopt-a-pup/web-app/src/Components/AnimalDetails.tsx
File was deleted
adopt-a-pup/web-app/src/Components/LoadingData.tsx
New file
@@ -0,0 +1,49 @@
import React from "react";
import { Alert, AlertActionCloseButton, Text, TextContent } from "@patternfly/react-core";
import BullseyeSpinner from "./BullseyeSpinner";
type LoadingDataProps = {
    title?: string,
    showLoader: boolean,
    showError: boolean
    errorTitle: string,
    errorDescription: string,
    onErrorClosed: () => void
}
/**
 * Use this component when you need to show a loader while loading data
 * and possibly show an error alert if data can't be loaded
 */
export default class LoadingData extends React.Component<LoadingDataProps> {
    public render() {
        const {
            title,
            showLoader,
            showError,
            errorTitle,
            errorDescription,
            onErrorClosed,
            children
        } = this.props;
        return (
            <React.Fragment>
                {showError &&
                    <Alert
                        className="popup"
                        variant="danger"
                        title={errorTitle}
                        action={<AlertActionCloseButton onClose={onErrorClosed} />}>
                        {errorDescription}
                    </Alert>}
                {title &&<TextContent>
                    <Text component="h2">{title}</Text>
                </TextContent>}
                {showLoader && <BullseyeSpinner />}
                {children}
            </React.Fragment>
        );
    }
}
adopt-a-pup/web-app/src/Components/NavList.tsx
@@ -6,7 +6,7 @@
} from "@patternfly/react-core";
import { Link} from "react-router-dom";
import Environment from "../Services/Environment";
import Environment from "../Config";
@@ -14,7 +14,7 @@
    public render() {
        const { pathname } = window.location;
        const enableNews = Environment.get("REACT_APP_NEWS_ENABLED");
        const enableNews = Environment.getEnv("REACT_APP_NEWS_ENABLED");
        return (
            <Nav theme="dark">
                <NavList>
adopt-a-pup/web-app/src/Components/SheltersList.tsx
@@ -3,6 +3,8 @@
import { ShelterService } from "../Services/ShelterService";
import { Shelter } from "../Models/Shelter";
import { Link } from "react-router-dom";
import { RESTConnectionError } from "../Services/RESTService";
import LoadingData from "./LoadingData";
type ShelterListProps = {
@@ -10,7 +12,13 @@
}
type ShelterListState = {
    shelters: Array<any>
    shelters: Array<Shelter>,
    loading: boolean,
    error: {
        isActive: boolean,
        header: string,
        message: string
    }
}
export default class ShelterList extends React.Component<ShelterListProps, ShelterListState> {
@@ -18,27 +26,68 @@
    constructor(props: ShelterListProps) {
        super(props);
        this.state = {
            shelters: []
            shelters: [],
            loading: false,
            error: {
                isActive: false,
                header: "",
                message: ""
            }
        };
    }
    public async componentDidMount() {
        const shelters = await this.props.shelterService.getAll();
        this.setState({ loading: true });
        try {
            const shelters = await this.props.shelterService.getAll();
            this.setState({ shelters });
        } catch (error) {
            if (error instanceof RESTConnectionError) {
                this.showConnectionError(error);
            }
        } finally {
            this.setState({ loading: false });
        }
    }
    private showConnectionError(error: RESTConnectionError) {
        this.setState({
            shelters
            error: {
                isActive: true,
                header: error.message,
                message: error.description,
            }
        });
    }
    private closeErrorAlert = () => {
        this.setState({
            error: {
                isActive: false,
                message: "",
                header: ""
            }
        });
    }
    public render() {
        const { shelters } = this.state;
        const { shelters, loading, error } = this.state;
        return (
            <List>
                {shelters.map(shelter => <ListItem key={shelter.shelterId}>
                    {this.renderShelter(shelter)}
                </ListItem>)}
            </List>
            <LoadingData
                showLoader={loading}
                showError={error.isActive}
                errorTitle={error.header}
                errorDescription={error.message}
                onErrorClosed={this.closeErrorAlert}
            >
                <List>
                    {shelters.map(shelter => <ListItem key={shelter.shelterId}>
                        {this.renderShelter(shelter)}
                    </ListItem>)}
                </List>
            </LoadingData>
        );
    }
adopt-a-pup/web-app/src/Config.ts
New file
@@ -0,0 +1,21 @@
export default {
    ADOPTION_SERVICE_URL: getEnv("REACT_APP_ADOPTION_SERVICE_URL"),
    ANIMAL_SERVICE_URL: getEnv("REACT_APP_ANIMAL_SERVICE_URL"),
    SHELTER_SERVICE_URL: getEnv("REACT_APP_SHELTER_SERVICE_URL"),
    NEWS_SERVICE_URL: getEnv("REACT_APP_NEWS_SERVICE_URL"),
    NEWS_ENABLED: getEnv("REACT_APP_NEWS_ENABLED"),
    getEnv
};
function getEnv(variable: string) {
    // In production, env variables at runtime are read
    // from the global "window" object in the browser
    if (process.env.NODE_ENV === "production") {
        const { env } = window as any;
        return env ? env[variable]: undefined;
    }
    // In dev, the React devserver lets us read variables from Nodejs "process.env"
    return process.env[variable];
}
adopt-a-pup/web-app/src/Services/AnimalRESTService.ts
@@ -14,7 +14,7 @@
    }
    public getAllAdoptable(): Promise<Animal[]> {
        return this.get("/animal/getAllAdoptable");
        return this.get("/animals/getAllAdoptable");
    }
    public getById(id: string): Promise<Animal> {
adopt-a-pup/web-app/src/Services/Environment.ts
File was deleted
adopt-a-pup/web-app/src/Services/RESTService.ts
@@ -7,11 +7,11 @@
    private readonly axiosInstance: AxiosInstance;
    constructor(
        baseUrl: string,
        baseURL: string,
        private readonly remoteServiceName: string,
        private readonly timeoutMs = 3000
    ) {
        this.axiosInstance = Axios.create({ baseURL: baseUrl });
        this.axiosInstance = Axios.create({ baseURL });
    }
    protected async get<T>(url: string): Promise<T> {
@@ -19,7 +19,7 @@
            const r = await this.axiosInstance.get<T>(url, { timeout: this.timeoutMs });
            return r.data;
        } catch (e) {
            throw new RESTConnectionError(e, this.remoteServiceName);
            throw new RESTConnectionError(e, this.remoteServiceName, e.response?.status);
        }
    }
adopt-a-pup/web-app/src/Views/AnimalDetailsView.tsx
@@ -6,6 +6,8 @@
import { AnimalService } from "../Services/AnimalService";
import { Animal } from "../Models/Animal";
import AdoptionForm from "../Components/AdoptionForm";
import { RESTConnectionError } from "../Services/RESTService";
import LoadingData from "../Components/LoadingData";
type AnimalDetailsViewProps = {
    adoptionService: AdoptionService;
@@ -18,7 +20,13 @@
}
type AnimalDetailsViewState = {
    animal?: Animal
    animal?: Animal,
    loading: boolean,
    error: {
        isActive: boolean,
        header: string,
        message: string
    }
}
export default class AnimalDetailsView
@@ -27,22 +35,66 @@
    constructor(props: AnimalDetailsViewProps) {
        super(props);
        this.state = {
            animal: undefined
            animal: undefined,
            loading: false,
            error: {
                isActive: false,
                header: "",
                message: ""
            }
        };
    }
    public async componentDidMount() {
        const { animalId } = this.props.match.params;
        const animal = await this.props.animalService.getById(animalId);
        this.setState({ loading: true });
        try {
            const animal = await this.props.animalService.getById(animalId);
            this.setState({ animal });
        } catch (error) {
            if (error instanceof RESTConnectionError) {
                this.showConnectionError(error);
            }
        } finally {
            this.setState({ loading: false });
        }
    }
    private showConnectionError(error: RESTConnectionError) {
        this.setState({
            animal
            error: {
                isActive: true,
                header: error.message,
                message: error.description,
            }
        });
    }
    private closeErrorAlert = () => {
        this.setState({
            error: {
                isActive: false,
                message: "",
                header: ""
            }
        });
    }
    public render() {
        const { animal } = this.state;
        return animal ? this.renderAnimal(animal) : this.renderMissingAnimal();
        const { animal, loading, error } = this.state;
        return (
            <LoadingData
                showLoader={loading}
                showError={error.isActive}
                errorTitle={error.header}
                errorDescription={error.message}
                onErrorClosed={this.closeErrorAlert}
                title="Adoptable Animals"
            >
                {animal ? this.renderAnimal(animal) : ""}
            </LoadingData>
        );
    }
    private renderAnimal(animal: Animal) {
@@ -94,14 +146,15 @@
                            </TextContent>
                        </GridItem>
                    </Grid>
                </PageSection>
                <PageSection>
                    <TextContent>
                        <Text component="h2">Adopt {animal.animalName}!</Text>
                    </TextContent>
                    <AdoptionForm adoptionService={this.props.adoptionService} animalId={animal.animalId} />
                    <AdoptionForm
                        adoptionService={this.props.adoptionService}
                        animalId={animal.animalId}
                    />
                </PageSection>
            </React.Fragment>
        );
adopt-a-pup/web-app/src/Views/AnimalsView.tsx
@@ -1,13 +1,13 @@
import React from "react";
import { AnimalService } from "../Services/AnimalService";
import {
    PageSection, PageSectionVariants, Text, TextContent, Alert, AlertActionCloseButton
import {
    PageSection, PageSectionVariants, Text, TextContent
} from "@patternfly/react-core";
import AdoptableAnimalList from "../Components/AdoptableAnimalList";
import { AdoptionService } from "../Services/AdoptionService";
import { Animal } from "../Models/Animal";
import { RESTConnectionError } from "../Services/RESTService";
import BullseyeSpinner from "../Components/BullseyeSpinner";
import LoadingData from "../Components/LoadingData";
type AnimalsViewProps = {
@@ -66,7 +66,7 @@
        });
    }
    private closeAlert = () => {
    private closeErrorAlert = () => {
        this.setState({
            error: {
                isActive: false,
@@ -90,17 +90,16 @@
                    </TextContent>
                </PageSection>
                <PageSection>
                {error.isActive &&
                    <Alert
                        className="popup"
                        variant="danger"
                        title={error.header}
                        action={<AlertActionCloseButton onClose={this.closeAlert} />}>
                        {error.message}
                    </Alert>}
                    <Text component="h2">Adoptable Animals</Text>
                    { loading && <BullseyeSpinner />}
                    <AdoptableAnimalList animals={this.state.animals} />
                    <LoadingData
                        showLoader={loading}
                        showError={error.isActive}
                        errorTitle={error.header}
                        errorDescription={error.message}
                        onErrorClosed={this.closeErrorAlert}
                        title="Adoptable Animals"
                    >
                        <AdoptableAnimalList animals={this.state.animals} />
                    </LoadingData>
                </PageSection>
            </React.Fragment>
        );
adopt-a-pup/web-app/src/Views/SheltersView.tsx
@@ -22,9 +22,6 @@
                    </TextContent>
                </PageSection>
                <PageSection>
                    <TextContent>
                        <Text component="h2">Shelters list</Text>
                    </TextContent>
                    <ShelterList shelterService={this.props.shelterService}></ShelterList>
                </PageSection>
                <PageSection variant={PageSectionVariants.light}>