Skip to content

The front-end for Lokaal Beslist, a webapp that uses linked data to empower everyone in Flanders to consult the decisions made by their local authorities.

Notifications You must be signed in to change notification settings

lblod/frontend-burgernabije-besluitendatabank

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BurgerNabijeBesluitendatabank (front-end)

The front-end for BNB, a site that uses linked data to empower everyone in Flanders to consult the decisions made by their local authorities.

You can check out more info on besluitendatabanken here, and the app repo here. The app repo will also contain general project information; this repo should only contain information needed specifically for the front-end itself.

Tutorial: Getting Started

Prerequisites

Installation

git clone https://github.com/lblod/frontend-burgernabije-besluitendatabank
cd frontend-burgernabije-besluitendatabank
npm install

From there, you can use npm run dev:proxy, or any of the following npm scripts:

npm run... Description
build Creates a production-ready static build
build:analyze-bundle Creates a production-ready static build and outputs some extra bundle information.
lint Runs the linter and returns any errors/warnings. Is run automatically before committing, cancelling the commit on error
lint:fix Runs the linter, attempting to automatically fix any errors
dev Run a development server with the mock api
dev:proxy Run a development server with a proxy to the external dev server
start Run a development server with the mock api
prod Run a production server
test Run the linter and then ember tests

Automated builds

The project uses woodpecker to automate builds. The configuration files can be found in the .woodpecker folder. Builds are available here.

  • Latest master build : lblod/frontend-burgernabije-besluitendatabank:latest
  • Feature branch build : lblod/frontend-burgernabije-besluitendatabank:feature-<branch-name>
  • Version tag build : lblod/frontend-burgernabije-besluitendatabank:<version-tag> tag like v1.2.3 would be published as 1.2.3 (so the v is dropped)

Release

The project uses release-it to automate releases. The configuration files can be found in the .release-it.json file.

To enable the release process, it's crucial to set the GITHUB_AUTH environment variable, which requires a personal access token with the repo scope. This access token is necessary for creating and publishing releases on GitHub.

CHANGELOG.md is generated by @release-it-plugins/lerna-changelog. The changelog is constructed based on the labels applied to merged pull requests, specifically the enhancement and bug labels.

The release process is as follows:

  1. Check the labels on recently merged pull requests
  2. Checkout master branch
  3. Run npm run release to initiate a new release
  4. Follow instructions

Reference

Feature flags

Feature flags are used to enable/disable features in the application. They are defined in config/environment.js.

// in config/environment.js
let ENV = {
  // Other configuration settings...
  features: {
    "new-feature": true, // Enable the 'new-feature' by default
    "beta-feature": false, // Disable the 'beta-feature' by default
  },
};

The configuration can be manually overridden by adding a query parameter to the URL:

  • ?feature-new-feature=false to disable the 'new-feature'
  • ?feature-beta-feature=true to enable the 'beta-feature'

The overriding will be saved in a cookie, so it will persist across page reloads. The cookie can be cleared by adding ?clear-feature-overrides=true to the URL.

The feature flags can be used in the application by injecting the features service and calling the isEnabled method.

import { inject as service } from "@ember/service";

export default class ExampleComponent extends Component {
  @service features;

  doSomething() {
    if (this.features.isEnabled("new-feature")) {
      // Implement the logic for the new feature
      console.log("New feature is enabled!");
    } else {
      // Implement the logic for the default behavior without the new feature
      console.log("New feature is disabled!");
    }
  }
}

Or in template files by using the is-feature-enabled helper:

