Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2c939ea
hide devserver warning in dev mode
shapiromatron Jun 10, 2025
4ff8817
Bump requests from 2.32.3 to 2.32.4 in the pip group across 1 directo…
dependabot[bot] Jun 10, 2025
863da68
fix djhtml on windows
shapiromatron Jun 10, 2025
1275716
add link to admin on assessment if staff (#1189)
shapiromatron Jun 10, 2025
7441f6b
Remove velocity-react dependency; replace with Animation Component (#…
Copilot Jun 11, 2025
50611d9
Remove settings from exception report (#1199)
shapiromatron Aug 15, 2025
b6069a4
activate timezone (#1198)
shapiromatron Aug 15, 2025
5c25004
change legend location based on data direction (#1200)
shapiromatron Aug 18, 2025
e655d91
Update React to v17.0.2 (#1183)
Copilot Aug 22, 2025
0fcf058
add copilot setup instructions (#1201)
shapiromatron Aug 24, 2025
fd7bb1b
read-only permission for workflow
shapiromatron Aug 24, 2025
6e35ed0
remove tests; add concurrency check
shapiromatron Aug 24, 2025
298ef20
Update MobX from version 5 to version 6 (#1194)
Copilot Aug 24, 2025
e53a164
Update Quill and react-quill packages to latest versions with API mig…
Copilot Aug 25, 2025
07d23be
Update React to v18 and migrate to createRoot API (#1205)
Copilot Aug 25, 2025
2f5252c
CI -verbose tests; block network access (#1210)
shapiromatron Aug 28, 2025
aa58d3e
revise bmd based on interval feedback (#1211)
shapiromatron Sep 3, 2025
5ccbe08
update package versions (#1212)
shapiromatron Sep 3, 2025
075dd4e
Heatmap labels (#1214)
shapiromatron Sep 17, 2025
c8da261
prepare 2025.1rc1 (#1215)
shapiromatron Sep 18, 2025
a10f15b
Updating .gitignore for claude use (#1)
shanethacker Feb 2, 2026
82fecc8
use new comptox api (#1272) (#2)
shanethacker Feb 2, 2026
d055a7d
Chore: update django for prisma (#3)
shanethacker Feb 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Health Assessment Workspace Collaborative (HAWC)

HAWC is a web-based platform for capturing, analyzing, and sharing data related to human-health assessments of chemicals and environmental exposures. It supports hazard identification and quantitative risk assessment workflows for researchers and regulatory agencies.

## Folder Structure

- `hawc/`: Python Django backend application
- `hawc/client/`: Python client package
- `frontend/`: JavaScript/React frontend application
- `frontend/tests/`: JavaScript/React tets
- `compose/`: Docker Compose files and deployment configuration
- `tests/`: Python backend tests
- `tests/integration/`: Python integration tests which require the JavaScript code to be compiled. This can be done either in a separate process (via `npm run start`; preferred) or built (via `npm run build`).
- `docs/`: documentation for development and overall structure of application

## Libraries and Frameworks

- **Backend:** Python 3.13, Django, uv (dependency management), flit (packaging)
- **Frontend:** React, MobX, Yarn (dependency management), Vite (build tool)
- **Testing:** Pytest, Coverage.py, Playwright (integration), Jest (JS unit tests)

The HAWC application is a mixture of Django HTML templates for some pages, and then a Django API plus JavaScript application for serving more dynamic pages. We use simpler HTML templates where possible, and only use JavaScript for more complex interactions. We are increasingly using htmx for more complex django HTML template based interactions.

## Code Standards (Linting/Formatting)

- **Python:**
- Lint with `poe lint-py` (ruff)
- HTML linting with `poe lint-html`
- **JavaScript:**
- Lint with `yarn --cwd ./frontend run lint` (uses biome.js)

## Testing (Python)

Run backend tests: `pytest --record-mode=none`

## Testing (JavaScript)

Run frontend tests: `yarn --cwd ./frontend run test`

## Testing (Integration)

Integration tests require both backend and frontend to be available. The integration tests are run using Python and the playwright integration to check browser interactions.

Python integration tests which require the JavaScript code to be compiled. To compile:

- **Preferred:** Start the frontend in a separate process: `yarn --cwd ./frontend run`
- **Alternative:** Build the frontend before running integration tests: `yarn --cwd ./frontend build`

The preferred approach allows for faster development cycles since it doesn't require a compile step.

To run integration tests: `py.test -sv tests/integration/`

## References:

* See GitHub actions in `.github/workflows`
* See documentation in `docs/`
* For editing [this file](https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions?tool=vscode):

77 changes: 77 additions & 0 deletions .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/customize-the-agent-environment
name: "Copilot Setup Steps"

on:
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml
schedule:
- cron: "14 3 1 */1 *" # At 03:14 on the 1st of every month

permissions:
contents: read

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

jobs:
# The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
copilot-setup-steps:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: hawc
POSTGRES_PASSWORD: password
POSTGRES_DB: hawc
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
ports:
- 5432:5432
redis:
image: redis:6-alpine
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
ports:
- 6379:6379

env:
DJANGO_DB_NAME: hawc
DJANGO_DB_USER: hawc
DJANGO_DB_PW: password
DJANGO_DB_HOST: localhost
DJANGO_CACHE_LOCATION: redis://localhost:6379/0

steps:
- name: Checkout code
uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "yarn"
cache-dependency-path: "frontend/yarn.lock"
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
cache-dependency-glob: "**/pyproject.toml"
- name: install
run: |
uv pip install --system -e ".[dev,docs]"
uv pip install --system -e client
- name: install
run: |
yarn --cwd ./frontend install --frozen-lockfile

# check environment is setup correctly
# - name: test
# run: |
# pytest --record-mode=none
# yarn --cwd ./frontend run test
6 changes: 3 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:
if [ "$RUNNER_OS" == "Linux" ]; then
export "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib"
fi
coverage run -m pytest --record-mode=none
coverage run -m pytest -v --block-network --allowed-hosts=127.0.0.1,::1 --record-mode=none
echo "# Python coverage report" >> $GITHUB_STEP_SUMMARY
coverage report --format=markdown -i >> $GITHUB_STEP_SUMMARY || true
coverage html -d coverage -i
Expand Down Expand Up @@ -162,13 +162,13 @@ jobs:
id: test
continue-on-error: true
run: |
uv run py.test -sv tests/integration/
uv run py.test -v --block-network --allowed-hosts=127.0.0.1,::1 tests/integration/
- name: run integration tests (retry)
if: steps.test.outcome=='failure'
id: retry
continue-on-error: true
run: |
uv run py.test -sv --lf tests/integration/
uv run py.test -v --block-network --allowed-hosts=127.0.0.1,::1 --lf tests/integration/
- name: set status
if: always()
run: |
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ hawc/gitcommit.json
hawc/main/settings/local.py
package-lock.json
uv.lock
CLAUDE.md

# folders
*.egg-info/
Expand All @@ -28,6 +29,8 @@ uv.lock
.ruff_cache/
.venv/
.vscode/
.serena/
.claude/
/data/
docs/site
__pycache__/
Expand Down
2 changes: 1 addition & 1 deletion client/hawc_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from .summary import SummaryClient
from .vocab import VocabClient

__version__ = "2024.4"
__version__ = "2025.1.1"
__all__ = ["BaseClient", "HawcClient", "HawcClientException", "HawcServerException"]


Expand Down
2 changes: 1 addition & 1 deletion compose/app/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ COPY ./dist /app/build/dist
WORKDIR /app/build

RUN python -m pip install -U pip --root-user-action=ignore && \
python -m pip install "$(ls dist/hawc-*.whl | head -1)[prod]" --no-cache-dir && \
python -m pip install "$(ls -t dist/hawc-*.whl | head -1)[prod]" --no-cache-dir && \
mkdir -p /app/logs && \
python -m pip uninstall -y pip setuptools && \
rm -rf /app/build && \
Expand Down
2 changes: 1 addition & 1 deletion compose/example.env
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ DJANGO_HTTPS_ONLY=False
DJANGO_EMAIL_BACKEND=CONSOLE
ADMIN_URL_PREFIX=c6cbceb2-0592-497b-bd92-5d881a1fae40
PUBMED_API_KEY=8852fcfa8cc0850a16d7e24d21e23f77
CCTE_API_KEY=0468d596fe45f2e7b562318d80b11ea2
EPA_COMPTOX_API_KEY=0468d596fe45f2e7b562318d80b11ea2
HAWC_SESSION_DURATION=604800
HAWC_ANYONE_CAN_CREATE_ASSESSMENTS=True
HAWC_PM_CAN_MAKE_PUBLIC=True
Expand Down
2 changes: 1 addition & 1 deletion compose/nginx/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM nginx:stable-alpine
FROM nginx:alpine-slim

RUN apk update && \
apk upgrade --available && \
Expand Down
2 changes: 1 addition & 1 deletion compose/redis/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM redis:7-alpine
FROM redis:8-alpine

RUN apk update && \
apk upgrade --available && \
Expand Down
17 changes: 9 additions & 8 deletions docs/docs/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,26 +58,27 @@ Client tutorials for common operations are below:

### Changelog

#### [2025-1](https://pypi.org/project/hawc-client/2025.1/) (TBD)
#### [2025.1](https://pypi.org/project/hawc-client/2025.1/) (TBD)

* Add `unpublished` parameter to bmd dataset download
* Remove data pivot API endpoints; all behaviors can be accomplished using the visual API
* Publish package on PyPI using GitHub Actions

#### [2024-4](https://pypi.org/project/hawc-client/2024.4/) (January 2025)
#### [2024.4](https://pypi.org/project/hawc-client/2024.4/) (January 2025)

* Added assessment team member API endpoint to retrieve assessments a user is a member of
* Added literature tag API {create, update, delete, move} to modify tags

#### [2024-3](https://pypi.org/project/hawc-client/2024.3/) (October 2024)
#### [2024.3](https://pypi.org/project/hawc-client/2024.3/) (October 2024)

* Update data pivot and visual output formats to return JSON
* Add a new BMDS Export to return dose-response data

#### [2024-2](https://pypi.org/project/hawc-client/2024.2/) (July 2024)
#### [2024.2](https://pypi.org/project/hawc-client/2024.2/) (July 2024)

* Minor documentation and docstring updates

#### [2024-1](https://pypi.org/project/hawc-client/2024.1/) (February 2024)
#### [2024.1](https://pypi.org/project/hawc-client/2024.1/) (February 2024)

* Add new interactive client for downloading visual and data pivots images
* Updated study evaluation/risk of bias APIs
Expand All @@ -86,20 +87,20 @@ Client tutorials for common operations are below:
* Add APIs to modify Visuals and Data Pivots
* Add APIs to modify Assessments, Assessment Values, and Assessment Details

#### [2023-2](https://pypi.org/project/hawc-client/2023.2/) (April 2023)
#### [2023.2](https://pypi.org/project/hawc-client/2023.2/) (April 2023)

* Add new `login` parameter to `client.set_authentication_token`, and returns boolean instead of a dict.
- If `login` is False (default), a token based API session is created. This is consistent with previous behavior.
- If `login` is True, a django cookie-based session is set instead of a token-based API session. This may be used if using the client to browse the website, however CSRF tokens will be required for API and form requests.
* Added ``client.lit.reference_user_tags`` - retrieves all user tag for all references for an assessment.

#### [2023-1](https://pypi.org/project/hawc-client/2023.1/) (January 2023)
#### [2023.1](https://pypi.org/project/hawc-client/2023.1/) (January 2023)

* Added ``client.riskofbias.compare_metrics`` - align metrics when copying across assessments via fuzzy text matching
* Added ``client.summary.datapivot_list`` - retrieve all data pivots for an assessment
* Added ``client.summary.table_list`` - retrieve all tables for an assessment

#### [2022-7](https://pypi.org/project/hawc-client/2022.7/) (July 2022)
#### [2022.7](https://pypi.org/project/hawc-client/2022.7/) (July 2022)

* Added ``client.study.studies`` to return dataframe of studies for an assessment
* Added new parameter, ``invert``, to the ``client.animal.endpoints``
Expand Down
8 changes: 4 additions & 4 deletions frontend/animal/AnimalGroupTable/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import {Provider} from "mobx-react";
import React from "react";
import ReactDOM from "react-dom";
import {createRoot} from "react-dom/client";

import App from "./App";
import Store from "./store";

export default function (el, config) {
const store = new Store(config);
ReactDOM.render(
const root = createRoot(el);
root.render(
<Provider store={store}>
<App />
</Provider>,
el
</Provider>
);
}
3 changes: 2 additions & 1 deletion frontend/animal/AnimalGroupTable/store.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import _ from "lodash";
import {action, computed, observable, toJS} from "mobx";
import {action, computed, makeObservable, observable, toJS} from "mobx";
import TableFootnotes from "shared/utils/TableFootnotes";

import Endpoint from "../Endpoint";
Expand All @@ -17,6 +17,7 @@ class AnimalGroupTableStore {
@observable sortValue = 1;

constructor(data) {
makeObservable(this);
this.endpoints = data.endpoints.map(d => new Endpoint(d));
this.footnotes = new TableFootnotes();
}
Expand Down
6 changes: 4 additions & 2 deletions frontend/animal/BMDResult.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import _ from "lodash";
import PropTypes from "prop-types";
import React from "react";
import ReactDOM from "react-dom";
import {createRoot} from "react-dom/client";

import h from "shared/utils/helpers";

import EndpointCriticalDose from "./EndpointCriticalDose";
Expand Down Expand Up @@ -55,7 +56,8 @@ BmdResultComponent.propTypes = {

class BMDResult extends EndpointCriticalDose {
update() {
ReactDOM.render(<BmdResultComponent endpoint={this.endpoint} />, this.span[0]);
const root = createRoot(this.span[0]);
root.render(<BmdResultComponent endpoint={this.endpoint} />);
}
}

Expand Down
6 changes: 5 additions & 1 deletion frontend/animal/DRPlot.js
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,11 @@ class DRPlot extends D3Plot {
if (this.legend_left) {
legend_settings.box_l = this.legend_left;
} else {
legend_settings.box_l = 10;
let isIncreasing = true;
if (Array.isArray(this.values) && this.values.length > 0) {
isIncreasing = this.values[0].y < this.values[this.values.length - 1].y;
}
legend_settings.box_l = isIncreasing ? 10 : this.w - 80;
}

if (this.legend_top) {
Expand Down
8 changes: 4 additions & 4 deletions frontend/animal/EndpointForm/index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import {Provider} from "mobx-react";
import React from "react";
import ReactDOM from "react-dom";
import {createRoot} from "react-dom/client";

import App from "./App";
import Store from "./store";

export default function (el, config) {
const store = new Store(config);

ReactDOM.render(
const root = createRoot(el);
root.render(
<Provider store={store}>
<App />
</Provider>,
el
</Provider>
);
}
3 changes: 2 additions & 1 deletion frontend/animal/EndpointForm/store.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import _ from "lodash";
import {action, computed, observable} from "mobx";
import {action, computed, makeObservable, observable} from "mobx";
import h from "shared/utils/helpers";

import {termUrlLookup} from "./constants";
Expand All @@ -11,6 +11,7 @@ class EndpointFormStore {
@observable useControlledVocabulary = null;

constructor(config) {
makeObservable(this);
this.config = config;
this.setUseControlledVocabulary();
}
Expand Down
3 changes: 2 additions & 1 deletion frontend/animal/EndpointListApp/EndpointListStore.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import _ from "lodash";
import {action, computed, observable, toJS} from "mobx";
import {action, computed, makeObservable, observable, toJS} from "mobx";
import h from "shared/utils/helpers";

const CRITICAL_VALUES = ["noel", "loel", "fel", "bmd", "bmdl"];
Expand All @@ -16,6 +16,7 @@ class EndpointListStore {
@observable criticalValueOptions = null;

constructor(config) {
makeObservable(this);
this.config = config;
this.getDataset();
}
Expand Down
Loading
Loading