Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 6 additions & 1 deletion .github/workflows/cogstack-cohorter-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ on:
- ".github/workflows/cogstack-cohorter-docker**"

jobs:
docker:
docker-build-and-push:
runs-on: ubuntu-latest
if: github.event.pull_request.user.login != 'dependabot[bot]' && github.repository == 'CogStack/cogstack-platform'
permissions:
contents: read
packages: read
strategy:
matrix:
include:
Expand Down Expand Up @@ -69,3 +72,5 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ matrix.image }}:buildcache
cache-to: type=registry,ref=${{ matrix.image }}:buildcache,mode=max
secrets: |
"npm_token=${{ github.token }}"
37 changes: 37 additions & 0 deletions cogstack-cohorter/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Single env file for cogstack-cohorter.
# Copy to .env and fill in values before running docker-compose or npm run dev.
# .env is gitignored — never commit it.
#
# This file is read by:
# • docker-compose — variable substitution for build args and runtime env
# • Vite dev server — VITE_* vars picked up via envDir in vite.config.js

# ── Required ─────────────────────────────────────────────────────────────────

# GitHub Packages token with read:packages scope.
# Used as a BuildKit secret at image build time to install @cogstack/frontend-common-react.
# Also needed locally for `npm install` in WebAPP/client-react/ (export NPM_TOKEN in your shell,
# or ensure it is set here so docker-compose can pass it through).
NPM_TOKEN=ghp_your_token_here

# ── OAuth2 proxy paths (Vite build-time, baked into the client bundle) ───────
# Only override if your deployment uses non-standard oauth2-proxy routes.
# Defaults match standard oauth2-proxy conventions; leave commented out
# unless you need something different.

# VITE_OAUTH2_USERINFO_PATH=/oauth2/userinfo
# VITE_OAUTH2_LOGIN_PATH=/oauth2/sign_in
# VITE_OAUTH2_LOGOUT_PATH=/oauth2/sign_out?rd=/

# ── Default user (server runtime, pre-oauth2-proxy deployment) ───────────────
# Returned by GET /oauth2/userinfo when oauth2-proxy is not yet deployed.
# Has no effect once oauth2-proxy is in front of the app.

# DEFAULT_USER_NAME=Local User
# DEFAULT_USER_EMAIL=
# DEFAULT_USER_ID=local
# DEFAULT_USER_GROUPS= # comma-separated, e.g. "cohorter-users,role:admin"

# ── Other runtime options ─────────────────────────────────────────────────────

# RANDOM_DATA=true # generate synthetic patient data on first startup
1 change: 1 addition & 0 deletions cogstack-cohorter/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# =========================
.env
.env.*
!.env.example
*.key
*.pem
*.p12
Expand Down
1 change: 1 addition & 0 deletions cogstack-cohorter/WebAPP/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ Thumbs.db
# =========================
.env
.env.*
!.env.example
secrets/
30 changes: 26 additions & 4 deletions cogstack-cohorter/WebAPP/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
FROM node:latest
FROM node:20-alpine

ARG VITE_OAUTH2_USERINFO_PATH
ARG VITE_OAUTH2_LOGIN_PATH
ARG VITE_OAUTH2_LOGOUT_PATH

# VITE_* vars must be ENV (not just ARG) for Vite to pick them up at build time.
ENV VITE_OAUTH2_USERINFO_PATH=${VITE_OAUTH2_USERINFO_PATH}
ENV VITE_OAUTH2_LOGIN_PATH=${VITE_OAUTH2_LOGIN_PATH}
ENV VITE_OAUTH2_LOGOUT_PATH=${VITE_OAUTH2_LOGOUT_PATH}

WORKDIR /usr/src/app
RUN mkdir -p client-react
COPY client-react/ ./client-react/
WORKDIR /usr/src/app/client-react


