Skip to content

Commit

Permalink
Merge pull request #7 from seroanalytics/scale
Browse files Browse the repository at this point in the history
add lineplot tests and choosescale component
  • Loading branch information
hillalex authored Sep 6, 2024
2 parents b7b44d5 + 54fcfc7 commit 57202ab
Show file tree
Hide file tree
Showing 20 changed files with 1,081 additions and 36 deletions.
43 changes: 43 additions & 0 deletions .github/workflows/docker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: 🚢 Docker

on:
push:
branches:
- main
pull_request:

env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
docker:
name: 🚢 Docker
runs-on: ubuntu-latest
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4

- name: ⎔ Setup node
uses: actions/setup-node@v4

- name: 📥 Install deps
run: npm install --frozen-lockfile

- name: 🔨 Build image
run: ./scripts/build

- name: 🔥 Smoke test
run: ./scripts/smoke-test

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: 🚢 Push image
run: ./scripts/push
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
[![🔨 Build](https://github.com/seroanalytics/seroviz/actions/workflows/build.yml/badge.svg)](https://github.com/seroanalytics/seroviz/actions/workflows/build.yml)
[![🔎 Test](https://github.com/seroanalytics/seroviz/actions/workflows/test.yml/badge.svg)](https://github.com/seroanalytics/seroviz/actions/workflows/test.yml)
[![codecov](https://codecov.io/gh/seroanalytics/seroviz/graph/badge.svg?token=2DH6NUOXRe)](https://codecov.io/gh/seroanalytics/seroviz)
![Docker Image Version](https://img.shields.io/docker/v/seroanalytics/seroviz?logo=docker)
![GitHub License](https://img.shields.io/github/license/seroanalytics/seroviz)

Client-side React application for visualising serological data.

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},
"license": "GPL-3.0",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
Expand Down
4 changes: 3 additions & 1 deletion scripts/build
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ HERE=$(dirname $0)
npm run build

docker build --pull \
--tag $TAG_SHA \
--tag $DOCKER_COMMIT_TAG \
.

docker tag $DOCKER_COMMIT_TAG $DOCKER_BRANCH_TAG
4 changes: 2 additions & 2 deletions scripts/common
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ else
GIT_BRANCH=$(git symbolic-ref --short HEAD)
fi

TAG_SHA="${PACKAGE_ORG}/${PACKAGE_NAME}:${GIT_SHA}"
TAG_BRANCH="${PACKAGE_ORG}/${PACKAGE_NAME}:${GIT_BRANCH}"
DOCKER_COMMIT_TAG="${PACKAGE_ORG}/${PACKAGE_NAME}:${GIT_SHA}"
DOCKER_BRANCH_TAG="${PACKAGE_ORG}/${PACKAGE_NAME}:${GIT_BRANCH}"
6 changes: 4 additions & 2 deletions scripts/push
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ set -e
HERE=$(dirname $0)
. $HERE/common

docker tag $TAG_SHA $TAG_BRANCH
docker push $TAG_BRANCH
if [[ "$GIT_BRANCH" == "main" ]]; then
docker push $DOCKER_BRANCH_TAG
docker push $DOCKER_COMMIT_TAG
fi
35 changes: 35 additions & 0 deletions scripts/smoke-test
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env bash

HERE=$(realpath "$(dirname $0)")
. $HERE/common

wait_for()
{
echo "waiting up to $TIMEOUT seconds for app"
start_ts=$(date +%s)
for i in $(seq $TIMEOUT); do
result="$(curl --write-out %{http_code} --silent --output /dev/null --insecure https://localhost 2>/dev/null)"
if [[ $result -eq "200" ]]; then
end_ts=$(date +%s)
echo "App available after $((end_ts - start_ts)) seconds"
exit 0
fi
sleep 1
echo "...still waiting"
done
return $result
}

$HERE/run

# The variable expansion below is 60s by default, or the argument provided
# to this script
TIMEOUT="${1:-60}"
sleep 2 # Wait for SSL to be ready
wait_for
RESULT=$?
if [[ $RESULT -ne 200 ]]; then
echo "App did not become available in time"
exit 1
fi
exit 0
3 changes: 2 additions & 1 deletion src/RootContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ export enum ActionType {
DATASET_METADATA_FETCHED = "DATASET_METADATA_FETCHED",
DATASET_SELECTED = "DATASET_SELECTED",
SELECT_COVARIATE = "SELECT_COVARIATE",
UNSELECT_COVARIATE = "UNSELECT_COVARIATE"
UNSELECT_COVARIATE = "UNSELECT_COVARIATE",
SELECT_SCALE = "SELECT_SCALE"
}

export interface RootAction {
Expand Down
26 changes: 26 additions & 0 deletions src/components/ChooseScale.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, {useContext} from "react";
import {ActionType, RootContext, RootDispatchContext} from "../RootContext";
import Form from "react-bootstrap/Form";

export default function ChooseScale() {

const state = useContext(RootContext);
const dispatch = useContext(RootDispatchContext);

const onSelect = (event: any) => {
dispatch({
type: ActionType.SELECT_SCALE,
payload: event.target.value
});
}

return <Form.Group key="choose-scale" className="mb-3">
<Form.Label htmlFor="scale">Transform data by</Form.Label>
<Form.Select id="scale" onChange={onSelect}
value={state.datasetSettings[state.selectedDataset].scale}>
<option value={"natural"}>none</option>
<option value={"log"}>log (log base 10)</option>
<option value={"log2"}>log2 (log base 2)</option>
</Form.Select>
</Form.Group>
}
22 changes: 13 additions & 9 deletions src/components/LinePlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,20 @@ export default function LinePlot({

const facetDefinition = facetDefinitions.join("+")

const covariateSettings = state.datasetSettings[state.selectedDataset].covariateSettings;
const scale = state.datasetSettings[state.selectedDataset].scale;
useEffect(() => {
dataService(state.language, dispatch)
.getDataSeries(state.selectedDataset,
biomarker, facetDefinition, state.datasetSettings[state.selectedDataset].covariateSettings)
.then(data => {
if (data && data.data) {
setSeries(data.data)
}
});
}, [state.language, dispatch, state.selectedDataset, biomarker, facetDefinition, state.datasetSettings]);
const fetchData = async () => {
const result = await dataService(state.language, dispatch)
.getDataSeries(state.selectedDataset,
biomarker, facetDefinition, covariateSettings, scale);

if (result && result.data) {
setSeries(result.data)
}
}
fetchData();
}, [state.language, dispatch, state.selectedDataset, biomarker, facetDefinition, covariateSettings, scale]);

let series: any[] = [];

Expand Down
2 changes: 2 additions & 0 deletions src/components/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {ActionType, RootContext, RootDispatchContext} from "../RootContext";
import CovariateOptions from "./CovariateOptions";
import SelectedCovariate from "./SelectedCovariate";
import ChooseDataset from "./ChooseDataset";
import ChooseScale from "./ChooseScale";

export default function SideBar() {

Expand Down Expand Up @@ -37,6 +38,7 @@ export default function SideBar() {
className={"text-secondary"}>{state.datasetMetadata?.biomarkers.join(", ")}</span>
</Col>
</Row>
<ChooseScale />
{availableCovariates.length > 0 &&
<Form.Group className="mb-3">
<Form.Label>Disaggregate by</Form.Label>
Expand Down
10 changes: 9 additions & 1 deletion src/reducers/datasetReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export const datasetReducer = (state: AppState, action: RootAction): AppState =>
return selectCovariate(state, action)
case ActionType.UNSELECT_COVARIATE:
return unselectCovariate(state, action)
case ActionType.SELECT_SCALE:
return selectScale(state, action)
default:
return state
}
Expand All @@ -34,7 +36,7 @@ const selectCovariate = (state: AppState, action: RootAction): AppState => {
const newState = {...state}
const settings = newState.datasetSettings[state.selectedDataset].covariateSettings
if (settings.indexOf(action.payload) === -1) {
settings.push(action.payload)
newState.datasetSettings[state.selectedDataset].covariateSettings = [...settings, action.payload]
}
return newState
}
Expand All @@ -45,3 +47,9 @@ const unselectCovariate = (state: AppState, action: RootAction) => {
newState.datasetSettings[state.selectedDataset].covariateSettings = settings.filter(v => v.name !== action.payload)
return newState
}

const selectScale = (state: AppState, action: RootAction): AppState => {
const newState = {...state}
newState.datasetSettings[state.selectedDataset].scale = action.payload
return newState
}
9 changes: 6 additions & 3 deletions src/services/dataService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ export class DataService {
async getDataSeries(selectedDataset: string,
biomarker: string,
facetDefinition: string,
selectedCovariates: CovariateSettings[]) {
covariateSettings: CovariateSettings[],
scale: "log" | "natural" | "log2") {


const traces = selectedCovariates
const traces = covariateSettings
.filter(v => v.display === "trace")
.map(v => v.name).join("+")

Expand All @@ -63,9 +64,11 @@ export class DataService {
}

if (traces.length > 0) {
queryString += `disaggregate=${encodeURIComponent(traces)}`
queryString += `disaggregate=${encodeURIComponent(traces)}&`
}

queryString += `scale=${scale}`

return await this._api
.ignoreSuccess()
.withError(ActionType.ERROR_ADDED)
Expand Down
25 changes: 12 additions & 13 deletions test/components/ChooseDataset.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
} from "../../src/RootContext";
import {render, screen, waitFor} from "@testing-library/react";
import {mockAppState, mockAxios, mockSuccess} from "../mocks";
import {act} from "react";
import {userEvent} from "@testing-library/user-event";

describe("<ChooseOrUploadDataset/>", () => {
Expand All @@ -22,11 +21,11 @@ describe("<ChooseOrUploadDataset/>", () => {
let state = mockAppState();
const dispatch = jest.fn();

act(() => render(<RootContext.Provider value={state}>
render(<RootContext.Provider value={state}>
<RootDispatchContext.Provider
value={dispatch}><ChooseOrUploadDataset/>
</RootDispatchContext.Provider>
</RootContext.Provider>));
</RootContext.Provider>);

await waitFor(() => expect(dispatch.mock.calls.length).toBe(1));

Expand All @@ -45,11 +44,11 @@ describe("<ChooseOrUploadDataset/>", () => {
});
const dispatch = jest.fn();

act(() => render(<RootContext.Provider value={state}>
render(<RootContext.Provider value={state}>
<RootDispatchContext.Provider
value={dispatch}><ChooseOrUploadDataset/>
</RootDispatchContext.Provider>
</RootContext.Provider>));
</RootContext.Provider>);

expect(screen.getAllByLabelText("Choose dataset").length).toBe(1);
const select = screen.getByRole("combobox") as HTMLSelectElement;
Expand All @@ -67,11 +66,11 @@ describe("<ChooseOrUploadDataset/>", () => {
});
const dispatch = jest.fn();

act(() => render(<RootContext.Provider value={state}>
render(<RootContext.Provider value={state}>
<RootDispatchContext.Provider
value={dispatch}><ChooseOrUploadDataset/>
</RootDispatchContext.Provider>
</RootContext.Provider>));
</RootContext.Provider>);

expect(screen.queryByLabelText("Choose dataset")).toBe(null);
expect(screen.queryByText("Go")).toBe(null);
Expand All @@ -87,11 +86,11 @@ describe("<ChooseOrUploadDataset/>", () => {
const dispatch = jest.fn();
const user = userEvent.setup();

act(() => render(<RootContext.Provider value={state}>
render(<RootContext.Provider value={state}>
<RootDispatchContext.Provider
value={dispatch}><ChooseOrUploadDataset/>
</RootDispatchContext.Provider>
</RootContext.Provider>));
</RootContext.Provider>);

const select = screen.getByRole("combobox") as HTMLSelectElement;

Expand All @@ -115,11 +114,11 @@ describe("<ChooseOrUploadDataset/>", () => {
const dispatch = jest.fn();
const user = userEvent.setup();

act(() => render(<RootContext.Provider value={state}>
render(<RootContext.Provider value={state}>
<RootDispatchContext.Provider
value={dispatch}><ChooseOrUploadDataset/>
</RootDispatchContext.Provider>
</RootContext.Provider>));
</RootContext.Provider>);

expect(screen.getByTestId("advanced-options")).toHaveClass("d-none");
const toggle = screen.getByText("Advanced options");
Expand All @@ -143,11 +142,11 @@ describe("<ChooseOrUploadDataset/>", () => {
const dispatch = jest.fn();
const user = userEvent.setup();

act(() => render(<RootContext.Provider value={state}>
render(<RootContext.Provider value={state}>
<RootDispatchContext.Provider
value={dispatch}><ChooseOrUploadDataset/>
</RootDispatchContext.Provider>
</RootContext.Provider>));
</RootContext.Provider>);

const fileInput = screen.getByLabelText("Upload new dataset");
const testFile = new File(['hello'], 'hello.csv', {type: 'text/csv'});
Expand Down
43 changes: 43 additions & 0 deletions test/components/ChooseScale.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react";
import {render, screen} from "@testing-library/react";
import {
mockAppState,
mockDatasetSettings
} from "../mocks";
import {
RootDispatchContext,
RootContext,
ActionType
} from "../../src/RootContext";
import ChooseScale from "../../src/components/ChooseScale";
import {userEvent} from "@testing-library/user-event";

describe("<ChooseScale />", () => {

test("can change scale", async () => {
const dispatch = jest.fn();
const state = mockAppState({
selectedDataset: "d1",
datasetSettings: {
"d1": mockDatasetSettings()
}
});
render(
<RootContext.Provider value={state}>
<RootDispatchContext.Provider value={dispatch}>
<ChooseScale/>
</RootDispatchContext.Provider>
</RootContext.Provider>);

const select = screen.getByRole("combobox") as HTMLSelectElement;
expect(select.value).toBe("natural");
expect(select.item(1)!!.value).toBe("log");
expect(select.item(2)!!.value).toBe("log2");
await userEvent.selectOptions(select, "log");

expect(dispatch.mock.calls[0][0]).toEqual({
type: ActionType.SELECT_SCALE,
payload: "log"
});
});
});
Loading

0 comments on commit 57202ab

Please sign in to comment.