KommuneAdvisor is a web application that lets you find information about and review the Norwegian municipalities, also known as kommuner. A user can search for a given kommune and filter kommuner on county. Kommuner can also be sorted by area, population and ratings, both ascending and descending. From the search results, the user can click on a kommune to view a details page with more information and ratings about this kommune.
In the project directory, you can run:
cd frontend
to navigate to the frontend directorynpm start
to install dependencies and run the project in development modenpm test
to install dependencies run the test runner interactivelynpm run build
to build the app for production to thebuild
foldernpx run lint
to run prettier and eslint checksnpm run test:e2e
to run Cypress tests (headlessly)npm run test:e2e-open
to run Cypress tests (with GUI)
Open http://localhost:3000 to view it in the browser.
In order to access the database, you must be connected to NTNU VPN.
In the project directory, you can run:
cd backend
to navigate to the backend directorynpm install
to install dependenciesnpm run dev
to run server using nodemon, automatically restarting server on file changesnpx run lint
to run prettier and eslint checks
The backend runs on http://localhost:8000.
The main purpose of global state is to share a state among multiple components in order to avoid prop drilling. In this application we have implemented global state management using Redux Toolkit for the search field, dropdown menus and current page. A user can search for and filter kommuner, click on a kommune to view the details page, then go back to the search results and see that the values for search field, filter and page have persisted.
We implemented the global states with Redux before fetching data from the backend, and therefore did not know that this could be done in the cache configuration with Apollo client. Although using Redux requires a bit more boilerplate code, it was quite simple to implement and works well with our GraphQL backend.
With the search field, the user can search for a kommune. Suggested kommuner based on the user inputs will be displayed on the main page. When the user types, the input is sent to the backend which returns kommuner that match the input. The kommuner that match the input are then displayed, sorted after whatever sorting the user has applied (alphabetically if no sorting is applied). The search is also compatible with filtering on counties, which means that when the user filters on a county and enters a search term, the kommuner that match the search term and are in the selected county will be displayed.
The user has the option sort kommuner by area, population and ratings, both ascending and descending. The user can also filter kommuner on county. If no sorting is applied, kommuner will be sorted alphabetically. The three last letters of the Norwegian alphabet are not sorted as most Norwegians are used to, as Å comes before Ø. This is because these letters are not used in the English alpahbet, and therefore not sorted correctly by default.
The kommune cards are paginated, displaying 24 kommuner at a time. We chose this number since it is divisible by both 4, 3 and 2, making it compatible with the grid. The query for fetching kommuner is paginated, such that one can specify the number of elements per page and get the current page. This is implemented frontend by storing the current page in a global state, and setting the current page to 1 when the user updates the input fields.
From the search results, a user can click into the details page of a kommune. This does a new query to fetch information about the given kommune based on id. The id is also displayed in the url for the details page /kommune/:id
.
Web accessibility is the need for websites to utilize tools and technologies developed to aid the perception, understanding, contribution, navigation, and interaction of a person with disabilities on the site. We have used the component library Mantine in order to achieve this.
To make our website perceivable, the information about a kommune is displayed on the details page with a readable font and described with a label and emoji. All input fields are described with a label as well. When hovering a button, the cursor is changed to pointer, indicating that the user can click. All images have textual alternatives, implemented with the alt-attribute.
To make the website understanding, feedback is given on user interaction. When hovering a kommune card or review, a shadown is added. While fetching data a loading icon is displayed, and if the data cannot be retrieved an error message is displayed. When creating a new review, the star rating displays a textual description according to the number of selected stars.
To ensure intuitive navigation, the user can view the kommune details page by clicking on the "Show more" button for the given kommune. In order to navigate back to the main page, the user can either click on the logo in the navbar or the back-button.
The website scales dynamically after screen size, and is therefore mobile responsive for screens larger than 300px.
The backend is buildt with Node Express.
MongoDB is a document database. To implemenet mongoDB on the Express backend we have used mongoose. The database consists of three collections:
- county
- kommune
- kommuneRating
To query data from the backend we have buildt a GraphQL interface. Here are the GraphQL endpoints:
Endpoint to query all kommunes
Parameters
sortBy - What to sort by (ex: name / population)
sortDirection - Which way to sort the reults (ex: ascending / descending)
search - Search for kommues (ex: Molde / Trondheim)
county - Filter by county (ex: Møre og Romsdal / Viken)
Fields
_id - Unique identifier, this is set as the national kommune number
name - Name of the kommune
population - The kommunes Population
areaInSquareKm - Size of the kommune in Km2.
landAreaInSquareKm - Size of the kommune in squareKm, excluding water.
populationByArea - Average population pr squareKm
mapUrl - Link to the map image
snlLink - Link to the SNL page
logoUrl - Link to the logo image
writtenLanguage - What written language the kommune uses
averageRating - The average of all kommuneRatings for the kommune
county - The county the kommune belongs to
kommuneRating - All the KommuneRatings for that kommune
Endpoint to query count of all kommuner
Parameters
search - Search for kommues (ex: Molde / Trondheim)
county - Filter by county (ex: Møre og Romsdal / Viken)
Fields
kommunerCount - Count of how many kommuner matched the filter.
Endpoint to query all countys
Parameters
No parameters
Fields
_id - Unique identifyer, this is set as the national county number
name - Name of the county
The kommune weapons and map images are scraped from Wikipedia. The numbers and statistics used in the application are gathered from Statistisk Sentralbyrå (SSB).
The average rating is calculated by taking the sum of all ratings and dividing it by the number of ratings. This is done in the backend, and the average rating is stored in the database. This is done to avoid having to calculate the average rating for each kommune every time the user visits the details page. The average rating is also updated when a new review is created.
We have chosen to store the date as a timestamp in the backend. This way it is easy to convert to other date formats and time zones later.
We have used the testing framework Jest to create unit tests, using its built-in functions. One of the tests that were performed is to check whether the app crashes on render, by testing the App
component. In addition, we have tested user interaction for the search input field. The service function getRatingDescription
has also been tested, which is used to display the correct description of selected amount of stars. Snapshot tests are useful when wanting to make sure that the UI does not change unexpectedly. The tests generate a json version of a component, to check if this matches an earlier representation. We created snapshot tests for the KommuneCard
and ReviewCard
components with use of the react-test-renderer, to ensure that they rendered as expected with data.
Cypress is used for end-to-end testing. The naming convention of Cypress IDs is to use kebab-case, rather than camel-case, which is used in other parts of the application. This is done to better distinguish between the two types of IDs. We have created tests for the main page, details page and when adding a review.
We have used the formatting tool Prettier and linter ESLint to ensure a common coding style and good code quality.
All development tasks are documented in a issue with appropriate labels. After assigning yourself to an issue, the issue should be solved in its own branch. We have disabled pushing to main branch, such that the only way to add code is though merge requests. When merging with main branch, the commits should be squashed.
We have used a simple version of the conventional commits guidelines.
Format: <type>: <subject>
feat: add hat wobble
^--^ ^------------^
| |
| +-> Summary in present tense.
|
+-------> Type: chore, docs, feat, fix, refactor, style, or test.
More Examples:
feat
: (new feature for the user, not a new feature for build script)fix
: (bug fix for the user, not a fix to a build script)docs
: (changes to the documentation)style
: (formatting, missing semi colons, etc; no production code change)refactor
: (refactoring production code, eg. renaming a variable)test
: (adding missing tests, refactoring tests; no production code change)chore
: (updating grunt tasks etc; no production code change)