A Kotlin application providing APIs to support managing Education And Work Plans for prisoners.
This application is in development by the Farsight Consulting team Personal Learning Plans (PLP)
. They can be contacted on MOJ Slack channel #plp-dev
.
The application has a health endpoint found at /health
which indicates if the app is running and is healthy.
The application has a ping endpoint found at /ping
which indicates that the app is responding to requests.
To develop and build the application locally you will need JDK 21 installed and configured.
Ktlint is used to format the source code and a task runs in the Circle build to check the formatting.
You should run the following commands to make sure that the source code is formatted locally before it breaks the Circle build.
./gradlew ktlintApplyToIdea
Or to apply to all Intellij projects:
./gradlew ktlintApplyToIdeaGlobally
./gradlew addKtlintFormatGitPreCommitHook
The easiest (and slowest) way to run the app is to use docker compose to create the service and all dependencies.
docker-compose pull
docker-compose up
- See
http://localhost:8081/health
to check the app is running. - See
http://localhost:8081/swagger-ui/index.html?configUrl=/v3/api-docs
to explore the OpenAPI spec document.
The following environment variables are required in order for the app to start:
Name | Description |
---|---|
SERVER_PORT | The port that the application will run on |
HMPPS_AUTH_URL | The URL for OAuth 2.0 authorisation server |
Name | Description |
---|---|
DB_SERVER | The host of the DB server |
DB_NAME | The name of the database instance |
DB_USER | The application's DB username |
DB_PASS | The DB user's password |
Name | Description |
---|---|
APPINSIGHTS_INSTRUMENTATIONKEY | The instrumentation key for App Insights |
APPLICATIONINSIGHTS_CONNECTION_STRING | The connection string for App Insights |
APPLICATIONINSIGHTS_CONFIGURATION_FILE | A configuration file for App Insights |
Name | Description |
---|---|
PRISON_API_URL | The URL of the Prison API |
PRISON_API_CLIENT_ID | hmpps-auth oauth2 client-id for connecting to the Prison API |
PRISON_API_CLIENT_SECRET | hmpps-auth oauth2 client-secret for connecting to the Prison API |
PRISONER_SEARCH_API_URL | The URL of the Prisoner Search API |
PRISONER_SEARCH_API_CLIENT_ID | hmpps-auth oauth2 client-id for connecting to the Prisoner Search API |
PRISONER_SEARCH_API_CLIENT_SECRET | hmpps-auth oauth2 client-secret for connecting to the Prisoner Search API |
MANAGE_USERS_API_URL | The URL of the Manage Users API |
MANAGE_USERS_API_CLIENT_ID | hmpps-auth oauth2 client-id for connecting to the Manage Users API |
MANAGE_USERS_API_CLIENT_SECRET | hmpps-auth oauth2 client-secret for connecting to the Manage Users API |
To run the test suite (unit and integration):
./gradle clean check
The integration tests spin up the following docker containers, therefore a docker runtime is required on local development environments in order to run the tests.
- postgres - A real postgres instance is used rather than h2 in order to properly test various constraints etc. (h2 is more forgiving that postgres in this respect)
The API is instrumented with the opentelemetry and Application Insights java agent. Useful data can be found and reported
on via the Azure Application Insights console, all under the cloud_RoleName
property of hmpps-education-and-work-plan-api
.
The Application Insights console and the Kusto Query Language will be your friend here, but some example queries are described below.
All example query links below are for nomisapi-t3
(dev) - you will need to change the scope in order to target
preprod or prod.
Each HTTP request is logged in Application Insights. These can be queried as follows:
The where name
clause is significant, otherwise the query will also return hits to the /health
and /info
endpoints.
requests
| where cloud_RoleName == 'hmpps-education-and-work-plan-api'
| where name has '/action-plans'
| order by timestamp
A number of custom events are triggered at various points in the API. The custom event names can be discovered with the following query
customEvents
| where cloud_RoleName == 'hmpps-education-and-work-plan-api'
| distinct name
A query can include field projections, including projecting named fields from within the custom dimensions data.
For example
customEvents
| where cloud_RoleName == 'hmpps-education-and-work-plan-api'
| where name == 'goal-create'
| project
timestamp,
status = customDimensions.status,
reference = customDimensions.reference,
stepCount = customDimensions.stepCount
| order by timestamp
Queries can contain aggregate functions, and the results rendered in charts. For example, this query produces a column chart of the number of goals created per day that were created with more than 2 steps:
customEvents
| where cloud_RoleName == 'hmpps-education-and-work-plan-api'
| where name == 'goal-create'
| where customDimensions.stepCount > 2
| summarize count() by bin(timestamp, 1d)
| render columnchart
Several objects within the Application Insights "tables" contain an operation_Id
field which is effectively a correlation ID
between objects. For example, this query
produces a report of usernames who have created goals with more than 2 steps. It does this by querying the customEvents
table for records whose name is goal-create
and whose custom dimension field stepCount
is greater than 2. It then joins
on the requests
table on the operation_Id
field, projecting out the custom dimension field username
back out to the
outer query.
customEvents
| where cloud_RoleName == 'hmpps-education-and-work-plan-api'
| where
name == 'goal-create'
and customDimensions.stepCount > 2
| join kind=innerunique
(requests
| where cloud_RoleName == 'hmpps-education-and-work-plan-api'
| project
operation_Id,
username = customDimensions.username
)
on operation_Id
| project
timestamp,
username
| order by timestamp desc
There could be problems with the JVM used in the API, such as running out of threads or memory.
Various JVM related metrics are pushed to Application Insights by Spring/Micrometer. Run the following query to see what metrics are available:
customMetrics
| where cloud_RoleName == 'hmpps-education-and-work-plan-api'
| where name startswith 'jvm'
| order by timestamp desc
For example, if the memory usage is consistently high you may need to increase the memory allocated in the helm_dploy/hmpps-education-and-work-plan-api/values.yaml
file env var JAVA_OPTS
.
This API consumes, and is therefore dependent on, data from the following APIs:
hmpps-auth
- Standard HMPPS Digital configuration; used for Spring Security.application-insights
- Standard HMPPS Digital configuration; used for telemetry and event tracing.prison-api
- The Prison API; used for looking up a Prisoner's prison history.manage-users-api
- The Manage Users API; used for looking up the full name (display name) of DPS users.prisoner-search-api
- The Prisoner Search API; used for looking up Prisoner data in order to get their release date.
The following are the known consumers of this API. Any changes to this API - especially breaking or potentially breaking changes should consider the use case of these consumers.
As is standard in HMPPS projects the term "service UI" specifically means the node/express service codebase; and not the UI running in the browser. UI's running in the browser do not make xhr/ajax style requests directly to the APIs.
hmpps-eduction-and-work-plan-ui
- The Education and Work Plan service UI (aka PLP)
The REST API endpoints exposed by this API are consumed by the service UI. Roles required are dependent on the API endpoint being consumed - see the individual controller methods and/or swagger spec for specific details.hmpps-prisoner-profile
- The DPS Prisoner Profile UI
The DPS Prisoner Profile UI consumes theGET /action-plans/{prisonNumber}/goals
endpoint in order to retrieve the prisoner's active goals from their action plan. The role required isROLE_EDUCATION_AND_WORK_PLAN__GOALS__RO
Features can be toggled by setting the relevant environment variable.
Name | Default Value | Type | Description |
---|---|---|---|
SOME_TOGGLE_ENABLED | false | Boolean | Example feature toggle, for demonstration purposes. |