RUN --mount=type=secret,id=npm_token,required=true \
sh -c 'echo "@cogstack:registry=https://npm.pkg.github.com/" > ~/.npmrc && \
echo "//npm.pkg.github.com/:_authToken=$(cat /run/secrets/npm_token)" >> ~/.npmrc && \
npm ci && npm run build'

# Stage 2: Server
WORKDIR /usr/src/app
COPY server/package.json server/package-lock.json* ./server/
RUN cd server && npm install

COPY . .
RUN cd /usr/src/app/client-react && npm install && npm run build
RUN cd /usr/src/app/server && npm install
COPY server/ ./server/

COPY entrypoint.sh /entrypoint.sh
RUN sed -i 's/\r$//' /entrypoint.sh && chmod +x /entrypoint.sh
Expand Down
26 changes: 17 additions & 9 deletions cogstack-cohorter/WebAPP/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@
This webapp is a cohort app for users to obtain the number of patients satifying the search query. Both structured and unstructured data are used. Structured data include age, gender, dod and ethnicity. Unstructured (text) data are processed using [MedCAT](https://github.com/CogStack/MedCAT) and the MedCAT annotations are used for searching.

### Frontend
This repository currently contains two frontend implementations:

- `client-react` (React shell frontend): React 18 + `react-scripts`, using reusable HTML template components and a lightweight Alpine-style runtime.
- `client-react` (React shell frontend): React 18 + **Vite**, using reusable HTML template components and a lightweight Alpine-style runtime. The top-level UI chrome (header, auth) is provided by `@cogstack/frontend-common-react`.

Other runtime dependencies include [ECharts](https://echarts.apache.org/en/index.html) for charts and [popper.js](https://popper.js.org/) for tooltips.

To work on the React frontend:
A GitHub Packages token with `read:packages` scope is required to install `@cogstack/frontend-common-react`. Set it in a `.env` file at the `cogstack-cohorter/` root (see `.env.example`) or export it in your shell:

```bash
export NPM_TOKEN=ghp_your_token_here
```

To work on the React frontend (Vite dev server on `http://localhost:5173`, proxies API calls to the Express backend at `http://localhost:3000`):

```bash
cd client-react
npm install
npm start
npm run dev
```

To build the React frontend:
Expand All @@ -24,7 +29,7 @@ cd client-react
npm run build
```

The React build is generated by CRA and then moved to `client-react/dist/`.
Build output goes to `client-react/dist/`, which the Express server serves as static files.

### Backend
The backend of the app is in the `server` folder which is a [node.js](https://nodejs.org/en/) application (v14 or higher) using [express.js](https://expressjs.com/) for the web/api server and [flexsearch](https://github.com/nextapps-de/flexsearch) for indexing and searching SNOMED terms. In order to run the app, the required data has to be prepared. First, extract the snomed terms by running `cd server/data && tar xzvf snomed_terms_data.tar.gz` from the app folder, it will extract 2 files, `snomed_terms.json` is an array of SNOMED terms while `cui_pt2ch.json` contains the parent-to-child relationships of the SNOMED terms. For patients data, 6 files are needed in the `server/data/` folder:
Expand All @@ -39,15 +44,18 @@ There is a script `gen_random_data.js` in `server/data/` folder to generate the

Please make sure to have the six data files ready in the `server/data/` folder before starting the server. To start the server, in the app folder run `cd server && npm install && npm run start`.

There is also a Dockerfile in the app folder if using docker, to build and run the container, run `docker build --tag cohortingtool/webapp . && docker run -p 3000:3000 cohortingtool/webapp`.
The recommended way to run the full stack (webapp + NL2DSL + MedCAT + Ollama) is via `docker-compose` from the `cogstack-cohorter/` root — see the root-level README and `.env.example` for details. The `NPM_TOKEN` is passed as a BuildKit secret; set it in the `.env` file before building.

### Run using Docker with random data
The following code can be used to run the app using Docker with random data:

Set `RANDOM_DATA=true` in your `.env` file (or leave it unset — it defaults to `true`), then from the `cogstack-cohorter/` root:

```bash
docker build --tag cohortingtool/webapp --build-arg random=true .
docker run -p 3000:3000 cohortingtool/webapp
docker compose up --build
```

The webapp will be available at `http://localhost:3000`.

### Generate data from CogStack
The following code snippet can be used to generate the 4 data (json) files if you have access to [Cogstack](https://github.com/CogStack).
```python
Expand Down
4 changes: 4 additions & 0 deletions cogstack-cohorter/WebAPP/client-react/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Resolve @cogstack packages from GitHub Packages registry.
# NPM_TOKEN must be set in the environment (CI secret or local ~/.npmrc).
@cogstack:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
95 changes: 57 additions & 38 deletions cogstack-cohorter/WebAPP/client-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,72 @@

This folder contains the React-based frontend shell for Cohorter.

It keeps the existing Alpine-style template/data model behavior, while using React for mounting and app lifecycle.
It keeps the existing Alpine-style template/data model behaviour, while using React for mounting, app lifecycle, and the top-level UI chrome (header, auth).

## What this app is

- React 18 app bootstrapped with `react-scripts`
- React 18 app built with **Vite**
- `@cogstack/frontend-common-react` — provides `Header`, `UserSection`, `UserModal`, `Login`, and `AccessDenied` components
- Custom lightweight Alpine-like runtime (`src/alpine-runtime.js`)
- Data/state layer in `src/alpine-data.js`
- Data/state layer in `src/alpine-data.js`)
- HTML template composition using reusable partials in `public/components/`

## Requirements

- Node.js 14+
- Node.js 18+
- npm
- Cohorter backend server running (same origin by default)
- A GitHub Packages token with `read:packages` scope (to install `@cogstack/frontend-common-react`)
- Cohorter backend server running at `http://localhost:3000` (proxied automatically in dev)

## Environment variables

A single `.env` file at the **`cogstack-cohorter/` root** (one level up from `WebAPP/`) is shared between Vite and `docker-compose`. Copy `.env.example` there and fill in values:

```bash
cp ../../.env.example ../../.env
```

| Variable | Required | Description |
|---|---|---|
| `NPM_TOKEN` | Yes | GitHub Packages token (`read:packages`) for installing the library |
| `VITE_OAUTH2_USERINFO_PATH` | No | Override the `/oauth2/userinfo` path |
| `VITE_OAUTH2_LOGIN_PATH` | No | Override the `/oauth2/sign_in` path |
| `VITE_OAUTH2_LOGOUT_PATH` | No | Override the `/oauth2/sign_out?rd=/` path |

`VITE_*` variables are baked into the client bundle at build time.

## Install

```bash
export NPM_TOKEN=ghp_your_token_here # or set in your shell profile
npm install
```

## Run in development

```bash
npm start
npm run dev
```

By default CRA serves on `http://localhost:3000`.

This frontend expects backend endpoints on the same origin (for example `/get_query_result`, `/keywords`, `/nl2dsl`, etc.).
If your backend runs on a different host/port, add a proxy strategy before local development.
Vite serves on `http://localhost:5173` and proxies all API and auth routes (`/oauth2`, `/query`, `/nl2dsl`, `/export`, etc.) to the Express backend at `http://localhost:3000`.

## Build for production

```bash
npm run build
```

Build output is produced by CRA and then moved to `dist/`:

- `build/` is generated by CRA
- `dist/` is the final deployable folder for this project

## Test

```bash
npm test
```
Output goes to `dist/`. The Express server in `WebAPP/server/` serves this folder as static files.

## Project structure

```text
client-react/
├── index.html
├── public/
│ ├── app-template.html
│ ├── components/
│ │ ├── topbar.html
│ │ ├── topbar.html
│ │ ├── sidebar-desktop.html
│ │ ├── sidebar-mobile.html
│ │ ├── query-v1.html
Expand All @@ -68,38 +77,48 @@ client-react/
│ │ └── busy-pill.html
│ └── assets/
├── src/
│ ├── index.js
│ ├── App.js
│ ├── main.jsx
│ ├── App.jsx
│ ├── alpine-runtime.js
│ ├── alpine-data.js
│ └── template-components.js
├── tailwind.config.js
├── vite.config.js
├── package.json
└── dist/
└── dist/
```

## Runtime flow

1. `src/index.js` mounts React app.
2. `src/App.js` calls `mountCohorterApp()`.
3. Runtime fetches `public/app-template.html`.
1. `src/main.jsx` mounts the React app and imports the library CSS (`@cogstack/frontend-common-react/style.css`).
2. `src/App.jsx` renders the `Header` (with `UserSection`) and calls `mountCohorterApp()`.
3. The runtime fetches `public/app-template.html`.
4. `coh-include` nodes are expanded from `public/components/*.html`.
5. Alpine-like directives are compiled and bound to state from `createAppState()`.
6. Initial query submission runs automatically.

## Backend API endpoints used
## Authentication

The frontend currently calls:
`UserSection` fetches `/oauth2/userinfo` on mount. Before oauth2-proxy is deployed, the Express server returns a configurable default user from environment variables (`DEFAULT_USER_*`). Once oauth2-proxy is deployed it intercepts `/oauth2/*` before Express, so the stub has no effect.

## Backend API endpoints used

- `POST /keywords`
- `POST /nl2dsl`
- `POST /get_query_result`
- `POST /get_filter_result`
- `POST /get_age`
- `POST /get_top_terms`
- `POST /compare_query`
- `POST /export`
| Method | Path | Description |
|---|---|---|
| `GET` | `/oauth2/userinfo` | Current user info (stub or oauth2-proxy) |
| `POST` | `/keywords` | Keyword suggestions |
| `POST` | `/nl2dsl` | Natural language → DSL compilation |
| `POST` | `/get_query_result` | Execute a cohort query |
| `POST` | `/get_filter_result` | Apply filters to a query result |
| `POST` | `/get_age` | Age distribution for a cohort |
| `POST` | `/get_top_terms` | Top clinical terms for a cohort |
| `POST` | `/compare_query` | Compare two queries |
| `POST` | `/export` | Export patient list |
| `POST` | `/admin_login` | Admin session login (pre-oauth2-proxy) |
| `POST` | `/admin_logout` | Admin session logout (pre-oauth2-proxy) |

## Notes

- This is an incremental migration: React hosts the app, while template behavior is still Alpine-style.
- This is an incremental migration: React hosts the app and renders the top chrome, while template behaviour is still Alpine-style.
- Reusable UI sections are maintained as HTML partials in `public/components/`.
- Tailwind `preflight` is disabled in `tailwind.config.js` to avoid conflicts with the Alpine.js Tailwind CSS loaded via `public/assets/css/tailwind.output.css`.
41 changes: 41 additions & 0 deletions cogstack-cohorter/WebAPP/client-react/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CogStack Cohorter</title>
<link rel="icon" href="/assets/Cogstack-Logo.svg" />

<!-- Initialise dark mode before first paint to avoid flash -->
<script>
if (
localStorage.theme === 'dark' ||
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
</script>

<link rel="stylesheet" href="/assets/css/google.fonts.css" />
<link rel="stylesheet" href="/assets/css/tailwind.output.css" />
<script type="text/javascript" src="/assets/js/echarts.min.js"></script>
<script src="/assets/js/popper.min.js" charset="utf-8"></script>

<style>
/* Alpine.js cloak — hide x-cloak elements until Alpine initialises */
[x-cloak] { display: none !important; }

input[type='date']::-webkit-calendar-picker-indicator {
padding: 0;
margin-left: -16px;
}
</style>
</head>
<body class="overflow-x-hidden">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
Loading
Loading