Skip to content

Commit

Permalink
Merge pull request #32 from seroanalytics/i31
Browse files Browse the repository at this point in the history
i31 add router and faq
  • Loading branch information
hillalex authored Dec 11, 2024
2 parents 6ee4c95 + 58acca0 commit 0d19322
Show file tree
Hide file tree
Showing 10 changed files with 259 additions and 2 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: 🗑 Cleanup session files
# Hitting the API checks for inactive sessions and cleans up any orphaned files.
# This action runs on an hourly schedule to make sure this cleanup happens at least
# once an hour.

on:
schedule:
# * is a special character in YAML so you have to quote this string
- cron: '0 * * * *'

jobs:
cleanup:
name: 🗑 Cleanup
runs-on: ubuntu-latest
steps:
- name: Cleanup
run: curl https://seroviz.seroanalytics.org/api/
69 changes: 69 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"json-schema-to-typescript": "^15.0.0",
"mime-types": "^2.1.35",
"react-plotly.js": "^2.6.0",
"react-router-dom": "^7.0.2",
"ts-jest": "^29.2.4",
"ts-node": "^10.9.2"
},
Expand Down
105 changes: 105 additions & 0 deletions src/components/FAQ.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, {useReducer} from 'react';
import {Col, Container, Row} from "react-bootstrap";
import TopNav from "./TopNav";
import usePersistedState from "../hooks/usePersistedState";
import {
initialState,
RootContext,
RootDispatchContext
} from "../RootContext";
import {rootReducer} from "../reducers/rootReducer";

export default function FAQ() {

const [theme, setTheme] = usePersistedState<string>("theme", "dark");
const [state, dispatch] = useReducer(rootReducer, initialState);

return <RootContext.Provider value={state}>
<RootDispatchContext.Provider value={dispatch}>
<TopNav theme={theme as string}
setTheme={setTheme as (newState: string) => void}></TopNav>
<Container fluid>
<Row className={"mt-5"}>
<Col xs={12} sm={{span: 8, offset: 2}}>
<h1>FAQ</h1>
<ul>
<li className={"fw-bold mt-2"}>
What format does my data need to be in?
</li>
<p>Data should be a CSV containing an absolute or
relative time series of biomarker values, in
long-format.
Required columns
are <i>biomarker</i> and <i>value</i>,
plus a column containing a time variable, the
name of which is configurable but defaults
to <i>day</i>. Arbitrarily many extra columns
are allowed and will be made available to
disaggregate by.</p>
<li className={"fw-bold mt-2"}>
What is the difference between "serological
surveillance" and "post-exposure" data?
</li>
<p>
By "serological surveillance" we mean time
series
data where the time variable is based on
absolute
calendar date; for example, a study that
monitors a population level biomarker over time.
The time variable here might be day of study, or
calendar date.</p>
<p>
By "post-exposure" data we mean a relative time
series,
where biomarker levels are measured for each
individual
relative to a known exposure (vaccination or
infection). The time variable here would be time
since exposure.
</p>
<li className={"fw-bold mt-2"}>
Where is my data kept? Is it secure?
</li>
<p>SeroViz is deployed to Digital Ocean's App
Platform; access to the remote server is
limited to app maintainers and secured via 2fa.
You can read more about Digital
Ocean's infrastructure security <a
href={"https://www.digitalocean.com/security/infrastructure-security"}>here</a>.
</p>
<p>Files are temporarily uploaded to the remote
server
under a unique session id, and will persist as
long as your keep your browser open.
When you close your browser all session cookies
will be deleted, and remote files associated
with inactive sessions are deleted every hour.
</p>
<p>Or, to force your session and all associated
files to be deleted immediately, you can click
the "End session" link in the top-right
corner.
</p>
<li className={"fw-bold mt-2"}>
Who funded this project? Who maintains it?
</li>
This project was funded by the Wellcome Trust. It
was built
by <a href={"https://github.com/hillalex/"}>Alex
Hill</a> and is maintained by <a
href={"https://github.com/dchodge/"}>David
Hodgson</a>.
<li className={"fw-bold mt-2"}>
How do I request new features?
</li>
Please raise a GitHub issue on the <a
href={"https://github.com/seroanalytics/seroviz"}>seroviz
repo</a>.
</ul>
</Col>
</Row>
</Container>
</RootDispatchContext.Provider>
</RootContext.Provider>
}
26 changes: 26 additions & 0 deletions src/components/NoPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, {useReducer} from 'react';
import {Container} from "react-bootstrap";
import TopNav from "./TopNav";
import usePersistedState from "../hooks/usePersistedState";
import {
initialState,
RootContext,
RootDispatchContext
} from "../RootContext";
import {rootReducer} from "../reducers/rootReducer";

export default function NoPage() {

const [theme, setTheme] = usePersistedState<string>("theme", "dark");
const [state, dispatch] = useReducer(rootReducer, initialState);

return <RootContext.Provider value={state}>
<RootDispatchContext.Provider value={dispatch}>
<TopNav theme={theme as string}
setTheme={setTheme as (newState: string) => void}></TopNav>
<Container fluid>
<h1>Page not found</h1>
</Container>
</RootDispatchContext.Provider>
</RootContext.Provider>
}
2 changes: 1 addition & 1 deletion src/components/TopNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default function TopNav({
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="me-auto">
<Nav.Link href="/">Manage datasets</Nav.Link>
<Nav.Link href="/docs">Docs</Nav.Link>
<Nav.Link href="/faq">FAQ</Nav.Link>
</Nav>
</Navbar.Collapse>
<Nav>
Expand Down
11 changes: 10 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import "bootstrap/dist/css/bootstrap.min.css";
import './index.css';
import App from './components/App';
import reportWebVitals from './reportWebVitals';
import {BrowserRouter, Route, Routes} from "react-router-dom";
import FAQ from "./components/FAQ";
import NoPage from "./components/NoPage";

const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
Expand All @@ -20,7 +23,13 @@ const getApiUrl = () => {

root.render(
<React.StrictMode>
<App />
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="FAQ" element={<FAQ />} />
<Route path="*" element={<NoPage />} />
</Routes>
</BrowserRouter>
</React.StrictMode>
);

Expand Down
12 changes: 12 additions & 0 deletions test/components/FAQ.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";
import {render, screen} from "@testing-library/react";
import FAQ from "../../src/components/FAQ";

describe("<FAQ />", () => {
test("renders FAQ", async () => {

render(<FAQ/>);
const list = await screen.findAllByRole("listitem");
expect(list.length).toBe(5);
});
});
12 changes: 12 additions & 0 deletions test/components/NoPage.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";
import {render, screen} from "@testing-library/react";
import NoPage from "../../src/components/NoPage";

describe("<NoPage />", () => {
test("renders page not found message", async () => {

render(<NoPage/>);
const message = await screen.findByRole("heading");
expect(message.textContent).toBe("Page not found");
});
});
6 changes: 6 additions & 0 deletions test/components/TopNav.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import {
} from "../../src/RootContext";
import TopNav from "../../src/components/TopNav";

test("Displays FAQ link", async () => {
render(<TopNav theme={"light"} setTheme={jest.fn()}/>);
const faq = screen.getByText("FAQ") as HTMLAnchorElement;
expect(faq.href).toBe("http://localhost/faq");
});

test("user can end session", async () => {
mockAxios.onDelete(`/session/`)
.reply(200, mockSuccess("OK"));
Expand Down

0 comments on commit 0d19322

Please sign in to comment.