Providers' UI - A restricted administration client for Kultus event providers (formerly Palvelutarjotin).
Table of Contents
- About Kultus
- Service architecture
- Development
- Headless CMS
- Releases, changelogs and deployments
What is it? A service for teachers to find and book culture and leisure sectors activities for their student groups.
Why is it? To make booking culture and leisure activities easy to all participants:
- help teachers find & enrol their student groups to activities
- help providers (culture and leisure department and partly metropolitan-area ecosystem) to show activities for teachers & their student groups
- gather data (equal possibilities and use to all groups, use of Kuva's work, resource needs etc) for service development
Who use the service?
- Teacher
- A school or kindergarten teacher who seeks educational activities for student groups.
- Provider
- A third party or a Culture and Leisure Division (i.e. KUVA) services user who wants to provide activities for teachers & their student groups
- Admin
- A user who manages the system's rights, pages etc.
The Kultus service consists of:
- Kultus API: The API backend service - The primary source of data. Integrates to LinkedEvents API and extends it's features.
- Providers' UI: (This service). A restricted UI where the events are maintained and published. Often called as "Admin UI".
- Teachers' UI: The frontend service where the groups can view and enrol in events.
- Headless CMS: Content Management Service that provides dynamic pages and dynamic content for the teachers' UI. It also provides content for the header and the footer. A React component library can be found from https://github.com/City-of-Helsinki/react-helsinki-headless-cms.
- LinkedEvents API: A city of Helsinki centralized API for events.
- Notification Service API: A service used by the Kultus API to send SMS messages.
- Unified Search: Provide a list of divisions.
- Helsinki Profile (Keycloak): Authorization service
- Mailer: A service used by the Kultus API to send emails.
- PowerBI: Data visualization.
- Digia Iiris: Web analytics (a Matomo service).
- Sentry: A monitoring service.
- Gruppo: Email newsletter service (https://api.createsend.com/api/v3.2/).
flowchart LR
subgraph ExternalServices["Third party services"]
direction LR
Gruppo["Gruppo"]
Matomo["Matomo"]
Sentry["Sentry"]
Mailer["Mailer"]
end
subgraph HelsinkiGraph["City of Helsinki services"]
direction LR
PowerBI["PowerBI"]
Keycloak["Keycloak"]
NotificationService["Notification Service"]
LinkedEvents["Linked Events"]
HeadlessCMS["HeadlessCMS"]
UnifiedSearch["UnifiedSearch"]
end
subgraph KultusBackend["Kultus Backend"]
direction LR
KultusAPI["Kultus API"]
end
subgraph PublicFrontend["Public Frontend"]
direction LR
KultusUI["Teachers UI"]
end
subgraph AdminInterface["Admin Interface"]
direction LR
KultusAdminUI["Providers UI"]
end
subgraph KultusInternal["Kultus"]
direction LR
KultusBackend
PublicFrontend
AdminInterface
end
KultusAPI == Syncs events data with ==> LinkedEvents
KultusAPI -- Authenticates via --> Keycloak
KultusAPI -- Sends SMS via --> NotificationService
KultusAPI -. Sends emails via .-> Mailer
KultusAPI -- Provides data for --> PowerBI
KultusAPI -. Reports errors to .-> Sentry
KultusUI == Depends on ==> KultusAPI
KultusUI == Uses content from ==> HeadlessCMS
KultusUI -- Getches divisions from --> UnifiedSearch
KultusUI -. Tracks usage via .-> Matomo
KultusUI -. Reports errors to .-> Sentry
KultusUI -. Subscribe to newsletter .-> Gruppo
KultusAdminUI == Depends on ==> KultusAPI
KultusAdminUI -- Authenticates with --> Keycloak
KultusAdminUI == Uses content from ==> HeadlessCMS
KultusAdminUI -. Tracks usage via .-> Matomo
KultusAdminUI -. Reports errors to .-> Sentry
style KultusInternal fill:#757575
Providers UI (the admin client) environments (this service):
- Production environment: https://kultus-admin.hel.fi/
- Staging environment: https://kultus-admin-ui.stage.hel.ninja/
- Testing environment: https://kultus-admin-ui.test.hel.ninja/
Teachers UI (the public UI) environments:
- Production environment: https://kultus.hel.fi/
- Staging environment: https://kultus-ui.stage.hel.ninja/
- Testing environment: https://kultus-ui.test.hel.ninja/
Kultus API environments:
- Production environment: https://kultus.api.hel.fi/graphql
- Staging environment: https://kultus.api.stage.hel.ninja/graphql
- Testing environment: https://kultus.api.test.hel.ninja/graphql
Headless CMS environments:
- Production environment: https://kultus.content.api.hel.fi/graphql
- Testing environment: https://kultus.app-staging.hkih.hion.dev/graphql
LinkedEvents
- Production environment: https://api.hel.fi/linkedevents/v1/
- Testing environment: https://linkedevents.api.test.hel.ninja/v1/
Notification service
- Production environment: https://kuva-notification-service.api.hel.fi/v1/
- Testing environment: https://kuva-notification-service.api.stage.hel.ninja/v1/
Unified Search
- Production environment: https://kuva-unified-search.api.hel.fi/search
- Testing environment: https://kuva-unified-search.api.stage.hel.ninja/search
This project is built using the following key frameworks and libraries:
- Vite: A modern frontend build tool that provides a fast and efficient development experience. It offers features like instant server start, hot module replacement, and optimized builds.
- React: A JavaScript library for building user interfaces. It allows for the creation of reusable UI components and efficient management of application state.
- Apollo: Apollo Client is a comprehensive state management library for JavaScript. It enables you to manage both local and remote data with GraphQL.
- Helsinki Design System: The central digital experience resource of Helsinki Design Language. Guidelines, design assets and component libraries for building a consistent and accessible digital brand across the city.
- React Helsinki Headless CMS -library: React Helsinki Headless CMS - is a highly customized component library based on HDS. It is designed for Helsinki City Web applications which are using preconfigured Wordpress Headless CMS environments (compatible with the library). This library is a set of unified visual components.
- Clone this repository
- If you're new to multiple
.env*files, read Vite's Env variables - If you're new to multiple Docker compose files, read Docker's Merge Compose Files
- Follow the instructions below for your preferred way of running this app:
Compatibility defined by Dockerfile:
- Docker
- Node.js 24.x
- pnpm
IMPORTANT: The event data is partially stored in LinkedEvents and partially in Kultus API. Because of that, Kultus API and LinkedEvents API databases needs to be in sync so they can successfully share and fullfill each other's event data. This means that if you run a local Kultus API, you will also need a local LinkedEvents, or otherwise one of the APIs have event data that cannot be fully fullfilled, which leads to missing events.
Kultus API extends the LinkedEvents event data by providing (one or more) occurrence times for a single event. When users are enrolling, they are actually enrolling to the event occurrences, not the event itself. The event is like an container to multiple occurrences.
There are multiple types of events:
-
Enrolments can be managed differently:
- Events that are public and does not need any enrolling.
- Events that have internal enrolments when the enrolment is done from the Kultus (from Teacher's UI) itself.
- Events that have external enrolments. Then Kultus does not know the enrolment status and cannot do any management to them.
-
Enrolments can be automatically or manually approved. IF the enrolments are approved automatically, the approval is given immediately after the enrolment is sent and the event still had enough space left for the study group that enrolled. If the enrolments are approved manually, the enrolments are managed by the event admins or providers through the Provider's UI.
-
Multiple occurrences might be needed. When an event is created (in Kultus Provider's UI), the admin can choose whether an enrolment needs multiple occurrences to be chosen. In such events, the registrant must commit to attending the event on multiple event dates. These enrolments are always handled in Kultus internally and are automatically approved.
-
Enrolments queue might also be enabled, which means that when an occurrence is fully booked, the study groups can enrol to queue. IF any enrolment cancellations are made or the enrolments are reorganized by the admins, enrolments in queue can be promoted to the group of actual registrations.
Please read the About Kultus data models (and relations to LinkedEvents) before choosing and configuring the environment to understand how fragmented the data is and how does it affect in Kultus when another environmetn is chosen.
The public test (and development environments) can be used to run this app, but sometimes it might be, that all the new features of the API are yet to be published to the public environment. Then you may need also a local Kultus API running in your own local machine.
-
Select the used API:
- When running with a public API, you will need only the UI running locally (in a Docker container or with a local Node).
- When running a fully local environment, you will need
- a local Kultus API (running with a local Python or in a Docker container)
- a local LinkedEvents (running with a local Python or in a Docker container, but a dockerized environment is highly recommended).
-
The OIDC clients needs to be configured so that
- the UI client and the API are using the same the authorization server, meaning that the API can validate and verify the JWT issuer.
- the UI's and the API's OIDC client names are set so that the authorization server the JWT's authorized party and audience matches the client names.
-
Configure LinkedEvents: The UI client needs to be configured to use the same LinkedEvents as the API is using or otherwise the links to the events will not work properly, since part of the event data would be missing (see About Kultus data models (and relations to LinkedEvents).
Using the following instructions you should be able to:
- Run this UI using local Node.js
- Run the Kultus API backend locally in Docker or use the public test environment's backend
- Use the public test environments of Helsinki Profile and Keycloak for authentication
-
Set up a local Node version can be set for example using NVM.
-
Copy .env.example file to ./.env file).
cp .env.example .env
-
Configure API endpoints
Kultus API
VITE_APP_API_URI=https://kultus.api.test.hel.ninja/graphql VITE_APP_API_REPORT_URI=https://kultus.api.test.hel.ninja/reports
LinkedEvents
VITE_APP_LINKEDEVENTS_API_URI=https://linkedevents.api.test.hel.ninja/v1
Optionally, if you set up a local API, remember to set up also a local LinkedEvents. You can find the instructions for that from Kultus API documentation https://github.com/City-of-Helsinki/palvelutarjotin. To find out why's that important, see the data model instructions.
NOTE: You can read how Vite uses multiple env-files from Vite's env variables -doc.
There are also some example configurations provided in the repo. Copy the .env.development.local.example file to ./.env.development.local:
cp .env.development.local.example .env.development.local
The most important things to set a local API are:
- set Kultus API endpoints
VITE_APP_API_URI=http://localhost:8081/graphql VITE_APP_API_REPORT_URI=https://localhost:8081/reports
- set LinkedEvents endpoint
VITE_APP_LINKEDEVENTS_API_URI=http://localhost:8080/v1
- Set OIDC client. Remember to set also the API to match these values.
# authorization server sets in JWT as a authorized party and audience. VITE_APP_OIDC_AUDIENCES="kultus-api-dev,profile-api-test" # API client id as in audience VITE_APP_OIDC_API_CLIENT_ID=kultus-api-dev # authorized party VITE_APP_OIDC_CLIENT_ID=kultus-admin-ui-dev
NOTE: if you have any problems to connect, double check the network port where the API is running. Running Docker services and their port forwardings can be checked with
docker ps -a.If you connect to a Docker container from your local computer, you can usually use https://localhost:8081/ to reach the API.
-
Run the Node application in development mode with
pnpm dev. At this point you should be also running the local APIs if you have selected to use them.
Using the following instructions you should be able to:
- Run this UI using Docker & Docker compose
- Run the Kultus backend locally in Docker or use the public test environment's backend
-
Copy .env.example file to ./.env file).
cp .env.example .env
-
Configure API endpoints
Kultus API
VITE_APP_API_URI=https://kultus.api.test.hel.ninja/graphql VITE_APP_API_REPORT_URI=https://kultus.api.test.hel.ninja/reports
LinkedEvents
VITE_APP_LINKEDEVENTS_API_URI=https://linkedevents.api.test.hel.ninja/v1
to use the API from the public test environment.
NOTE: If you connect to a Docker container from your local computer, you can usually use https://localhost:8081/ to reach the API, but if you connect from a Docker container to another, you should be using the container names like http://kultus-backend:8081/ to reach the API or use http://host.docker.internal:8081/ to connect via your local machine ports.
-
To build and start a docker container, you can use a development build:
NOTE: compose.yml maps some local directories as a volume in order to get a hot reload for the code to work.
DOCKER_TARGET=development docker compose up --build
or
if you want to run a production build, you most likely first need to unmap the composer volumes from compose.yml, by removing or fully commenting out the following section:
volumes: # Share local directory to enable development with hot reloading # NOTE: the volume mapping will break the production-stage build! - .:/app # Prevent sharing the following directories between host and container # to avoid ownership and/or platform issues: - /app/node_modules
and then running
DOCKER_TARGET=production docker compose up --build
NOTE: in normal use, you should not need this and usually you can just rely in Helsinki Profile's Keycloak.
For a local authorization service, you can use Tunnistamo. See Setup local Tunnistamo.
This project uses Husky to manage Git hooks. Husky is configured to run specific scripts before committing changes to ensure code quality and consistency.
The pre-commit hook is configured to run the following commands:
pnpm doctoc .
pnpm lint-staged --relativepnpm doctoc .: This command updates the table of contents in your markdown files.pnpm lint-staged --relative: This command runs linting on staged files to ensure they meet the project's coding standards. The lint-staged configuration can be found from lint-staged.config.js.- Using
--relativeflag to reduce command line length, as the combined length of all the absolute paths for a large commit can get quite long
- Using
NOTE:
doctocandhuskydoes not work seamlessly together, since thedoctocdoes update the TOCs of the markdown files, but does not reject the pre-commit hook execution, and only leaves the refactored files as unstaged in Git.
The commit-msg hook is configured to run the following command:
npx --no-install commitlint --edit "$1"npx --no-install commitlint --edit "$1": This command uses Commitlint to lint commit messages based on the project's commit message conventions. This repo follows the Conventional Commits.
In the project directory, you can run:
Runs the app in the development mode.
Open http://localhost:3000/ to view it in the browser.
The page will reload if you make edits.
You will also see any lint errors in the console.
Runs the tests and watches for changes in interactive watch mode.
See the section about running tests for more
information.
Run tests against uncommitted changes (including staged and unstaged).
Run tests against staged changes, used in husky git commit hook.
Run tests and generate coverage report
Run tests and generate coverage report, meant for Continuous Integration (CI) pipeline use.
Debug tests
Builds the app for production to the build folder.
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.
Your app is ready to be deployed!
See the section about deployment for more information.
Codegen settings in codegen.yml
- Generate static types for GraphQL queries by using the schema from the backend server. url to backend server is defined to VITE_APP_API_URI in .env.development.local
- Generate react hooks for GraphQL queries from query.ts and mutation.ts files.
A headless CMS system is used to produce some dynamic pages, but also the header and the footer contents for the UI (layout). The Headless CMS server endpoint is set with VITE_APP_CMS_URI environment variable.
The default server that is used is the test / staging server:
VITE_APP_CMS_URI=https://kultus.app-staging.hkih.hion.dev/graphql
See the available servers from Environments.
To login to the Wordpress admin UI, use
/wp/wp-admin/-endpoint, if the credentials you are using are Entra ID -credentials/wp-login.php-endpoint, if the credentials you are using are not Entra ID -credentials.
Git repository: https://github.com/City-of-Helsinki/react-helsinki-headless-cms/
The React Helsinki Headless CMS is a React component library developed by the City of Helsinki to facilitate the creation of web applications that interact with a headless WordPress CMS. This library provides a suite of pre-built components and utilities designed to streamline the development process and ensure consistency across various applications.
The architecture of the React Helsinki Headless CMS is modular and designed for flexibility:
- Component-Based Structure: The library is organized into reusable React components, each responsible for a specific piece of functionality or UI element. This modularity allows developers to compose applications efficiently by assembling these components as needed.
- Integration with Headless CMS: The library is tailored to work seamlessly with headless WordPress CMS environments. It relies on GraphQL schemas to fetch and present content, making it heavily dependent on the structure and features of the connected WordPress instance.
- Apollo Client Integration: For data management, the library utilizes Apollo Client to handle GraphQL queries and mutations. Developers are expected to provide an Apollo client linked to a GraphQL endpoint with a supported schema (headless CMS) in the apolloClient field of the configuration object.
The used environments are listed in Service environments.
The application uses automatic semantic versions and is released using Release Please.
Release Please is a GitHub Action that automates releases for you. It will create a GitHub release and a GitHub Pull Request with a changelog based on conventional commits.
Each time you merge a "normal" pull request, the release-please-action will create or update a "Release PR" with the changelog and the version bump related to the changes (they're named like release-please--branches--master--components--palvelutarjotin-admin).
To create a new release for an app, this release PR is merged, which creates a new release with release notes and a new tag. This tag will be picked by Azure pipeline and trigger a new deployment to staging. From there, the release needs to be manually released to production.
When merging release PRs, make sure to use the "Rebase and merge" (or "Squash and merge") option, so that Github doesn't create a merge commit. All the commits must follow the conventional commits format. This is important, because the release-please-action does not work correctly with merge commits (there's an open issue you can track: Chronological commit sorting means that merged PRs can be ignored ).
See Release Please Implementation Design for more details.
And all docs are available here: release-please docs.
Use Conventional Commits to ensure that the changelogs are generated correctly.
Release please goes through commits and tries to find "releasable units" using commit messages as guidance - it will then add these units to their respective release PR's and figures out the version number from the types: fix for patch, feat for minor, feat! for major. None of the other types will be included in the changelog. So, you can use for example chore or refactor to do work that does not need to be included in the changelog and won't bump the version.
The release-please workflow is located in the release-please.yml file.
The configuration for release-please is located in the release-please-config.json file. See all the options here: release-please docs.
The manifest file is located in the release-please-manifest.json file.
When adding a new app, add it to both the release-please-config.json and release-please-manifest.json file with the current version of the app. After this, release-please will keep track of versions with release-please-manifest.json.
If you were expecting a new release PR to be created or old one to be updated, but nothing happened, there's probably one of the older release PR's in pending state or action didn't run.
- Check if the release action ran for the last merge to main. If it didn't, run the action manually with a label.
- Check if there's any open release PR. If there is, the work is now included on this one (this is the normal scenario).
- If you do not see any open release PR related to the work, check if any of the closed PR's are labeled with
autorelease: pending- ie. someone might have closed a release PR manually. Change the closed PR's label toautorelease: tagged. Then go and re-run the last merge workflow to trigger the release action - a new release PR should now appear. - Finally check the output of the release action. Sometimes the bot can't parse the commit message and there is a notification about this in the action log. If this happens, it won't include the work in the commit either. You can fix this by changing the commit message to follow the Conventional Commits format and rerun the action.
Important! If you have closed a release PR manually, you need to change the label of closed release PR to autorelease: tagged. Otherwise, the release action will not create a new release PR.
Important! Extra label will force release-please to re-generate PR's. This is done when action is run manually with prlabel -option
Sometimes there might be a merge conflict in release PR - this should resolve itself on the next push to main. It is possible run release-please action manually with label, it should recreate the PR's. You can also resolve it manually, by updating the release-please-manifest.json file.
- Open release-please github action
- Click Run workflow
- Check Branch is master
- Leave label field empty. New label is not needed to fix merge issues
- Click Run workflow -button
There's also a CLI for debugging and manually running releases available for release-please: release-please-cli
When a Release-Please pull request is merged and a version tag is created (or a proper tag name for a commit is manually created), this tag will be picked by Azure pipeline, which then triggers a new deployment to staging. From there, the deployment needs to be manually approved to allow it to proceed to the production environment.
The tag name is defined in the azure-pipelines-release.yml.