Skip to content

Commit

Permalink
implement basic agency routing + view metric data viz w/ live data (#105
Browse files Browse the repository at this point in the history
)

* implement basic agency routing + view metric data viz w/ live data

* move toast to common

* add jest tests

* fix importing png type
  • Loading branch information
terryttsai authored Oct 24, 2022
1 parent 5f5b20d commit 7a1c92c
Show file tree
Hide file tree
Showing 37 changed files with 701 additions and 124 deletions.
1 change: 1 addition & 0 deletions agency-dashboard/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
REACT_APP_PROXY_HOST=http://localhost:5001
23 changes: 14 additions & 9 deletions agency-dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,14 @@
"private": true,
"dependencies": {
"@justice-counts/common": "*",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.11.64",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"http-proxy-middleware": "^2.0.6",
"mobx": "^6.4.2",
"mobx-react-lite": "^3.3.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6",
"styled-components": "^5.3.6",
"typescript": "^4.8.4",
"web-vitals": "^2.1.4"
"typescript": "^4.8.4"
},
"scripts": {
"dev": "react-app-rewired start",
Expand Down Expand Up @@ -43,7 +39,16 @@
]
},
"devDependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.11.64",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"@types/react-router-dom": "^5.3.3",
"customize-cra": "^1.0.0",
"jest-fetch-mock": "^3.0.3",
"react-app-rewired": "^2.2.1",
"react-scripts": "5.0.1",
"resize-observer-polyfill": "^1.5.1"
Expand Down
35 changes: 35 additions & 0 deletions agency-dashboard/src/AgencyOverview.styles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Recidiviz - a data platform for criminal justice reform
// Copyright (C) 2022 Recidiviz, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

import { palette } from "@justice-counts/common/components/GlobalStyles";
import styled from "styled-components/macro";

export const MetricCategory = styled.div`
height: 100px;
width: 100%;
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: ${palette.solid.blue};
&:hover {
cursor: pointer;
text-decoration: underline;
}
`;
98 changes: 98 additions & 0 deletions agency-dashboard/src/AgencyOverview.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Recidiviz - a data platform for criminal justice reform
// Copyright (C) 2022 Recidiviz, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================
import { render, screen } from "@testing-library/react";
import React from "react";
import { MemoryRouter } from "react-router-dom";

import AgencyOverview from "./AgencyOverview";
import { StoreProvider } from "./stores";

beforeEach(() => {
fetchMock.resetMocks();
});

// test("renders loading state", () => {
// fetchMock.mockResponseOnce(
// JSON.stringify({
// datapoints: [],
// dimension_names_by_metric_and_disaggregation: {},
// })
// );

// render(
// <StoreProvider>
// <MemoryRouter initialEntries={["/agency/1"]}>
// <AgencyOverview />
// </MemoryRouter>
// </StoreProvider>
// );
// const loadingElement = screen.getByText(/Loading.../i);
// expect(loadingElement).toBeInTheDocument();
// });

test("renders 'No published metrics' state", async () => {
fetchMock.mockResponseOnce(
JSON.stringify({
datapoints: [],
dimension_names_by_metric_and_disaggregation: {},
})
);

render(
<StoreProvider>
<MemoryRouter initialEntries={["/agency/1"]}>
<AgencyOverview />
</MemoryRouter>
</StoreProvider>
);

const textElement = await screen.findByText(/No published metrics./i);
expect(textElement).toBeInTheDocument();
});

test("renders list of metrics", async () => {
fetchMock.mockResponseOnce(
JSON.stringify({
datapoints: [{}],
dimension_names_by_metric_and_disaggregation: {
LAW_ENFORCEMENT_ARRESTS: {},
LAW_ENFORCEMENT_BUDGET: {},
LAW_ENFORCEMENT_CALLS_FOR_SERVICE: {},
},
})
);

render(
<StoreProvider>
<MemoryRouter initialEntries={["/agency/1"]}>
<AgencyOverview />
</MemoryRouter>
</StoreProvider>
);
const textElement1 = await screen.findByText(
/Click on a metric to view chart:/i
);
expect(textElement1).toBeInTheDocument();
const textElement2 = await screen.findByText(/LAW_ENFORCEMENT_ARRESTS/i);
expect(textElement2).toBeInTheDocument();
const textElement3 = await screen.findByText(/LAW_ENFORCEMENT_BUDGET/i);
expect(textElement3).toBeInTheDocument();
const textElement4 = await screen.findByText(
/LAW_ENFORCEMENT_CALLS_FOR_SERVICE/i
);
expect(textElement4).toBeInTheDocument();
});
72 changes: 72 additions & 0 deletions agency-dashboard/src/AgencyOverview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Recidiviz - a data platform for criminal justice reform
// Copyright (C) 2022 Recidiviz, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