{{#if (is-feature-enabled "new-feature")}}
  <p>New feature is enabled!</p>
{{else}}
  <p>New feature is disabled!</p>
{{/if}}

List of feature flags

Name Description
statistics-page-feature This feature handles the statistics part of the dataQuality page

Environment variables

Name Description
PLAUSIBLE_APIHOST The domain where the plausible.js can be found
PLAUSIBLE_DOMAIN The domain that has been configured to be tracked. If undefined
GOVERNING_BODY_DISABLED_LIST A comma-separated list of governing bodies that should be disabled

Disabling governing bodies

Time to time, it might be necessary to disable certain governing bodies. This can be done by setting the GOVERNING_BODY_DISABLED_LIST environment variable. The value should be a comma-separated list of mu:uuid of the governing bodies that should be disabled. The agenda items related to the disabled governing bodies will not be accessible in the application.

styles/ naming scheme

Ember works by outputting one big CSS file. This results in only needing one request for all styling (good for the end users) but also that every css you write for a route will always apply (ouch for the developers). To prevent duplicate ID's goofing things up and the .scss file becoming unreadable, The following structure has been set.

  • app.scss: File that will be output. HTML tag styling & imports are done here.
  • _au-overrides.scss: Overrides for appuniverse components
  • _custom-classes.scss: Custom classes that are meant to be used across the application
  • components/_*.scss
    • One scss file per component
    • Use the following selector structure .component-COMPONENT-NAME (make sure you use a class selector, as components may be reused on the same page)
  • routes/_*.scss
    • One scss file per route
    • Use the following selector structure #route-ROUTE-NAME
      • Use an ID, ideally applied to the top-most element of the route
      • For further SCSS rules that need to apply to this specific route, write them as follows:
        #route-ROUTE-NAME {
          .header {
            /* ... */
          }
          p {
            /* ... */
          }
        }
        This is to ensure it only applies to this route. If the styling should apply to multiple routes, it should be a custom class or element style instead

Discussions

Data Alignment

Due to the intricacies of levels of Flemish bureaucratic units a data alignment meeting had to be planned. The reason for this confusion was due to the levels of municipal governance in Flanders. After properly outlining the importance and nuance of each level it was decided that citizens should be able to see both the administrative unit. The levels are as follows:

  • Governing Body (Bestuursorgaan) (Algemene Vergadering Boom Plus)
  • Administrative Unit (Bestuurseenheid) (Boom Plus)
  • Location (Boom)

Data Alignment Analysis

Filtering

To help end users find what they're looking for, we allow filtering on agenda-items as well as sessions in the following ways:

*: only agenda-items

These filters are powered by two important moving parts

  1. mu-cl-resources: this is the JSON:API our back-end uses (because JSON:API leaves some freedom of implementation, mentioning this is important! There are a few features and quirks that come from mu-cl-resources itself)
  2. Ember-Data: this is the JSON:API-to-JavaScript our front-end uses

Default filters

The following filters are on by default for both agenda-items and sessions:

  • session:has:governing-body == true
  • session.governing-body:has:administrative-unit == true
  • session.governing-body.administrative-unit:has:name == true

Municipality

So, the filter we present offers to filter on municipality. This is kind of a lie. Or well, it works different in the back-end.

We chose not to allow filtering on which entity made the decision. While this would allow specific filtering (e.g. "OCMW X" or "Police Y"), this would also:

  • Create a gigantic select options list
  • Make the general use case of "I want to know what is happening in my city" tedious to achieve

Instead, we check (agenda-item.) session.governing-body.administrative-unit.location. Our municipality-list service queries all locations on the gemeente/municipality level, and we keep their label/name as well as their id.

This id allows us to use mu-cl-resources' :id: helper, which makes searching on one location as easy as multiple

  • Single: (agenda-item.) session.governing-body.administrative-unit.location:id:=ID
  • Multiple: (agenda-item.) session.governing-body.administrative-unit.location:id:=ID1,ID2,ID3 (*)

*Note: the :id: helper is an or selector, not and

Keyword

This is a very simple one! This uses mu-cl-resources' :or: helper to check if agenda-items.title or agenda-items.description features the typed keyword.

Date

This uses the mu-cl-resources' :gt: and :lt: helpers (greater than and less than respectively). An agenda-item or session is returned if...

Agenda-items:

  • The first selected date is after (gt) agenda-item.session.started-at
  • The second selected date is before (lt) agenda-item.session.ended-at

This will return agenda-items with sessions that have been finished.

Sessions:

  • The first selected date is after (gt) session.planned-start
  • The second selected date is before (lt) session.planned-start

This will return sessions that started between those two dates, whether they have been finished or not.

Note: the information in this spoiler is for a possible future filter change
  • The first selected date is after (gt) (agenda-item.) session.planned-start
  • The second selected date is before (lt) (agenda-item.) session.planned-start

There is additionally a session.started-at and session.ended-at property, but these are only present when the session is in the past, which would give incomplete results.