diff --git a/README.md b/README.md index 20028e43..4d925529 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,9 @@ This project was initally associated with The University of Auckland SOFTENG 761 # Contributing -Onboarding documentation can be found in the Github Wiki under `Wiki/Onboarding`. +[Documentation](./wiki/VPS%20Home.md) for the project lives here in this repo, with some basic onboarding / setup information and (very!) partial documentation of the codebase. -This project uses the [conventional commits](https://www.conventionalcommits.org) specification for commit messages. +Try to use the [conventional commits](https://www.conventionalcommits.org) specification for commit messages, it makes it easier for everyone. # Contributors ✨ diff --git a/wiki/Auth Implementation Guide.md b/wiki/Auth Implementation Guide.md new file mode 100644 index 00000000..01829f8a --- /dev/null +++ b/wiki/Auth Implementation Guide.md @@ -0,0 +1,117 @@ +# Auth Implementation Guide + +## Frontend Auth + +Accessing Auth Protected API Endpoints + +To access an authentication-protected backend, follow these steps: + +### 1. Include the Auth Token in the Header + +Ensure that the authorization token is included in the header of every request. This token is typically retrieved after a successful login. + +Example: + +``` +// import user Token from Context +const { user, loading, error } = useContext(AuthenticationContext); + +//Check if finished loading +if (loading) { + // handle loading state + //show loading spinner or smt +} if (error) { + //handle error +} if (user) { + const token = await user.getIdToken(); // Function to retrieve the auth token + const response = await fetch("https://your-api-endpoint.com", { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }); + const data = await response.json(); +} +``` + +### 2. Handle Token Expiry and Refresh + +Ensure your application handles token expiry. Implement logic to refresh the token when it expires. + +Example: + +``` +const fetchWithAuth = async (url, options = {}) => { + let token = await getAuthToken(); + let response = await fetch(url, { + ...options, + headers: { + ...options.headers, + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }); + if (response.status === 401) { + // Refresh the token if it's expired and retry the request + token = await refreshAuthToken(); + response = await fetch(url, { + ...options, + headers: { + ...options.headers, + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }); + } + return response; +}; +``` + +## Backend Auth + +### Implementing an Auth Protected Backend Endpoint + +To implement authentication-protected backend endpoints using Firebase authentication and scenario-based authorization, follow these steps: + +#### Protect Your API Routes with the Middlewares + +Apply these the Firebase authorization and scenario authentication middlewares to your API routes to ensure they are protected by both Firebase authorization and scenario-based authentication . + +Example: + +(Example is from `scenario.js`) + +1. Import authorization from firebaseAuth and import Scenario Authentication from scenarioAuth (also make sure you import the functions from the dao) + +``` +import { Router } from "express"; +import auth from "../../middleware/firebaseAuth"; +import scenarioAuth from "../../middleware/scenarioAuth"; +import { + createScenario, + retrieveScenarioList, + updateScenario, + deleteScenario, + updateDurations, +} from "../../db/daos/scenarioDao"; +import { retrieveAssignedScenarioList } from "../../db/daos/userDao"; +``` + +2. Apply Firebase Authorization protection to your routes + +``` +// Apply Firebase auth middleware to all routes +router.use(auth); + +// put API end points that require firebase Auth here +``` + +3. Apply scenario Authentication protection to your routes + +``` +// Apply scenario auth middleware +router.use("/:scenarioId", scenarioAuth); + +// put API end points that require scenario auth here +``` diff --git a/wiki/backend-api.md b/wiki/Backend API.md similarity index 90% rename from wiki/backend-api.md rename to wiki/Backend API.md index 7a765ca1..999902b7 100644 --- a/wiki/backend-api.md +++ b/wiki/Backend API.md @@ -1,4 +1,7 @@ -## Backend API +# Backend API + +> [!WARNING] +> I'm unsure how accurate this documentation is, given that it wasn’t updated at all during 2024. If you find something inaccurate or something missing, please correct it or add in some brief documentation for the endpoint. If you’re unsure ask someone else, it would be great to get it fully accurate at some point. - Hartej ### Routes diff --git a/wiki/Editor/Text Model.md b/wiki/Editor/Text Model.md new file mode 100644 index 00000000..5bd0428b --- /dev/null +++ b/wiki/Editor/Text Model.md @@ -0,0 +1,69 @@ +# Text Model + +The text model is going to be a tree structure to support span based styling, similar to other editors like google slides. + +> [!WARNING] +> Use the types defined in the codebase as the actual source of truth, since this document might not reflect any minor changes made in the actual implementation + +## Text Structure + +### Text Shape + +The highest level text structure, used for standard text boxes and other components like buttons. + +```ts +interface TextShape { + id: string; + type: "text"; + x: number; + y: number; + width: number; + height: number; + blocks: TextBlock[]; + style?: Partial; +} +``` + +### Text Block + +Represents paragraph blocks. + +```ts +interface TextBlock { + id: string; + style?: Partial; + spans: TextSpan[]; +} +``` + +### Text Span + +```ts +interface TextSpan { + text: string; + style?: Partial; +} +``` + +## Style Structure + +The lower level properties (block and span) take precedence over the base properties when both are defined. + +```ts +interface BaseTextStyle extends BlockTextStyle, SpanTextStyle {} + +interface BlockTextStyle { + alignment: "left" | "centre" | "right"; + lineHeight: number; +} + +interface SpanTextStyle { + fontFamily: "string"; + fontSize: number; + fontWeight: string; + fontStyle: string; + textDecoration: string; + textColor: HexString; + highlightColor: HexString; +} +``` diff --git a/wiki/Onboarding/Downloading-Packages.md b/wiki/Onboarding/Downloading-Packages.md deleted file mode 100644 index fbf1243c..00000000 --- a/wiki/Onboarding/Downloading-Packages.md +++ /dev/null @@ -1,45 +0,0 @@ -# Downloading Packages - -VPS runs in the MERN stack (MongoDB, Express, React, and NodeJS), and thus it is required to download a few tools to run this. -There are also a few recommended tools to download, for ease of use, version control, and code editing. - - -## Node - -Node is an open-source, cross-platform JavaScript runtime environment that allows developers to execute JavaScript code server-side. -If using nvm (see below), it can be installed by running `nvm install 20` or it can be downloaded [from the node website](https://nodejs.org/en) (note that clicking 'download' on this website installs the current version of Node instead of Node v20, so go to previous releases). To verify it installed successfully, run `node --version` in the terminal. - -### Versions of Node/NVM - -However, Node has many different versions available. This codebase has been developed on Node version of 20. - -In order to easily switch between Node versions (especially recommended if you are using Node for other projects as well), it is recommended to install nvm (Node Version Manager) which allows you to have multiple versions of Node on your machine simultaneously. This can be downloaded: -Windows: [https://github.com/coreybutler/nvm-windows](https://github.com/coreybutler/nvm-windows) -Mac/Linux/WSL: [https://github.com/nvm-sh/nvm](https://github.com/nvm-sh/nvm) or by running `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh` in the terminal -For extra info on nvm installation, see [this link](https://www.freecodecamp.org/news/node-version-manager-nvm-install-guide/) -To check nvm installed correctly, run the command `nvm --version` in the terminal. - -## NPM and Yarn - -NPM, which stands for Node Package Manager, is the default package manager for Node.js. It is a command-line tool that allows developers to install, share, and manage dependencies for their Node.js projects. Note that this is different to NVM (Node Version Manager, mentioned above). -NPM should install automatically when you install Node, and this can be checked by running the terminal command: `npm --version`. - -Yarn is a package manager for JavaScript that serves as an alternative to npm (basically it is a better version of NPM which we use to manage our repo). -This project works with Yarn 1.22.21, which can be installed by running the following terminal command: `npm install -g yarn`. -If you already have yarn installed, uninstall it via `npm uninstall -g yarn` (note this prevents you from using the current version of Yarn in any other projects simultaneously). -To check it installed successfully, run the command `yarn --version`. - -## Github - -Our remote repository is stored on Github, requiring you to have installed git and have a github account to access it. -Git can be installed from [this link](https://git-scm.com/downloads), and you can sign up for a github account [here](https://github.com). - -## Other Tools - -These tools are not mandatory, but may help with the developer experience when coding and running this project. - -## VS Code -No matter what, you will need a text editor to open and edit code files. The most commonly used (and recommended by us) is [VSCode](https://code.visualstudio.com/download), but you are free to use any text editor or IDE you are comfortable with. - -## Fork -Fork is a git client which often makes certain version control issues easier to manage (everything from fecthing, pushing and pulling to resolving merge conflicts). It can be downloaded [here](https://git-fork.com/) - it says it costs $59.99 but you can get the free evaluation, which lasts forever (no need to enter any credit card details). diff --git a/wiki/Onboarding/Git Conventions.md b/wiki/Onboarding/Git Conventions.md new file mode 100644 index 00000000..86e04ddc --- /dev/null +++ b/wiki/Onboarding/Git Conventions.md @@ -0,0 +1,23 @@ +# Git Conventions + +## Branches + +Create a branch for your issue and name the branch `VPS-[issue no.]/[issue name]` (if the issue name is long, a shorter version is also fine) + +- e.g. `VPS-007/add_labels_in_toolbar` + +### Merging + +When working on branches, we want to favor **rebasing** over creating merge commits. Rebasing maintains a cleaner, linear commit history, making it easier to review changes and understand the progression of the codebase. Its also helpful to rebase often when new commits are being pushed to main, so that any conflicts are smaller and easier to resolve. + +## Commit Messages + +The project follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification for commit messages and descriptions, which is a loose specification that allows for rapid development while making sure commits are thought out and purposeful. + +## Pull requests + +Once you've made all your commits, open a PR with the same name as the branch `(VPS-[issue no.]/[issue name])` and fill in the details of the template. If you want to look at it the PR template can be found [here](https://github.com/UoaWDCC/VPS/blob/). + +After the PR passes the validation pipeline (format and linting) and passes the required no. of approvals (should be 2), you can merge the PR preferably using either **squash and merge** or **rebase and merge** so that the commit history stays clean and unpolluted by merge commits. + +Make sure you delete the branch after you merge the PR, it’s not a good idea to re-use branches after their changes have been merged as it could easily lead to conflicts if you’re not careful. We also don’t want stale branches lying around. diff --git a/wiki/Onboarding/Git-Conventions.md b/wiki/Onboarding/Git-Conventions.md deleted file mode 100644 index 945c6ac5..00000000 --- a/wiki/Onboarding/Git-Conventions.md +++ /dev/null @@ -1,38 +0,0 @@ -# Git conventions - -## Branches - -Create a branch for your issue and name the branch __VPS-{ISSUE NUMBER}/{ISSUE NAME}__ (if the issue name is long, a shorter version if also fine) - - e.g. the issue below has the name __Add labels in toolbar__ but a branch named __VPS-007/toolbar-label__ -![picture 1](images/97c5a60476136bad6a548f65d9bea375b1b0934fc378a53cb54920bbb5ee0897.png) -![picture 2](images/d915d14397f3a85223e85e824f70f1545f538d87f638bd888071d2fb6756de3c.png) - -## Commit messages - -For each commit you make, follow this convention: __[VPS-{ISSUE NUMBER}] Your commit message__ - - e.g. __[VPS-007] add toolbar labels with minimal styling__ -![picture 3](images/8a5fac4d45ed78c426c0fb7895c51ddd9f7e942d19549312091180d83254f170.png) - - -## Pull requests - -Once you've made all your commits, open a PR with the name __VPS-{ISSUE NUMBER}/{ISSUE NAME}__ and fill in the details (the PR template can be found [here]( https://github.com/UoaWDCC/VPS/blob/master/.github/pull_request_template.md)) - - e.g. __VPS-007/Add labels in toolbar__ -![picture 1](images/0eeffe2bfa8023951ea66309f2227a02f700d20f61516555641970dba3d37bd6.png) - -# File Structure - -```. -├── frontend/ -│ └── src -│ └── package.json -├── backend/ -│ └── src -│ └── package.json -├── wiki/ -│ ├── react-movable.md -│ └── ... -├── README.md -├── package.json -└── ... -``` \ No newline at end of file diff --git a/wiki/Onboarding/Running-A-Local-Build.md b/wiki/Onboarding/Running-A-Local-Build.md deleted file mode 100644 index f35d44a6..00000000 --- a/wiki/Onboarding/Running-A-Local-Build.md +++ /dev/null @@ -1,35 +0,0 @@ -# Running A Local Build - -In order to make changes to this codebase, and check that the changes work - it is required to donwnload the remote repository onto your machine. This then allows you to run the code locally, actually generating the webpage on *localhost*- which allowxs you to manually test the features you create/update. - -These steps should be done after downloading the required packages to run the codebase, but alternatively you can clone the remote repo (the first step) before downloading these packages, running everything in the root directory. - -## Downloading the Remote Repo -This is done by navigating to the directory you want to place the project folder in, then running the terminal command: -`git clone https://github.com/UoaWDCC/VPS.git`. - -## Setting up your environment (.env) files -Environment (.env) files are the files keeping any sensitive information, and are not stored on the codebase. There are 2 .env files for this project, one for the frontend and one for the backend. The frontend one goes in `VPS/frontend` and the backend one goes in `VPS/backend`. - -Create a copy of the frontend and backend .env files [here](https://drive.google.com/drive/folders/19uZHA0lMrvc7QaM2dtE-DkS7veVriYZ_) (ask the TL or PM for access) and place them at the same level as the .env.example file in __frontend/__ and __backend/__. Make sure you rename these files to `.env` and that they are saved as env files, not text files. -Get the values for each key in the .env files using the links provided in them + any login details provided by the PM/APM (this may not be necessary if they are already updated). - -In the frontend .env file, add `REACT_APP_SERVER_URL = "http://localhost:[BACKEND_PORT_NUMBER]/"` and replace `[BACKEND_PORT_NUMBER]` with the value of `PORT` in the backend .env file e.g. 5001 - -⚠️ DO NOT share the .env file with anyone or upload it to GitHub or anywhere else - the file basically gives access to our databases which contain all the website assets and user information (including yours) - -## Running a local build -There are 2 methods you can use to run the local build for VPS, with both being fine to use. Method 1 is easier and quicked, while method 2 allows you to load the frontend and backend separately (if wanted). - -## Method 1 -- Run `yarn install` in the root directory (of the project) -- Run `yarn run init` in the root directory (installs dependencies for `frontend` and `backend` directories) -- Run `yarn run dev` in the root directory to start both `frontend` and `backend` environments to run the app - -## Method 2 -- Open two terminals -- Change directory by running `cd frontend` and `cd backend` in each terminal -- Run `yarn install` in each terminal -- Run `yarn start` in each terminal to run the app - - diff --git a/wiki/Onboarding/Setting Up a Local Build.md b/wiki/Onboarding/Setting Up a Local Build.md new file mode 100644 index 00000000..7d3d68b0 --- /dev/null +++ b/wiki/Onboarding/Setting Up a Local Build.md @@ -0,0 +1,104 @@ +# Setting Up a Local Build + +Unfortunately, due to VPS having being worked on since 2021, it is quite a large repository and can be complex to run on a developer’s local machine. Here, some steps are outlined to (hopefully) make it easier for new devs to get up and running. + +## Downloading Packages + +VPS runs in the MERN stack (MongoDB, Express, React, and NodeJS), and thus it is required to download a few tools to run this. + +There are also a few recommended tools to download, for ease of use, version control, and code editing. + +### Node + +Node is an open-source, cross-platform JavaScript runtime environment that allows developers to execute JavaScript code server-side. + +If using nvm (see below), it can be installed by running `nvm install 20` or it can be downloaded from the node website (note that clicking ‘download’ on this website installs the latest version of Node (currently 20 but will not be for long), so go to previous releases). To verify it installed successfully, run `node --version` in the terminal. + +#### Versions of Node/NVM + +However, Node has many different versions available. Previously, due to dependency issues, this codebase had to run on Node versions of 14.17.*. However, the Tech Lead for 2024 Woo Jin Lee heroically migrated the codebase to Node v20 and replaced the now-deprecated `create-react-app` with Vite (and also added Tailwind CSS). + +In order to easily switch between Node versions (especially recommended if you are using Node for other projects as well), it is recommended to install nvm (Node Version Manager) which allows you to have multiple versions of Node on your machine simultaneously. This can be downloaded: + +- Windows: +- Mac/Linux/WSL: or by running `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh` in the terminal + +For extra info on nvm installation, see [this link](https://www.freecodecamp.org/news/node-version-manager-nvm-install-guide/). + +To check if nvm installed correctly, run the command `nvm --version` in the terminal. + +### NPM and Yarn + +NPM, which stands for Node Package Manager, is the default package manager for Node.js. It is a command-line tool that allows developers to install, share, and manage dependencies for their Node.js projects. Note that this is different to NVM (Node Version Manager, mentioned above). + +NPM should install automatically when you install Node, and this can be checked by running the terminal command: `npm --version`. + +It is recommended to use an NPM version similar to 10.8.2 (which is the version automatically installed with Node v20) + +Yarn is a package manager for JavaScript that serves as an alternative to npm (basically it is was a “better” version of NPM which we use to manage our repo). + +This project works with Yarn 1.22.21, which can be installed by running the following terminal command: `npm install -g yarn@1.22.21`. + +If you already have yarn installed, uninstall it via `npm uninstall -g yarn` (note this prevents you from using the current version of Yarn in any other projects simultaneously). + +To check it installed successfully, run the command `yarn --version`. + +## GitHub + +Our remote repository is stored on GitHub, requiring you to have installed git and have a GitHub account to access it. + +Git can be installed from this link, and you can sign up for a GitHub account here. + +## Other Tools + +These tools are not mandatory, but may help with the developer experience when coding and running this project. + +### VS Code + +No matter what, you will need a text editor to open and edit code files. The most commonly used (and recommended by us) is VSCode, but you are free to use any text editor or IDE you are comfortable with. + +### Fork + +Fork is a git client which often makes certain version control issues easier to manage (everything from fetching, pushing and pulling to resolving merge conflicts). It can be downloaded here - it says it costs $59.99 but you can get the free evaluation, which lasts forever (no need to enter any credit card details). + +## Creating A Local Build + +In order to make changes to this code base, and check that the changes work - it is required to download the remote repository onto your machine. This then allows you to run the code locally, actually generating the webpage on *localhost*- which allows you to manually test the features you create/update. + +These steps should be done after downloading the required packages to run the code base, but alternatively you can clone the remote repo (the first step) before downloading these packages, running everything in the root directory. + +### Downloading the Remote Repo + +This is done by navigating to the directory you want to place the project folder in, then running the terminal command: + +git clone https://github.com/UoaWDCC/VPS.git. + +### Setting up your environment (.env) files + +Environment (.env) files are the files keeping any sensitive information, and are not stored on the code base. There are 2 .env files for this project, one for the frontend and one for the backend. The frontend one goes in VPS/frontend and the backend one goes in VPS/backend. + +Create a copy of the frontend and backend .env files (located on the drive) and place them at the same level as the .env.example file in frontend/ and backend/. Make sure you rename these files to .env and that they are saved as environment files, not text files. + +Get the values for each key in the .env files using the links provided in them + any login details provided by the PM/TL (this may not be necessary if they are already updated). + +In the frontend .env file, add `VITE_SERVER_URL = "http://localhost:[BACKEND_PORT_NUMBER]/"` and replace `BACKEND_PORT_NUMBER` with the value of `PORT` in the backend .env file, e.g. 5001 + +> [!WARNING] +> DO NOT share the .env file with anyone or upload it to GitHub or anywhere else - the file basically gives access to our databases which contain all the website assets and user information + +### Running a local build + +There are 2 methods you can use to run the local build for VPS, with both being fine to use. Method 1 is easier and quicker, while method 2 allows you to load the frontend and backend separately (if wanted). + +#### Method 1 + +- Run yarn install in the root directory (of the project) +- Run yarn run setup in the root directory (installs dependencies for frontend and backend directories) +- Run yarn run dev in the root directory to start both frontend and backend environments to run the app + +#### Method 2 + +- Open two terminals +- Change directory by running cd frontend and cd backend in each terminal +- Run yarn install in each terminal +- Run yarn start in each terminal to run the app diff --git a/wiki/Onboarding/Test-And-CI-CD.md b/wiki/Onboarding/Testing and CI.md similarity index 70% rename from wiki/Onboarding/Test-And-CI-CD.md rename to wiki/Onboarding/Testing and CI.md index 565a19c4..7e3150c1 100644 --- a/wiki/Onboarding/Test-And-CI-CD.md +++ b/wiki/Onboarding/Testing and CI.md @@ -1,18 +1,19 @@ -# Test and CI/CD -This is information about running unit-tests on your code, and linting your code (automatically tidying indentations and extra spaces). -This is not required for project setup but good practice to use once you start making changes. +# Testing and CI +This is information about running unit-tests on your code, and linting your code (automatically tidying indentations and extra spaces). This is not required for project setup but good practice to use once you start making changes. ## Instructions + 1. Open terminal and `cd frontend` or `cd backend` depending on which folder you are testing -2. Run `yarn run test` to run unit tests; +2. Run `yarn run test` to run unit tests 3. Run `yarn run lint` to lint; Run `yarn run lint:fix` to fix linting; 4. Run `yarn run prettier` to test for prettier; Run `yarn run prettify` to fix prettier issues; To update the Jest snapshots (e.g. when updating the UI) and pass all the frontend tests: -1. In VPS/frontend, run `yarn run test` to run unit tests; + +1. In VPS/frontend, run `yarn run test` to run unit tests 2. Press `a` to run all tests - some may fail and if they do, press `w` to show more then `u` to update failing snapshots - all tests should pass now 3. Press `w` then `q` to exit 4. Commit the updated snapshots before opening a PR -More information on Jest snapshot testing: https://jestjs.io/docs/snapshot-testing \ No newline at end of file +More information on Jest snapshot testing: diff --git a/wiki/Onboarding/What is VPS.md b/wiki/Onboarding/What is VPS.md new file mode 100644 index 00000000..ff7db9c6 --- /dev/null +++ b/wiki/Onboarding/What is VPS.md @@ -0,0 +1,9 @@ +# What is VPS? + +VPS stands for Virtual Patient Simulator, and is a teaching/learning tool aiming to provide medical students with a simulation of a real-world hospital scenario where they are required to make real-time decisions as a medical professional, affecting their patients' health and lives. + +Currently, this is being developed by the University of Auckland Web Development and Consulting Club (WDCC) for the University of Auckland’s Faculty of Medical and Health Sciences (FMHS). This project was initially started as a project for a final year software engineering course, and was passed on to WDCC in 2022. + +Even before 2021, there was a simple ‘trial’ version of the tool developed independently to get an idea of what it could look like, which you can find at this link: http://www.ingame.co.nz/files/readytopractice/. It’s not a web app like ours; it’s fully canvas based like a video game would be. + +Practically speaking - VPS can be simplified to a PowerPoint presentation crossed with a 'choose your own adventure' type of game. diff --git a/wiki/Right Click Menus.md b/wiki/Right Click Menus.md new file mode 100644 index 00000000..fde30ca7 --- /dev/null +++ b/wiki/Right Click Menus.md @@ -0,0 +1,11 @@ +# Right Click Menus + +## Portal + +The portal component that the context menus render in is ``, which is placed in the app root. The component just keeps the current menu, if any, in its state and renders it within a positioning container. + +The `portal.jsx` file exposes `render()` and `unrender()` for changing the current menu in the portal. There’s also a `handle()` function that’s intended to wrap a menu action so that it unrenders the menu component before executing the action. + +## Right Click Menu + +The `` component wraps the element you want to be right clickable, and accepts a menu parameter. This parameter should receive a react element (not a function but an actual element), which will be the menu displayed on right click. The element itself isn’t enforced so you can actually render anything you want to the screen, just make to handle unrendering for clicks within the element. diff --git a/wiki/Scene Crawler.md b/wiki/Scene Crawler.md new file mode 100644 index 00000000..0c8cc3c3 --- /dev/null +++ b/wiki/Scene Crawler.md @@ -0,0 +1,167 @@ +# Scene Crawler + +The data flow is designed to be asynchronous to provide instant responses to users while still maintaining isolation. This is done by only exposing the active scene and those immediately connected, role dependently. + +To maintain integrity across a group, it also relies on a universal scene pointer that is stored in a group object, allowing us to properly handle desynchronisation across different sessions and different users. + +## Backend Implementation + +The primary functionality exposed by the backend is captured by a sole API endpoint: `/api/navigate/group/:groupId`. This endpoint accepts 3 parameters, 1 mandatory URL parameter and 2 context dependent body props: + +- Group ID (URL parameter, mandatory) +- Current Scene ID +- Next Scene ID + +The request format is captured by this example axios request (auto generated via postman): + +``` +const axios = require('axios'); +let data = JSON.stringify({ + "currentScene": "66373e81dc2663410c735553", + "nextScene": "66373e89dc2663410c735565" +}); + +let config = { + method: 'post', + maxBodyLength: Infinity, + url: 'http://localhost:5000/api/navigate/group/6642fdb8a03cb4c2f15213ac', + headers: { + 'Content-Type': 'application/json', + 'Authorization': '••••••' + }, + data : data +}; + +axios.request(config) +.then((response) => { + console.log(JSON.stringify(response.data)); +}) +.catch((error) => { + console.log(error); +}); +``` + +There are 3 possible situations that we face when the navigate functionality is called, each one requiring differing logic. + +### Situation 1: No member of the group has visited this scenario + +In this case, the group object relating to the user will have no scene pointer set, because we don’t initialise that pointer when creating the group. + +To overcome this, we fetch the current scenario object and grab the first scene ID that’s stored within it, which we expect to be the entry point of the scenario. + +Once, we fetch this ID, we can return the information for that scene along with the connected scenes. + +### Situation 2: The user is navigating for the first time in their session + +In this case, we need to get the active scene using the group’s pointer, which we know will have already been set by way of Situation 1. Then we can return the information for that scene along with the connected scenes. + +We know this situation applies when the request doesn't provide either the current scene or next scene properties. + +### Situation 3: The user is navigating between scenes in their session + +In this case, we need to first validate the scene transition before making database changes. We can do this by way of two checks: + +- Desynchronisation: If the provided current scene property doesn’t match the group’s pointer, we know that the user’s session is out of sync. +- Non-existent edge: If the next scene property is not actually connected to the current scene, we know that this request is either malicious or something else has gone wrong with our frontend code. + +If either of these validation checks fail, the endpoint responds with a corresponding error. + +After this we return **only** the connected scenes, because we know that the frontend should already have the currently active scene from previous requests. + +### Response Format + +The following is an example of the response format for a successful request: + +``` +{ + "active": "66373e89dc2663410c735565", + "scenes": [ + { + "_id": "66373e8ddc2663410c735592", + "components": [ + { + "type": "BUTTON", + "text": "Button", + "variant": "contained", + "colour": "white", + "nextScene": "66373e90dc2663410c7355d7", + "left": 66.98858647936787, + "top": 68.95475819032761, + "height": 6, + "width": 20, + "id": "14be0ca7-6531-4c86-bbd3-48032c7ca126" + } + ], + "roles": [ + "Doctor", + "Nurse" + ] + }, + { + "_id": "66373e89dc2663410c735565", + "components": [ + { + "type": "BUTTON", + "text": "Button", + "variant": "contained", + "colour": "white", + "nextScene": "66373e8ddc2663410c735592", + "left": 66.11062335381914, + "top": 66.14664586583463, + "height": 6, + "width": 20, + "id": "9578d497-883d-4e43-9523-3cf47b0c86a6" + } + ], + "roles": [ + "Doctor" + ] + } + ] +} +``` + +### Invalid Role Handling + +In all of the situations, we perform a simple check after fetching the current active scene to see whether the object’s roles property contains the user’s role. If not, then we respond with a custom error which contains the scene’s roles property for use in the frontend. + +``` +{ + status: 403, + error: "Invalid role to access to this scene", + meta: { + roles_with_access: ["Doctor", "Nurse"] + } +} +``` + +However, because we want to give the user an instant response where possible, we need to also provide this error object in place of any connected scene which the user’s role doesn’t have access to. This way, there wont be a short time span between making the navigate request and receiving the error that we have to fill on the frontend. + +> [!NOTE] +> The object that is sent in place of any forbidden connected scenes is identical to the error object + +## Frontend Implementation + +On the frontend side, we use a simple Map object as a basic cache to store the scene data we receive from the API endpoint. We then update that cache alongside the requests we make, and return the active scene’s ID: + +``` +const navigate = async (user, groupId, currentScene, nextScene) => { + const token = await user.getIdToken(); + const config = { + method: "post", + url: `http://localhost:5000/api/navigate/group/${groupId}`, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + data: { currentScene, nextScene }, + }; + const res = await axios.request(config); + res.data.scenes.forEach((scene) => sceneCache.set(scene._id, scene)); + return res.data.active; +}; +``` + +This is called within a simple `useEffect` hook, which is called every time the active scene ID (stored in the URL) is changed. + +To make that actual URL change, we just attach a function to the button components in a scene which will redirect to the respective scene ID. diff --git a/wiki/Scene Graph.md b/wiki/Scene Graph.md new file mode 100644 index 00000000..3c6d0808 --- /dev/null +++ b/wiki/Scene Graph.md @@ -0,0 +1,5 @@ +# Scene Graph + +A scene graph for an example scenario. The nodes represent scenes, and the state consists of flags (booleans). + +![Scene Graph](./images/scene_graph.png) diff --git a/wiki/Specifications/Dashboard.md b/wiki/Specifications/Dashboard.md new file mode 100644 index 00000000..f8c86ea0 --- /dev/null +++ b/wiki/Specifications/Dashboard.md @@ -0,0 +1,16 @@ +# Dashboard + +This is the page that will give authors insight into the play statistics of their scenarios. It should be useful on a general level that averages all players, but also provide the specifics of each player / groups traversal through the scenario. This is an important feature for the clients, since they want to be able to get useful statistics on how students approach the scenarios, and also evaluate the outcomes of students so it can be translated into grading. + +## Spec + +The dashboard information is going to be tied specifically to one scenario at a time, so there wont be any inter-scenario statistics. The information is also very limited for now. + +### General + +- Scenario graph showing all pathways taken, with each edge coloured depending on the amount of times it was taken + +### Individual + +- Scenario graph showing the pathway taken, with each edge coloured depending on the amount of times it was taken +- Current state diff --git a/wiki/Specifications/Resources.md b/wiki/Specifications/Resources.md new file mode 100644 index 00000000..0edfecc8 --- /dev/null +++ b/wiki/Specifications/Resources.md @@ -0,0 +1,57 @@ +# Resources + +The feature that will allow authors to upload a range of different files that they can then show to the users at different points throughout the scenario depending on certain conditions. The dynamic nature of this feature will rely on [State Variables](./State%20Variables.md), which will allow fairly complex visibility management. + +## Spec + +There are two levels to the resources, the folder level and the file level. The author should be able to create, rename and add visibility conditions to both folders and files. In this way, authors can basically organise the resources, simplifying the interface for the players. It is basically a simple 2-level file system. + +### Files + +We want to support multiple file types that cover the base range of formats a scenario might benefit from. This list will probably evolve over time, but for release we want at least these: + +- PDF +- Text +- Markdown +- Image (PNG, JPG) + +This should cover the basics needs of richtext and images. + +#### Rendering + +We'll need at least 2 renderers to support these files, one for pdf and one for markdown. Since markdown is a richtext format, it inherently supports standard text (i.e. from .txt files) as well. + +### Folders + +Folders are containers for our resources for organisational purposes, and can act as a way to "bundle" resources for conditional visibility. For example, we might group multiple scan images together into a scan folder, and then show this folder when the scan is performed. + +### Conditionals + +The visibility conditionals operate on our [State Variables](./State%20Variables.md), and will likewise be limited in scope to make authoring as simple as possible for unfamiliar users. We'll use the same compositional approach we use to manage the state, where instead of an operation we have a comparison: + +- Equal to +- Greater than +- Less than +- Contains + +Additionally, we'll have a fourth component before the comparison to toggle negation. This would just behave as a toggle represented by 2 options: is and is not. + +Composed conditionals would look something like this: + +- *health is greater than 40* +- *intensity is not equal to super_intense* +- *inventory is contains mac_10* + +## Data Validation + +There are two approaches we can use for handling resources data: + +- Parse the conditionals on the backend with every state transition, and return only the visible resources alongside the scene data + - This ensures that the client never receives information the player isn't supposed to have + - However it means we have to do more processing on the server and data might take longer to send +- Parse the conditionals on the client with every state transition + - This means we'll have to fetch all of the resource objects on scenario load + +Although doing the parsing client side opens up the possibility of cheating, I think it's worth it given the resource tree will likely be a small object, so continuously sending it is overkill and unnecessary. + +One important consideration for this approach is to ensure that we only parse the conditionals on validated state data, otherwise we might end up parsing it twice when a desync occurs. diff --git a/wiki/Specifications/State Variables.md b/wiki/Specifications/State Variables.md new file mode 100644 index 00000000..d12b29d4 --- /dev/null +++ b/wiki/Specifications/State Variables.md @@ -0,0 +1,70 @@ +# State Variables + +The feature that will allow authors to set and manipulate global state within a scenario, so that they can track and display progress information, and then use this for creating basic conditionals. This allows for a lot of added complexity within scenarios, and hence a more immersive simulation / game. + +Some examples of what's possible with this system: + +- A global health that is displayed on each scene +- A basic inventory system +- Delayed release of resources / information throughout progression +- (Extension) Conditional scene navigation based on state requirement + +## Creation + +There will be two places where these variables can be managed within the authoring tool: + +- Globally for the whole scenario, where we can initialise the variables +- As a property on the button element, where we can define state transitions for that edge + +To define state transitions, we'll use a simple dropdown based 'action' composition, which will hopefully make it easier for authors to use. Each action consists of 3 components: + +- The variable to change / transition + - Limited to type of string, number (float), boolean and array +- The operation to do on that transition + - Depending on the variable type: set, increase, decrease, push, remove +- The value to provide to that operation + +Composed actions would look something like this: + +- *set intensity to super_intense* +- *increase health by 40* +- *push to inventory mac_10* + +We want to limit the scope of what exactly is possible with this system to make it fairly easy to use while still allowing important transitions that make sense in this context. + +## Storage + +The state variables should be available across both multiplayer and singleplayer scenarios. Within multiplayer, the state will be shared among the group. + +To make type validation easier, we store each variable as an object with its name, type and initial value in an array within each scenario object. + +However, at runtime we don't need type validation because we assume its already been validated on creation. Therefore the dynamic structure can just consist of a map, which will make operating on state more efficient. The keys will correspond to the variables, other than an additional version key. + +## Displaying + +One of the key purposes of the state variables is the display of them within the scenarios themselves. However we want to give granular control over what exactly is shown and how that looks. To achieve that, we need a system that makes it possible to encode the state variables into the scenes themselves. + +The easiest way to encode them is within the text inside the elements (speech boxes, text boxes). + +### Format + +A simple standard format for this is using dollar signs to wrap variable placeholders. + +For example: + +> John is really \$mood$ with you because you shot him \$no_shots$ times. + +At runtime, the placeholders would get replaced with the actual values in the state. This would allow the basic display of variables, which is enough to serve most use cases. + +In the future we can think about extending this to serve more complex use cases like conditionals. + +### Pre-rendering + +Since we want to retain the benefits provided by adopting an optimistic scene transition system, we'll need to do the pre-rendering on the frontend. This means we need to retain a local mirror of the state that gets instantly updated on transitions, which then gets used to replace the variable placeholders. + +The (minor) drawback of this is that if validation fails (e.g. desync) then the user will see the updated state for a short amount of time before the rollback, which could be used to cheat. I doubt anyone would go to the effort of making sense of that though. +Data Validation + +We technically already support non traversing scene transitions, because a link button can link to the same scene its within. This behaviour was fine before, but now that a button can also have state transitions, we need to explicitly handle this to avoid state mismatches. + +This means our transition validator (backend) needs to also check if the state is the same, not just the scene. One way we can do this is by sending the full local state alongside the navigation info to the navigation endpoint. However, a better way would be to generate and track a unique key that identifies a certain version of the state, which we can use instead to validate. diff --git a/wiki/Specifications/Timer.md b/wiki/Specifications/Timer.md new file mode 100644 index 00000000..2e50a803 --- /dev/null +++ b/wiki/Specifications/Timer.md @@ -0,0 +1,64 @@ +# Timer + +This is the feature that will allow authors to do two things: + +- Simulate the idea of "pressure" within a scene or group of scenes +- Provide deadlines for members of groups, or full groups, to have completed sections of the scenario by + +These are inherently linked, because they require the same underlying logic but just have different consequences. + +## Spec + +A timer should be able to be set across three levels of specificity: a single scene, a path between two scenes, and a full scenario. In this way, an author can easily define any "critical paths" that require extra pressure. + +### Single Scene + +Here we want to time the period between entering the scene and navigating away from the scene. It's important that we explicitly check for a scene transition, as we want to keep the timer running when a button is just applying a state transition. + +The use case of this is scenes that try to represent "split second decisions", where you want to challenge the quick thinking of the player. This is relevant to many contexts, not just medical simulations, and even some games lean into this functionality. + +### Multi-Scene + +Here we want to time the period between entering one scene and entering some other scene. We don't care about the specific path taken to traverse between these scenes, just that the player reaches the scene. + +The use case of this is to represent a "critical section", which would either have only one solution / path or many solutions with a "critical path". Depending on the time given, this allows for good control over the strictness or intensity of the section. + +### Global + +Here want to time the full period from entering the first scene until entering the last scene. It is purely for convenience for the authors, since the same behaviour can be implemented using a multi-scene timer. + +The use case here is a little bit different than the others, since we expect the players to play the scenarios through multiple disjointed periods of time across possibly multiple days. Therefore a tight period wouldn't make sense, and it becomes less of a matter of pressure and more of an administrative deadline. + +## Multiplayer + +To make sure the timer works correctly in multiplayer scenarios, we need to run the timer on the backend as well as the client. This way, we have a single source of truth that all of the client-side timers can base from. When any player loads in, their local timer will start based on the current value of the source timer. + +Of course, since we optimistically update the frontend, this means that when some other player does move on before the timer ends, and we don't, we'll briefly see the consequences of timeout before being re-synced. + +To avoid a situation like this, we can lock the scene(s) to a certain role, disabling multiplayer for the section. + +## Consequences + +When a timer ends, the author probably wants to perform some meaningful action. The most likely "actions" in this case are scene and state transitions. For example: + +- *decrement health by 20* +- *set out_of_time to true* + +The second one might seem useless, but it could be an important metric used by the author when reviewing the stats for players of the scenario. Especially if its used for grading an educational scenario. + +Additionally, the author could set it to perform a state transition that either goes to the "default" link (which would usually be the worst option) and continues on, or a specific scene made to handle the timeout. One standard way to handle timeouts is to reset progress back to the start of the critical section. + +### The Role Switch Action + +Outside of just state and scene transitions, there's a specific action that was requested by the clients: role switching. As I said before, one approach in handling the critical sections is to lock them to certain roles, ensuring only one player is playing through the section at once. However, if the active player runs out of time, the role assigned to the scenes should switch to another one. + +The general point of this is to avoid situations of negligence, ensuring that one person's failure doesn't stop the rest of the group from progressing. One other approach is to just perform a scene transition to the end of the critical section, which might work in some situations, but when there's important information within that section it robs the rest of the players from that knowledge. + +Since the scene graph can get fairly complex, we need to validate a few things before we can allow this type of action: + +- Every pathway should lead to either a dead end or the endpoint of the critical section +- Every scene within the section should only be assigned the same one role + +This way, cycling between the roles is actually possible within the section. We can just traverse the graph from the start point and switch the role assigned to every scene we encounter. If we didn't do this validation, we might end up overwriting roles on scenes unrelated to the critical section, due to a mistake on the author's part. + +The switching order should just cycle between the roles based on the definition order, aka the order they are stored in the scenario properties. Fine control over this order is unnecessary, and would introduce too much complexity. diff --git a/wiki/Target Scenario.md b/wiki/Target Scenario.md new file mode 100644 index 00000000..22b3d198 --- /dev/null +++ b/wiki/Target Scenario.md @@ -0,0 +1,249 @@ +# Target Scenario + +This page details the specification requirements (as detailed by Nataly in June 2024) for a single-scenario release targeted for the end-of-semester 2024. + +> [!NOTE] +> The specification below is as it was provided to us, so it probably wont be 100% clear, but you can get the general flow. + +## Scenario + +Overview: + +- Patient: John Smith +- Age: 65 +- Medical History: Type 2 diabetes, hypertension, chronic kidney disease stage 3 +- Presenting Complaint: Chest pain and shortness of breath + +--- + +Opening Scene with Speech Bubbles + +Nurse (NPC): "Mr Smith is having really bad chest pain and finding it hard to breathe. His vitals are elevated. Oxygen started at 2 liters per minute via nasal cannula. His oxygen saturation is improving slightly." + +Resources and Materials: + +- Vital Signs Monitor: Display vital signs such as Blood Pressure, Heart Rate, Respiratory Rate, and Oxygen Saturation. +- ECG Results: Provide an ECG image showing ST-segment elevation in leads II, III, and aVF. +- Lab Orders: Interface for labs (Troponin, CK-MB, CBC, BMP). +- Communication Interface: Chat function to interact with the nurse and other healthcare providers. + +--- + +Branched Scenario - Student 1 + +Nurse (NPC): "Mr. Smith's vitals are elevated, and the ECG shows ST-segment elevation. It looks like Mr. Smith might be having a heart attack. What should we do next?” + +Option 1: + +Student 1: "Let's check his vital signs again and ensure he's stable. Can we increase the oxygen flow rate?" + +- Nurse (NPC): "Increasing oxygen flow rate to 4 L per minute." +- Nurse (NPC): “The patient is deteriorating. What should we do?” (Loops back to main option screen) + +[outcome: The patient’s oxygen saturation improves slightly, but the ECG shows deterioration.] + +Option 2 (Correct): + +Student 1: "We need to start treatment immediately. Nurse, can you prepare morphine, nitroglycerin, and IV metoprolol? Also, please notify the cardiologist." + +- Nurse (NPC): "I'll get those medications ready and notify the cardiologist." + +[outcome: The patient receives the appropriate medications promptly. This path leads to next correct path screen.] + +Option 3: + +Student 1: "Let's wait for the blood test results before making any decisions." + +- Nurse (NPC): "Waiting might delay critical treatment. The ECG suggests we need to act fast." +- Nurse: “The patient is deteriorating. What should we do?” (Loops back to main option screen) + +[Outcome: ECG shows worsening of the patient's condition] + +Following the Correct Path: + +Nurse (NPC): "Medications are ready. The cardiologist has been notified and will consult shortly. What should we monitor next?" + +Option 1 (Correct): + +Student 1: "Let's monitor his kidney function closely due to his CKD and check his blood pressure frequently." + +- Nurse (NPC): "Monitoring setup for kidney function and blood pressure. Preparing for transfer to the cath lab." + +[Outcome: Goes to summary and handoff.] + +Option 2: + +Student 1: "We should monitor his glucose levels given his diabetes." + +- Nurse (NPC): "We'll add glucose monitoring, but we also need to focus on his cardiovascular status." + +[Outcome: loops back to previous options.] + +Option 3: + +Student 1: "Let's prepare for discharge as soon as his pain is managed." + +- Nurse (NPC): "Discharge at this stage might be premature given his condition." + +[Outcome: loops back to previous options.] + +Summary and Handoff: + +Student 1: "Nurse, please ensure all monitoring is in place and document the medication administration. I'll prepare to update the cardiologist on Mr. Smith’s status." + +Nurse (NPC): "All set. I'll keep monitoring his vitals and notify you of any changes." + +--- + +Student 2: Medication Appropriateness and Monitoring + +Resources and Materials: + +- Medication History and List: Overview of the patient's current medications and doses. +- Medical History Overview: Summary of the patient's medical history, including diabetes, hypertension, and chronic kidney disease. +- Consultation Interface: Chat function to interact with the nurse, cardiologist, and other healthcare providers. +- Clinical Guidelines: Access to guidelines for managing medications in patients with diabetes, cardiovascular disease, and chronic kidney disease. +- ECG Results: Display the ECG image with ST-segment elevation in leads II, III, and aVF. +- Lab Results Display + +- Nurse (NPC): "We need you to review Mr. Smith's medications. He has a complex history with diabetes, hypertension, and chronic kidney disease. The recent labs and ECG are available for your review." + +Reviewing Medication History and Lab Results: + +- Medication History and List: + - Metformin 1000 mg BID + - Lisinopril 20 mg daily + - Amlodipine 10 mg daily + - Atorvastatin 40 mg daily +- Lab Results Display: + - Troponin: Elevated + - CK-MB: Elevated + - CBC: Normal + - BMP: Elevated creatinine (2.0 mg/dL), reduced GFR (45 mL/min/1.73m²) + +Nurse (NPC): "Based on these results, what changes, if any, should we make to his medications?" + +I would want icons for all 4 medications with the option to increase/decrease them (as in Ready to Practice) so student can reduce metformin but continue the other medication at the normal dose. + +Option 1 (Correct): + +Considering his acute renal function decline, we should hold metformin to avoid the risk of lactic acidosis. We should also continue lisinopril and amlodipine for blood pressure management, and atorvastatin for cardiovascular protection, but monitor his kidney function closely. + +Other options: + +Increasing or decreasing other medications will show incorrect answers and students will need to start again until they get it right. + +--- + +Student 3: Medication Monitoring + +Following the Correct Path: + +Nurse (NPC): "Metformin held. Continuing lisinopril, amlodipine, and atorvastatin. Preparing for further instructions from the cardiologist. What else should we monitor?" + +Next Steps Interaction: + +Option 1 (Correct): + +Student 3: "We need to monitor his renal function, electrolytes, and blood glucose levels." + +- Nurse (NPC): "Monitoring setup for renal function, electrolytes, and blood glucose levels. Watching for side effects." + +[Outcome: moves onto the next screen]. + +Option 2: + +Student 3: "Let's only monitor his blood glucose levels given his diabetes." + +- Nurse (NPC): "We need to monitor more than just his glucose levels due to his complex condition." + +[Outcome: This option loops back to the main screen where students can select all the necessary options to monitor] + +Option 3: + +Student 3: "No additional monitoring is needed at this time." + +- Nurse (NPC): "Skipping monitoring could miss potential complications." + +[Outcome: This option loops back to the main screen where students can select all the necessary options to monitor] + + + +Following from correct pathway: + +- Nurse (NPC): "All set. I'll keep monitoring his vitals and notify you of any significant changes." +- Nurse (NPC): "We need to monitor Mr. Smith for potential side effects from his medications. What side effects should we be particularly concerned about?" + +Reviewing Medication List: + +- Medication List: + - Metformin (held) + - Lisinopril 20 mg daily + - Amlodipine 10 mg daily + - Atorvastatin 40 mg daily + - New medications: Aspirin, Nitroglycerin, Beta-blocker + +Option 1 (Correct): + +Student 3: "We should monitor for hypotension and bradycardia due to the beta-blocker, bleeding risk from aspirin, and angioedema from lisinopril." + +- Nurse (NPC): "Got it. I'll keep an eye on his blood pressure, heart rate, and any signs of bleeding or swelling." + +[Outcome: goes to the next screen] + +Option 2: + +Student 3: "We should monitor for hyperglycemia and fluid retention." + +- Nurse (NPC): "Those are important, but given the new medications, we need to focus on cardiovascular and bleeding risks first." + +[Outcome: loops back to main page of options]. + +Option 3: + +Student 3: "No specific monitoring is needed beyond what's already set up." + +- Nurse (NPC): "We should still watch for specific side effects from his new medications to ensure we catch any issues early." + +[Outcome: loops back to main page of options]. + + +Following the Correct Path: + +Nurse (NPC): "I'll monitor his blood pressure, heart rate, and check for any signs of bleeding or swelling. Anything else we should keep in mind?" + +Next Steps Interaction: + +Option 1 (Correct): + +Student 3: "We should also watch for muscle pain or weakness as potential side effects of atorvastatin." + +- Nurse (NPC): "Monitoring for muscle pain or weakness as well. Noted." + +[Outcome: goes to final screen]. + +Option 2: + +Student 3: "We should monitor for gastrointestinal upset due to metformin." + +- Nurse (NPC): "Metformin has been held, so gastrointestinal upset isn't a primary concern right now." + +[Outcome: loops back]. + +Option 3: + +Student 3: "We should prepare for possible allergic reactions to any medications." + +- Nurse (NPC): "That's a good point. We'll be vigilant for any signs of an allergic reaction." + +[Outcome: While important, this option alone doesn't cover all the critical side effects., so loops back to main screen] + +Summary and Handoff: + +Student 3: "Nurse, please ensure all the monitoring parameters are documented and communicated to the team. Let’s make sure we’re prepared to address any side effects promptly." + +Nurse (NPC): "All set. I'll keep an eye on his vitals and any potential side effects, and notify you immediately if there are any concerns." + +--- + +The end diff --git a/wiki/Undefined Navigation Bug.md b/wiki/Undefined Navigation Bug.md new file mode 100644 index 00000000..7c01e442 --- /dev/null +++ b/wiki/Undefined Navigation Bug.md @@ -0,0 +1,158 @@ +# Undefined Navigation Bug + +## Problem Description + +In various stages across the user play process, the application URL resolves to: + +- `/play/{scenarioId}/{mode}/undefined` + +This causes the application to return an infinite loading screen which the user can’t fix by simply refreshing. + +![undefined-bug](./images/undefined_bug.png) + +We’ve found that the bug occurs specifically in the following situations, but there could be other edge cases: + +- refreshing the invalid-role page at `/play/.../invalid-role`, or subsequently viewing it again +- trying to access a multiplayer scene which the user is not assigned to a group for +- seemingly with random frequency when trying to load a scenario normally + +## Logs + +This is the only error log I’m seeing on the /undefined page: + +``` +Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state. +PlayScenarioPageMulti@http://localhost:3000/static/js/main.chunk.js:16494:31 +Route@http://localhost:3000/static/js/vendors~main.chunk.js:257862:29 +Switch@http://localhost:3000/static/js/vendors~main.chunk.js:258033:29 +PlayScenarioResolver@http://localhost:3000/static/js/main.chunk.js:16771:63 +Route@http://localhost:3000/static/js/vendors~main.chunk.js:257862:29 +ProtectedRoute@http://localhost:3000/static/js/main.chunk.js:19609:24 +Switch@http://localhost:3000/static/js/vendors~main.chunk.js:258033:29 +Router@http://localhost:3000/static/js/vendors~main.chunk.js:257544:30 +BrowserRouter@http://localhost:3000/static/js/vendors~main.chunk.js:257201:35 +AuthenticationContextProvider@http://localhost:3000/static/js/main.chunk.js:18296:39 +ThemeProvider@http://localhost:3000/static/js/vendors~main.chunk.js:59016:18 +App +``` + +I did some looking around at other parts of the application to see if there was anything else that could be helpful, and it seems like the same log also occurs in other situations: + +- being sent to the invalid-role page for the first time +- being sent to the desync page + +However in both of these places the UI still updates successfully. + +## Possible Causes + +Looking at the log, I'm kind of confused by the message because the code for the PlayScenarioPageMulti component doesn’t have any explicit state updates being called in the main render function body. Instead they are all contained in life cycle functions and useEffect hooks. It also doesn't seem to affect the pages the other 2 times it appears. + +One other possible source of the issue is the PlayScenarioResolver component which is responsible for routing to either the singleplayer or multiplayer component. This could also relate to point 2 in the problem description. + +Taking a look at the requests sent and received from the backend, they are exactly as expected which indicates that this is a purely frontend issue and not a problem with the scene crawler itself (thankfully). + +## Actual Cause + +While thinking about the issue some more I noticed a slight flickering effect happening with the url on page refresh where it seems to route to the correct place `/play/.../invalid-role?roles=[...]` but then quickly jumps to /undefined afterwards. + +![undefined-bug](./images/undefined_bug.gif) + +Which tells me that the problem is most likely not related to the resolver but is definitely related to the way I'm handling URL navigation on the PlayScenarioPage components. + +Because the error log is also appearing on the other navigation operations, this means the issue is probably related to the log, and specifically the various history.push() operations being performed. + +Specifically this useEffect hook which handles scene navigation: + +``` +useEffect(() => { + const onSceneChange = async () => { + if (sceneId && !previous) return; + const res = await navigate(user, group._id, previous, sceneId).catch( + (e) => setError(e?.response) + ); + if (!sceneId) history.replace(`/play/${scenarioId}/multiplayer/${res}`); + }; + onSceneChange(); + }, [sceneId]); +``` + +If you look at this, you might realize that the code isn't correctly setting and handling the error state. + +In the block, the error state is being set within a catch function appended on the promise, but after that there is nothing that stops the onSceneChange function from continuing execution, so it subsequently tries to send us to another location. This is the same route as the /undefined because sceneId is undefined (which is the condition needed for it to happen in this logic). This explains the flickering. + +## Primary Solution + +The solution to this is to simply stop the execution when the error occurs, which we can do by using a try…catch block instead: + +``` + useEffect(() => { + const onSceneChange = async () => { + if (sceneId && !previous) return; + try { + const res = await navigate(user, group._id, previous, sceneId); + if (!sceneId) history.replace(`/play/${scenarioId}/multiplayer/${res}`); + } catch (e) { + setError(e?.response); + } + }; + onSceneChange(); + }, [sceneId]); +``` + +After testing this, it seems to fix the issue. + +However, the error log is still appearing on this page and the other pages i mentioned, which means that it is referencing something else that probably needs to be fixed as well. + +## Other Mitigation + +Specifically this conditional block in the main function body where the other history.push() functions are being executed: + +``` +if (error) { + if (error.status === 409) { + history.push(`/play/${scenarioId}/desync`); + } else if (error.status === 403) { + const roles = JSON.stringify(error.data.meta.roles_with_access); + history.push(`/play/${scenarioId}/invalid-role?roles=${roles}`); + } + // TODO: create a generic error page and redirect to it + return <>; + } +``` + +This is updating state with the pushes but then importantly it is also returning some blank html, because it is in the main function body, which is obviously a bad thing and might cause random issues to occur. I didn’t notice this before because I didn’t think about how the history methods were interacting with state, even though it seems obvious now. + +To fix this there are two possible solutions: + +- return the components provided by react-router-dom which point to the location +- move the error functionality into a seperate function and remove the error state entirely + +I decided to go for the second solution because I thought it would make more sense, especially because the error state essentially becomes useless because we aren’t showing the error UI in this component anyway. + +This is the code for the new function: + +``` +const setError = (error) => { + if (!error) return; + if (error.status === 409) { + history.push(`/play/${scenarioId}/desync`); + } else if (error.status === 403) { + const roles = JSON.stringify(error.meta.roles_with_access); + history.push(`/play/${scenarioId}/invalid-role?roles=${roles}`); + } else { + history.push(`/play/${scenarioId}/generic-error`) + } + }; +``` + +And now this component doesn’t have an error state at all but should still correctly handle everything. + +## Future Considerations + +Although this problem seems to be fixed at the moment, I have a suspicion that it isn’t completely fixed because the actually identified problem doesn't seem to explain one of the identified scenarios, + +> - trying to access a multiplayer scene which the user is not assigned to a group for + +and so this might be part of some other problem probably involving the crawler, because although we now handle the expected errors correctly, the backend shouldn’t be responding with an error for this situation in the first place. + +In general though, It would probably be a good idea not to ignore those “warning“ log errors in the future and pay more attention to function execution after state operations. diff --git a/wiki/image-audio-uploads.md b/wiki/Uplading Media Files.md similarity index 95% rename from wiki/image-audio-uploads.md rename to wiki/Uplading Media Files.md index 95b83664..37cd9bf7 100644 --- a/wiki/image-audio-uploads.md +++ b/wiki/Uplading Media Files.md @@ -1,4 +1,4 @@ -# Uploading Image and Audio Files +# Uploading Media Files This project uses [Firebase Storage](https://firebase.google.com/docs/storage) to allow users are given the ability to upload their own image and audio files. These files are stored in Firebase's Cloud Storage. @@ -18,4 +18,4 @@ The Firebase download urls are retrieved for each file and stored in the databas When images and audios are deleted from a scene but have already been uploaded to Firebase, a check is done on the file's `count` value. If only one scene uses the file (metadata count of 1), the file is deleted from Firebase. Otherwise, the file remains and the metadata is decremented by 1. ## Scene Duplication -When a scene is duplicated all its firebase files have their `count` values incremented by one. \ No newline at end of file +When a scene is duplicated all its firebase files have their `count` values incremented by one. diff --git a/wiki/VPS Home.md b/wiki/VPS Home.md new file mode 100644 index 00000000..514f1ea8 --- /dev/null +++ b/wiki/VPS Home.md @@ -0,0 +1,23 @@ +# VPS Home + +Welcome to the documentation home for the Virtual Patient Simulator (VPS) project! + +> [!NOTE] +> Confluence (along with the whole Atlassian suite) is an unintuitive and bloated product, so the wiki will live here on github instead (once again). Although the intention of using it was to expose everyone to 'industry standards', it seems that many in the industry don't like it either, so... - Hartej + +## Useful Links + +- [Github Repository](https://github.com/UoaWDCC/VPS) +- [Live Staging Deployment](https://wdcc-vps-staging.fly.dev/) +- [Production Deployment](https://wdcc-vps.fly.dev/) + +## Wiki Structure + +This wiki is split into 2 main sections: + +- Onboarding +- Documentation + +Onboarding is used for new developers to get up and running, and includes info about what VPS is, how to set up a local build and start coding, some good practices around how to create git branches and pull requests, and some info around unit tests and CI/CD. + +Documentation makes up the majority of this wiki, and details the code base structure, different features created (especially features created from June 2024 onward), and details on some major bugs and their solutions. diff --git a/wiki/continuous-deployment.md b/wiki/continuous-deployment.md deleted file mode 100644 index 4dc0c931..00000000 --- a/wiki/continuous-deployment.md +++ /dev/null @@ -1,22 +0,0 @@ -# Deploying backend - -We have considered and tried the following two ways approaching Continuous Deployment: - -- Connect heroku to Github Repo and let Heroku handle CD. But it says only repo admins have access to link it (even if heroku is granted access to the organization) -- Use Github Actions to Deploy when PR is merged. That I need to configure Heroku-API-key on Github Secrets - -Since the repository is not owned by any of the team members, and is owned by the lecturer of SOFTENG761, unfortunately, we cannot have CD set up for thie repository. All deployments needs to be done from terminal. Please contact @lucas2005gao if there are updates in the backend to be deployed. - -## Deploying manually to Heroku - -1. Setup heroku `git remote add heroku https://git.heroku.com/virtual-patient-system.git` - -If this is fast-forwarding the heroku remote, then simply do - -1. Push to Heroku `git subtree push --prefix backend heroku master` - -OR, If this requires a forced push then do - -1. `git subtree split --prefix backend master` -2. copy the generated commit hash from the above command, and paste it in the `$(hashcode)` below -3. `git push heroku $(hashcode):master --force` diff --git a/wiki/frontend-authoring-tool.md b/wiki/frontend-authoring-tool.md deleted file mode 100644 index 8bb9104a..00000000 --- a/wiki/frontend-authoring-tool.md +++ /dev/null @@ -1,29 +0,0 @@ -# Authoring Tool - -## Toolbar - -When new components are made the tool bar will need to be updated. -The steps to adding or changing the tool component inside Toolbar is as the following: - -1. Update `containers/pages/AuthoringTool/ToolBar/ToolBarData.js` to have the correct icons and titles - * All components must be defined with a **title** and **icon** - * Components without a dropdown menu must have a **onClick** property. - * Tool components with dropdown menu should have **title**, **icon** and a list of objects with the **dropdown** menu items - ``` - dropdown: [ - { - component: \ - }, - { - component: \ - } - ] - ``` -2. Update `containers/pages/AuthoringTool/ToolBar/ToolBarActions.js` with the **onClick** function required by the component, if applicable. -3. Create a new folder inside `containers/pages/AuthoringTool/ToolBar` to create dropdown menu items, if applicable. -4. If this tool component needs a dropdown menu, then you will have to create the following files corresponding to each menuItem in the dropdown - * a DropdownMenuItem component which will be used in `ToolBarData.js` - * For submenu items that open a modal on selection: - * a component that contains the Modal - * a hook that contains the state to manage the opening and closing of the Modal -4. Note that when adding a new tool component or dropdown item you should not be modifying the **`containers/AuthoringTool/ToolBar/ToolBar.js`** component. (or at least that's how @lucas2005gao initially designed it to be like) diff --git a/wiki/images/audioComponent.png b/wiki/images/audioComponent.png deleted file mode 100644 index 5dc281ee..00000000 Binary files a/wiki/images/audioComponent.png and /dev/null differ diff --git a/wiki/images/buttonComponent.png b/wiki/images/buttonComponent.png deleted file mode 100644 index d051bf4a..00000000 Binary files a/wiki/images/buttonComponent.png and /dev/null differ diff --git a/wiki/images/canvasPage.png b/wiki/images/canvasPage.png deleted file mode 100644 index fb872c37..00000000 Binary files a/wiki/images/canvasPage.png and /dev/null differ diff --git a/wiki/images/componentSelect.png b/wiki/images/componentSelect.png deleted file mode 100644 index 23e7239e..00000000 Binary files a/wiki/images/componentSelect.png and /dev/null differ diff --git a/wiki/images/imageFileExplorer.png b/wiki/images/imageFileExplorer.png deleted file mode 100644 index 33547a34..00000000 Binary files a/wiki/images/imageFileExplorer.png and /dev/null differ diff --git a/wiki/images/imageModal.png b/wiki/images/imageModal.png deleted file mode 100644 index 05e5011b..00000000 Binary files a/wiki/images/imageModal.png and /dev/null differ diff --git a/wiki/images/imageToolBar.png b/wiki/images/imageToolBar.png deleted file mode 100644 index d0b22dc7..00000000 Binary files a/wiki/images/imageToolBar.png and /dev/null differ diff --git a/wiki/images/loginPage.png b/wiki/images/loginPage.png deleted file mode 100644 index f41b39f9..00000000 Binary files a/wiki/images/loginPage.png and /dev/null differ diff --git a/wiki/images/saveModal.png b/wiki/images/saveModal.png deleted file mode 100644 index b7d2d9b2..00000000 Binary files a/wiki/images/saveModal.png and /dev/null differ diff --git a/wiki/images/scenarioPage.png b/wiki/images/scenarioPage.png deleted file mode 100644 index 7bc411dd..00000000 Binary files a/wiki/images/scenarioPage.png and /dev/null differ diff --git a/wiki/images/scenePage.png b/wiki/images/scenePage.png deleted file mode 100644 index 5136cf92..00000000 Binary files a/wiki/images/scenePage.png and /dev/null differ diff --git a/wiki/images/scene_graph.png b/wiki/images/scene_graph.png new file mode 100644 index 00000000..e651f691 Binary files /dev/null and b/wiki/images/scene_graph.png differ diff --git a/wiki/images/shareModal.png b/wiki/images/shareModal.png deleted file mode 100644 index 65b5a35f..00000000 Binary files a/wiki/images/shareModal.png and /dev/null differ diff --git a/wiki/images/textComponent.png b/wiki/images/textComponent.png deleted file mode 100644 index 2aafe194..00000000 Binary files a/wiki/images/textComponent.png and /dev/null differ diff --git a/wiki/images/undefined_bug.gif b/wiki/images/undefined_bug.gif new file mode 100644 index 00000000..4f870b45 Binary files /dev/null and b/wiki/images/undefined_bug.gif differ diff --git a/wiki/images/undefined_bug.png b/wiki/images/undefined_bug.png new file mode 100644 index 00000000..445c6161 Binary files /dev/null and b/wiki/images/undefined_bug.png differ diff --git a/wiki/project-dependencies.md b/wiki/project-dependencies.md deleted file mode 100644 index ea58b912..00000000 --- a/wiki/project-dependencies.md +++ /dev/null @@ -1,8 +0,0 @@ -# Project dependencies - -* [Firebase Authentication](https://firebase.google.com/docs/auth) - login functionality. -* [Firebase Storage](https://firebase.google.com/docs/storage) - image and audio storage -* [Google Drive](https://www.google.com/drive/) - image bank storage -* [MongoDB Atlas](https://www.mongodb.com/cloud/atlas) - database -* [Heroku](https://www.heroku.com/) - backend deployment -* [Netlify](https://www.netlify.com/) - frontend and wiki deployment diff --git a/wiki/react-moveable.md b/wiki/react-moveable.md deleted file mode 100644 index 3b655c1d..00000000 --- a/wiki/react-moveable.md +++ /dev/null @@ -1,35 +0,0 @@ -## React-Moveable - -This project uses [react-moveable](https://www.npmjs.com/package/react-movable) to create a draggable canvas. -The moveable component is rendered in canvas.js - -### On Draggable - -``` -x: ${(transfromMatrix[4] * 100) / canvas.width} -y: ${(transfromMatrix[5] * 100) / canvas.height} -``` - -transformMatrix is a length of 6 array. The last two elements (4,5) correspond to the relative positions on the page. This is then divided by the canvas width/height to get a percentage value of the canvas. - -### On Scalable - -``` -x-scale: ${transfromMatrix[0]} -y-scale: ${transfromMatrix[3]} -``` - -transformMatrix is a length of 6 array. The 1st and 4th elements (0,3) correspond to the x-scale and y-scale on the page. This will be pure CSS values that can be used to render on a larger or smaller canvas. - -Note: changing the scale affects the point of origin used in draggable. More testing might need to be done to see how this will affect rendering. - -### On Resize - -``` -const relWidth = `${(absWidth / canvas.width) * 100}%`; -const relHeight = `${(absHeight / canvas.height) * 100}%`; -``` - -Width and height is converted to a relative percentage of the canvas, by dividing by canvas width and height. - -Note: For resizing to work correctly, the component needs to have `position: "absolute"` in its css diff --git a/wiki/setup-and-run-app.md b/wiki/setup-and-run-app.md deleted file mode 100644 index 92d33471..00000000 --- a/wiki/setup-and-run-app.md +++ /dev/null @@ -1,35 +0,0 @@ -# Setup and Run App - -### Setup - -- Run `yarn install` in the root directory -- Run `yarn run init` in the root directory (installs dependencies for `frontend` and `backend` directories) - -Alternative way to set up - -- Open two terminals -- Change directory by running `cd frontend` and `cd backend` in each terminal -- Run `yarn install` in the each terminal - -### Run - -- Run `yarn run dev` in the root directory to start both `frontend` and `backend` environments - -Alternative way to run app - -- Open two terminals -- Change directory by running `cd frontend` and `cd backend` in each terminal -- Run `yarn start` in the each terminal - -### Test and Continuous Integration - -- Open terminal and `cd frontend` or `cd backend` depending on which folder you are testing -- Run `yarn run test` to run unit tests; -- Run `yarn run lint` to lint; Run `yarn run lint:fix` to fix linting; -- Run `yarn run prettier` to test for prettier; Run `yarn run prettify` to fix prettier issues; - -### Tool versions - -- Ideally you should use the following versions to make this repository work. -- node v20\* -- yarn 1.22.\* diff --git a/wiki/user-manual.md b/wiki/user-manual.md deleted file mode 100644 index 5cedf35a..00000000 --- a/wiki/user-manual.md +++ /dev/null @@ -1,170 +0,0 @@ -# User Manual - - -## Login - -On entering the default [website url](https://vps-uoa.netlify.app), users will be prompted to login using a google account. - -![Login Page](images/loginPage.png) -
- -## Scenario Dashboard Page - -Upon successfully logging in, users will be greeted by their Scenario Dashboard Page. This page contains all scenarios created by the logged in user. - -![Scenario Page](images/scenarioPage.png) - -(1) This button allows the user to create new scenario and will redirect them to the Scene Dashboard Page. - -(2) This area contains all scenarios created by the user. Clicking on a scenario card will select the scenario. - -(3) Clicking on the scenario name allows the user to edit and change the name of their scenario. The name change is automatically saved when the text is unfocused. - -(4) This button allows the user to play the currently selected scenario. Clicking will redirect them to the Play Scenario Page. - -(5) This button allows the user to edit their selected scenario and will redirect them to the Scene Dashboard Page. - -(6) This button deletes the selected scenario. - -(7) This button logs the user out and they are redirected to the login screen. - -
- -## Scene Dashboard Page - -The page contains all the scenes created within a scenario. - -![Scene Page](images/scenePage.png) - -(1) This button takes the user back to the Scenario Dashboard Page. - -(2) This dashed card allows the user to create new scenes. Clicking the card will take the user to the Authoring Tool page. - -(3) This area contains all scenes within this scenario. Clicking on a scene card will select the scene. - -(4) Clicking on the scene name allows the user to edit and change the name of the scene. The name change is automatically saved when the text is unfocused. - -(5) This button deletes the selected scene. - -(6) This button allows the user to edit a selected scene and redirects them to the Authoring Tool Page. - -(7) This button allows the user to duplicate a selected scene with all its components and their properties. - -(8) This button allows the user to play the current scenario. Clicking will redirect them to the Play Scenario Page. - -(9) This button allows the user to share the current scenario. Clicking will present a share modal. - -![Share Modal](images/shareModal.png) -*Share Modal* - -
- -## Authoring Tool Page - -This page is used for editing the components within a scenario. - -![Canvas Page](images/canvasPage.png) - -(1) This button takes the user back to the Scene Dashboard Page. - -(2) This is the tool bar and contains all components that can be added to the canvas. - -(3) This button is for adding images components. - -(4) This button is for adding text components. - -(5) This button is for adding button components. - -(6) This button is for adding audio components. - -(7) This is the canvas and represents the scene and all its components. Users can select any component on the canvas simply by clicking on it. For example, the start button. - -(8) This button saves the users changes in the scene. All changes on this scene will not persist unless this button is pressed. - -(9) The name of the scene can be changed here. - -(10) This sections shows all of the properties assocaited with the selected component. - -
- -Leaving the page without saving will prompt the user to save their changes or discard them. - -![Save Modal](images/saveModal.png) -*Save Modal* -
- -### Image component - -Clicking on the image icon tool bar presents two options. - -![Image Tool Bar](images/imageToolBar.png) - -(1) The first option presents the image Modal where a user can select an image from an existing image bank of medical images. - -(2) The second option presents a file explorer, where users can upload their own images to the canvas. - -![Image Modal](images/imageModal.png) -*Image Modal* - -
- -![Image File Explorer](images/imageFileExplorer.png) -*Image File Explorer* - -
- -### Text Component -This component is used to display text on the canvas. - -![Text Component](images/textComponent.png) - -(1) The specific text to be displayed can be entered here. - -(2) The font size can be adjusted. - -(3) The text Alignment can be adjusted. - -(4) The color can be adjusted. - -(5) The border and background can be toggled on and off. - -
- -### Button Component -This component is used to transition to different scenes. - -![Button Component](images/buttonComponent.png) - -(1) The text on the button can be edited. - -(2) The style of the button can be changed. - -(3) The color of the button can be changed. - -(4) The scene to transition to can be selected and changed. - -
- -### Audio Component -This component is used to play sound on a scenes. Once the scene loads the audio component will start playing. - -![Audio Component](images/audioComponent.png) - -(1) This button can be used to test the audio being played. - -(2) This toggle can be used to loop the audio once playing. - -
- -## Resizing and Dragging Components -All components can be dragged and resized. Clicking on an component will select that component. Selected components are outlined with a resizable box. Dragging on dots of the resizeable box will resize the component according to your new cursor position. Dragging the component will cause the component to change positions acccording to your new cursor position. - -![Component Select](images/componentSelect.png) - -## Deleting Components -Selected components can be deleted by pressing the "Backspace" or "Delete" key. - - - - -