import { showToast } from "@justice-counts/common/components/Toast";
import { observer } from "mobx-react-lite";
import React, { useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";

import { MetricCategory } from "./AgencyOverview.styles";
import { useStore } from "./stores";

const AgencyOverview = () => {
const navigate = useNavigate();
const params = useParams();
const agencyId = Number(params.id);
const { datapointsStore } = useStore();

const fetchDatapoints = async () => {
try {
await datapointsStore.getDatapoints(agencyId);
} catch (error) {
showToast("Error fetching data.", false, "red", 4000);
}
};
useEffect(() => {
fetchDatapoints();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

if (datapointsStore.loading) {
return <>Loading...</>;
}

const metrics = Object.keys(datapointsStore.datapointsByMetric);
if (metrics.length === 0) {
return <>No published metrics.</>;
}

return (
<>
Click on a metric to view chart:
{Object.keys(datapointsStore.dimensionNamesByMetricAndDisaggregation).map(
(metricKey) => (
<MetricCategory
key={metricKey}
onClick={() => {
navigate(`/agency/${agencyId}/dashboard?metric=${metricKey}`);
}}
>
{datapointsStore.metricKeyToDisplayName[metricKey] || metricKey}
</MetricCategory>
)
)}
</>
);
};

export default observer(AgencyOverview);
11 changes: 9 additions & 2 deletions agency-dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,18 @@
// =============================================================================

import React from "react";
import { Route, Routes } from "react-router-dom";

import { DashboardView } from "./DashboardView";
import AgencyOverview from "./AgencyOverview";
import DashboardView from "./DashboardView";

function App() {
return <DashboardView />;
return (
<Routes>
<Route path="/agency/:id" element={<AgencyOverview />} />
<Route path="/agency/:id/dashboard" element={<DashboardView />} />
</Routes>
);
}

export default App;
9 changes: 7 additions & 2 deletions agency-dashboard/src/DashboardView.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

import { typography } from "@justice-counts/common/components/GlobalStyles";
import styled from "styled-components/macro";

export const Container = styled.div`
height: 100%;
height: 800px;
width: 100%;
position: absolute;
position: relative;
display: flex;
flex-direction: column;
justify-content: stretch;
align-items: stretch;
`;

export const MetricTitle = styled.div`
${typography.sizeCSS.title}
`;
77 changes: 77 additions & 0 deletions agency-dashboard/src/DashboardView.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Recidiviz - a data platform for criminal justice reform
// Copyright (C) 2022 Recidiviz, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================
import { render, screen } from "@testing-library/react";
import React from "react";
import { MemoryRouter } from "react-router-dom";

import DashboardView from "./DashboardView";
import { StoreProvider } from "./stores";

beforeEach(() => {
fetchMock.resetMocks();
});

// test("renders loading state", () => {
// fetchMock.mockResponseOnce(
// JSON.stringify({
// datapoints: [{}],
// dimension_names_by_metric_and_disaggregation: {
// LAW_ENFORCEMENT_ARRESTS: {},
// LAW_ENFORCEMENT_BUDGET: {},
// LAW_ENFORCEMENT_CALLS_FOR_SERVICE: {},
// },
// })
// );

// render(
// <StoreProvider>
// <MemoryRouter
// initialEntries={["/agency/1/dashboard?metric=LAW_ENFORCEMENT_ARRESTS"]}
// >
// <DashboardView />
// </MemoryRouter>
// </StoreProvider>
// );
// const loadingElement = screen.getByText(/Loading.../i);
// expect(loadingElement).toBeInTheDocument();
// });

test("renders 'No reported data for this metric.' state", async () => {
fetchMock.mockResponseOnce(
JSON.stringify({
datapoints: [{}],
dimension_names_by_metric_and_disaggregation: {
LAW_ENFORCEMENT_ARRESTS: {},
LAW_ENFORCEMENT_BUDGET: {},
LAW_ENFORCEMENT_CALLS_FOR_SERVICE: {},
},
})
);

render(
<StoreProvider>
<MemoryRouter
initialEntries={["/agency/1/dashboard?metric=LAW_ENFORCEMENT_ARRESTS"]}
>
<DashboardView />
</MemoryRouter>
</StoreProvider>
);

const textElement = await screen.findByText(/LAW_ENFORCEMENT_ARRESTS/i);
expect(textElement).toBeInTheDocument();
});
Loading

0 comments on commit 7a1c92c

Please sign in to comment.