Galv Development Guide
Running a development instance
The Galv frontend requires an instance of the backend to be running.
To assist in development, a docker-compose
file is provided to run
an instance of the frontend and backend together.
The first time you run the development instance, you will need to build the frontend container.
You will also need to rebuild it anytime you change dependencies in the package.json
file,
or change any file outside the src
directory.
To build the frontend container, run the following command:
docker-compose build frontend
To bring up the development instance, run the following command:
docker-compose up -d frontend
This will start the frontend
and backend
servers, and the postgres
database.
It will also start a mailhog instance
to handle the activation emails the backend generates.
You can now log in to the frontend at https://localhost:8002.
Development container details
Several containers are included in the docker-compose.yml
file for testing and development purposes:
- Frontend Vite server:
frontend
runs the frontend withVite
Note: the server has hot-reloading disabled by default because it was crashing Docker
- Backend containers:
backend
runs the latest release of the backend withDjango
andGunicorn
postgres
runs the database the backend usesmailhog
runs a mail server so the backend can send activation emails
- Testing containers:
frontend_test
runs the frontend unit tests withJest
frontend_test_e2e
runs the frontend end-to-end tests withCypress
frontend_build
builds the frontend (useful for testing the production build)
- Documentation container:
docs
builds this documentation and runs a server with auto-reloading
Container ports
The
frontend
is available atlocalhost:8002
by default (the backend’s development port is 8001)The
backend
instance is available atlocalhost:8082
Mailhog
’s web interface is available atlocalhost:8025
The
documentation
is available atlocalhost:8003
Customizing the development deployment
The services and their settings can be changed in the docker-compose.yml
file.
In particular, if your frontend is targeting a different backend,
you will need to change the VITE_GALV_API_BASE_URL
environment variable in the frontend container.
Note that you will also need to ensure your target backend has the correct
FRONTEND_VIRTUAL_HOST
environment variable set.
Testing
Tests are run automatically with GitHub Actions (.github/workflows/test_*.yml
),
and can also be run manually during development.
The tests are run in Docker containers to ensure a consistent environment,
both in the CI and locally.
Unit tests
Unit tests are kept to a minimum, and should target specific functionality.
Unit tests are found in the src/test
directory.
The test runner is Jest
, and it uses Vite
to prepare the test code.
The unit test container can be run with the following command:
docker-compose run --rm --build frontend_test
When testing components, the contexts they use should not be mocked, while the components they use should be mocked.
Any new custom component should have a corresponding mock in the src/Components/__mocks__
directory.
This component can use the pattern from other mocks of extending DummyComponent
.
End-to-end tests
End-to-end (e2e) tests are run with Cypress
.
These tests are used to ensure that flows through the application work as expected.
The e2e test container can be run with the following command:
docker-compose run --rm --build frontend_test_e2e
Versioning
The Galv project uses Semantic Versioning.
Syncing with the backend version
When a new release of the Galv backend is made, it will automatically produce
a new Docker image and push it to the GitHub container registry.
It will also produce a new typescript-axios
API client for the frontend
and release it to NPM.
You should ensure that the version of the frontend API client is up to date
with the backend you are targeting by editing the
@galv/galv
dependency in the package.json
file.
The version of the backend API client will be the same as the version of the backend. So if the backend is at version 1.2.3, the API client will be at version 1.2.3.
Remember to run docker-compose build frontend
to rebuild the frontend container
after changing the backend version.
Releasing a new Frontend version
This documentation provides documentation for each release of the frontend. When a new release is made, the following steps should be taken:
Update the version number in the
package.json
fileUpdate the version number in
docs/source/conf.py
Add the new version to
docs/tags.json
with the version number prefixed with a ‘v’
The new version should be tagged in the git repository with the version number prefixed with a ‘v’.
For example, if the new version is 1.2.3, the tag should be v1.2.3
.
When the tag is pushed to the repository, the GitHub Actions workflow will automatically
issue a new release of the Frontend, build a container and push it to the GitHub container registry,
and publish updated documentation to GitHub Pages.
Understanding the application
This section provides a brief overview of the technology used to implement the different parts of the project, and a guide to some of the Custom context hooks and Custom components the project uses.
Technology
Typescript
The Galv frontend is written in TypeScript, a statically-typed superset of JavaScript. We use TypeScript to catch errors early and provide a better development experience.
When contributing to the frontend, please ensure that your code is written in TypeScript,
and that you have added type annotations where necessary.
This means that you should not use the any
type, and should use the unknown
type
where you are not sure of the type of a value.
React
The frontend uses React, to provide a fast and responsive user interface. React orders the UI into components, which are then composed together to form the application. Components keep logical parts of the UI separate, allow consistent styling and behaviour, support accessibility (a11y), make the flow of data through the application more transparent, and make the application easier to maintain.
Material-UI
Material-UI offers a suite of common components that are styled according to the Material Design guidelines. It provides a consistent look and feel to the application, and reduces the amount of custom styling required.
ReactQuery
It uses ReactQuery to cache calls made to the REST API and reduce loading times. It also provides a way to manage the state of the application in a more predictable way.
Codebase
The codebase is designed to be as modular as possible.
This means that the number of components is kept to a minimum,
and their behaviour is manipulated by values in constants.ts
.
Custom context hooks
The frontend has a number of custom hooks that are used to manage state and side effects.
ApiResourceContext
Provides a consistent interface for resources whether or not they have a ‘family’ parent resource
AttachmentUploadContext
Provides a rerender-resistant file object while choosing a file to upload
CurrentUserContext
Provides a consistent way to access the current user, login, and open the login dialog
FetchResourceContext
Wraps the
useQuery
anduseInfiniteQuery
hooks fromreact-query
to provide a consistent way to fetch resources from the backendCovers both
list
anddetail
views
SnackbarContext
Provides a consistent way to queue and show snackbar messages
SelectionManagementContext
Provides a way to manage selection of items across page navigation
FilterContext
Provides a unified way to filter resources
UndoRedoContext
Provides a way to manage undo and redo edits
Custom properties and Type-Value notation
Custom properties are stored with their types explicitly recorded. This is a verbose way to store information, but it ensures that the information can be understood.
The format is called Type-Value notation, and it looks like this:
type TypeValueNotation = {
_type: "string" | "number" | "boolean" | "null" | "attachment" | "object" | "array" |
TypeChangerLookupKey | TypeChangerAutocompleteKey
_value: string | number | boolean | null | TypeValueNotation[] | TypeValueNotationWrapper
}
type TypeValueNotationWrapper = Record<string, TypeValueNotation>
This format means that data represented in Type-Value notation can be serialized to JSON, which is used in the backend.
The frontend has to deal with two different data formats: the Type-Value notation, and standard objects. To handle this, all data passed to the display components is converted to Type-Value notation, and non-custom properties are converted back to standard objects when communicating with the backend.
Custom components
The frontend has several custom components.
The behaviour of these components is manipulated by values in constants.ts
,
allowing for a reduction in repetition of code across many otherwise similar components.
Many of these components will take a lookupKey
property to determine which kind
of resource they are displaying.
They may also have a resourceId
property to determine which resource they are displaying.
- Resource display
QueryWrapper
A component that will wrap a query and display a loading spinner, error message, or the result of the query
ResourceList
A generic list component that can be used to display a list of resources in collapsed
ResourceCard
components
ResourceCard
A generic card component that can be used to display a resource in either collapsed (three lines) or expanded (full) form
ResourceChip
A generic chip component that can be used to display a resource as a single line of text
ResourceCreator
A generic creator component that can be used to create a resource
Mapping
* A mapping component that can be used to handle adjusting raw File contents to a supported format
- Utilities
LoadingChip
A generic loading chip component that can be used to display a loading state
CountBadge
A generic badge component that can be used to display a count over an icon
LookupKeyIcon
A generic icon component that can be used to display a resource’s icon
CardActionBar
A generic action bar component that can be used to display actions for a resource
NumberInput
A generic number input component that can be used to input a number
ResourceStatuses
* A generic status component that can be used to display a resource’s status * Used to allow quick access to actions that can be taken on a resource
- Data display
- The family of components in the
src/Components/prettify
directory These components are used to display data in a more human-readable form
They are interrelated and pass change events recursively up their render tree
- The family of components in the
TypeChanger
A component that can be used to change the type of a data field
- Filtering
- The components in the
src/Components/filtering
directory These components are used to filter resources
Filters are instances of a family of available filters that can be applied to a resource
A Filter is a combination of a generic filtering function and a value to filter against
- The components in the
- Error handling
ErrorBoundary
A component that can be used to catch errors in a component tree and display an error message
Components in the
src/Components/errors
directory display error messages in the appropriate format
Documentation
Documentation is written in Sphinx’ reStructured Text and produced by Sphinx.
Documentation is located in the /docs/source
directory.
Contributor guide
We very much welcome contributions. Please feel free to participate in discussion around the issues listed on GitHub, submit new bugs or feature requests, or help contribute to the codebase.
If you are contributing to the codebase, we request that your pull requests identify and solve a specific problem, and include unit tests for logic that has been added or modified, along with updated documentation if relevant.
GitHub Actions
The project uses GitHub Actions to run tests, build the frontend and documentation, and issue new releases.
When you push to a branch with a version number different from the current one, the GitHub Actions workflow will check whether the version number is valid and whether the code can be built and released.
When a tag with the format v[0-9]+\.[0-9]+\.[0-9]+
(e.g. v1.2.3
) is pushed to the repository,
the GitHub Actions workflow will build the frontend and documentation,
and issue a new release of the frontend.
The testing workflows are always run when code is pushed to the repository.