Skip to content

Commit

Permalink
Merge branch 'main' into some_small_fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Lionqueen94 committed Feb 11, 2025
2 parents 5fe59c8 + 5575746 commit 9d96510
Show file tree
Hide file tree
Showing 15 changed files with 93 additions and 42 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/build-lint-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@ jobs:
if: runner.os != 'Windows'
- name: Run e2e playwright tests
run: npm run test:e2e
- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report-${{ matrix.os }}
path: frontend/playwright-report/
retention-days: 30

deploy:
name: Deploy to abacus-test.nl
Expand Down
13 changes: 9 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,12 @@ jobs:
path: ${{ matrix.target.binary }}

playwright-e2e:
name: Playwright e2e tests (${{ matrix.os }}, ${{ matrix.shardIndex }}/${{ matrix.shardTotal }})
name: Playwright e2e tests (${{ matrix.os }}
needs:
- build
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
shardIndex: [1, 2, 3]
shardTotal: [3]
runs-on: ${{ matrix.os }}
defaults:
run:
Expand All @@ -90,9 +88,16 @@ jobs:
run: chmod a+x ../builds/backend/abacus
if: runner.os != 'Windows'
- name: Run Playwright e2e tests
run: npm run test:e2e -- --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
run: npm run test:e2e
env:
BACKEND_BUILD: release
- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report-release-${{ matrix.os }}
path: frontend/playwright-report/
retention-days: 30

release:
name: Release
Expand Down
4 changes: 2 additions & 2 deletions backend/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2089,12 +2089,12 @@
"properties": {
"meets_surplus_threshold": {
"type": "boolean",
"description": "Whether this group met the threshold for surplus seat assigment"
"description": "Whether this group met the threshold for surplus seat assignment"
},
"pg_number": {
"type": "integer",
"format": "int32",
"description": "Political group number for which this assigment applies",
"description": "Political group number for which this assignment applies",
"minimum": 0
},
"rest_seats": {
Expand Down
34 changes: 21 additions & 13 deletions backend/src/apportionment/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize};
use tracing::{debug, info};
use utoipa::ToSchema;

use crate::election::PGNumber;
use crate::{data_entry::PoliticalGroupVotes, summary::ElectionSummary};

pub use self::{api::*, fraction::*};
Expand All @@ -24,13 +25,14 @@ pub struct ApportionmentResult {
/// Contains information about the final assignment of seats for a specific political group.
#[derive(Debug, PartialEq, Serialize, Deserialize, ToSchema)]
pub struct PoliticalGroupSeatAssignment {
/// Political group number for which this assigment applies
pg_number: u8,
/// Political group number for which this assignment applies
#[schema(value_type = u32)]
pg_number: PGNumber,
/// The number of votes cast for this group
votes_cast: u64,
/// The surplus votes that were not used to get whole seats assigned to this political group
surplus_votes: Fraction,
/// Whether this group met the threshold for surplus seat assigment
/// Whether this group met the threshold for surplus seat assignment
meets_surplus_threshold: bool,
/// The number of whole seats assigned to this group
whole_seats: u64,
Expand Down Expand Up @@ -59,7 +61,8 @@ impl From<PoliticalGroupStanding> for PoliticalGroupSeatAssignment {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
pub struct PoliticalGroupStanding {
/// Political group number for which this standing applies
pg_number: u8,
#[schema(value_type = u32)]
pg_number: PGNumber,
/// The number of votes cast for this group
votes_cast: u64,
/// The surplus of votes that was not used to get whole seats (does not have to be a whole number of votes)
Expand Down Expand Up @@ -312,7 +315,7 @@ fn allocate_remainder(
}

/// Assign the next remainder seat, and return which group that seat was assigned to.
/// This assigment is done according to the rules for elections with 19 seats or more.
/// This assignment is done according to the rules for elections with 19 seats or more.
fn step_allocate_remainder_using_highest_averages(
standing: &[PoliticalGroupStanding],
remaining_seats: u64,
Expand Down Expand Up @@ -360,7 +363,7 @@ fn political_groups_qualifying_for_unique_highest_average<'a>(
}

/// Assign the next remainder seat, and return which group that seat was assigned to.
/// This assigment is done according to the rules for elections with less than 19 seats.
/// This assignment is done according to the rules for elections with less than 19 seats.
fn step_allocate_remainder_using_highest_surplus(
assigned_seats: &[PoliticalGroupStanding],
remaining_seats: u64,
Expand Down Expand Up @@ -427,7 +430,7 @@ pub enum AssignedSeat {

impl AssignedSeat {
/// Get the political group number for the group this step has assigned a seat
fn political_group_number(&self) -> u8 {
fn political_group_number(&self) -> PGNumber {
match self {
AssignedSeat::HighestAverage(highest_average) => highest_average.selected_pg_number,
AssignedSeat::HighestSurplus(highest_surplus) => highest_surplus.selected_pg_number,
Expand All @@ -449,9 +452,11 @@ impl AssignedSeat {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
pub struct HighestAverageAssignedSeat {
/// The political group that was selected for this seat has this political group number
selected_pg_number: u8,
#[schema(value_type = u32)]
selected_pg_number: PGNumber,
/// The list from which the political group was selected, all of them having the same votes per seat
pg_options: Vec<u8>,
#[schema(value_type = Vec<u32>)]
pg_options: Vec<PGNumber>,
/// This is the votes per seat achieved by the selected political group
votes_per_seat: Fraction,
}
Expand All @@ -460,9 +465,11 @@ pub struct HighestAverageAssignedSeat {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
pub struct HighestSurplusAssignedSeat {
/// The political group that was selected for this seat has this political group number
selected_pg_number: u8,
#[schema(value_type = u32)]
selected_pg_number: PGNumber,
/// The list from which the political group was selected, all of them having the same number of surplus votes
pg_options: Vec<u8>,
#[schema(value_type = Vec<u32>)]
pg_options: Vec<PGNumber>,
/// The number of surplus votes achieved by the selected political group
surplus_votes: Fraction,
}
Expand All @@ -474,7 +481,7 @@ pub enum ApportionmentError {
}

/// Create a vector containing just the political group numbers from an iterator of the current standing
fn political_group_numbers(standing: &[&PoliticalGroupStanding]) -> Vec<u8> {
fn political_group_numbers(standing: &[&PoliticalGroupStanding]) -> Vec<PGNumber> {
standing.iter().map(|s| s.pg_number).collect()
}

Expand All @@ -493,6 +500,7 @@ mod tests {
get_total_seats_from_apportionment_result, seat_allocation, ApportionmentError,
},
data_entry::{Count, PoliticalGroupVotes, VotersCounts, VotesCounts},
election::PGNumber,
summary::{ElectionSummary, SummaryDifferencesCounts},
};
use test_log::test;
Expand All @@ -502,7 +510,7 @@ mod tests {
let mut political_group_votes: Vec<PoliticalGroupVotes> = vec![];
for (index, votes) in pg_votes.iter().enumerate() {
political_group_votes.push(PoliticalGroupVotes::from_test_data_auto(
u8::try_from(index + 1).unwrap(),
PGNumber::try_from(index + 1).unwrap(),
*votes,
&[],
))
Expand Down
27 changes: 21 additions & 6 deletions backend/src/data_entry/structs.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use crate::{data_entry::status::DataEntryStatus, error::ErrorReference, APIError};
use crate::{
data_entry::status::DataEntryStatus,
election::{CandidateNumber, PGNumber},
error::ErrorReference,
APIError,
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::{types::Json, FromRow};
Expand Down Expand Up @@ -154,7 +159,8 @@ impl DifferencesCounts {

#[derive(Serialize, Deserialize, ToSchema, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct PoliticalGroupVotes {
pub number: u8,
#[schema(value_type = u32)]
pub number: PGNumber,
#[schema(value_type = u32)]
pub total: Count,
pub candidate_votes: Vec<CandidateVotes>,
Expand Down Expand Up @@ -193,7 +199,11 @@ impl PoliticalGroupVotes {

/// Create `PoliticalGroupVotes` from test data.
#[cfg(test)]
pub fn from_test_data(number: u8, total_count: Count, candidate_votes: &[(u8, Count)]) -> Self {
pub fn from_test_data(
number: PGNumber,
total_count: Count,
candidate_votes: &[(CandidateNumber, Count)],
) -> Self {
PoliticalGroupVotes {
number,
total: total_count,
Expand All @@ -209,22 +219,27 @@ impl PoliticalGroupVotes {

/// Create `PoliticalGroupVotes` from test data with candidate numbers automatically generated starting from 1.
#[cfg(test)]
pub fn from_test_data_auto(number: u8, total_count: Count, candidate_votes: &[Count]) -> Self {
pub fn from_test_data_auto(
number: PGNumber,
total_count: Count,
candidate_votes: &[Count],
) -> Self {
Self::from_test_data(
number,
total_count,
&candidate_votes
.iter()
.enumerate()
.map(|(i, votes)| (u8::try_from(i).unwrap() + 1, *votes))
.map(|(i, votes)| (CandidateNumber::try_from(i + 1).unwrap(), *votes))
.collect::<Vec<_>>(),
)
}
}

#[derive(Serialize, Deserialize, ToSchema, Clone, Debug, PartialEq, Eq, Hash)]
pub struct CandidateVotes {
pub number: u8,
#[schema(value_type = u32)]
pub number: CandidateNumber,
#[schema(value_type = u32)]
pub votes: Count,
}
Expand Down
14 changes: 10 additions & 4 deletions backend/src/election/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,24 @@ pub enum ElectionStatus {
DataEntryFinished,
}

pub type PGNumber = u32;

/// Political group with its candidates
#[derive(Serialize, Deserialize, ToSchema, Clone, Debug, PartialEq, Eq, Hash)]
pub struct PoliticalGroup {
pub number: u8,
#[schema(value_type = u32)]
pub number: PGNumber,
pub name: String,
pub candidates: Vec<Candidate>,
}

pub type CandidateNumber = u32;

/// Candidate
#[derive(Serialize, Deserialize, ToSchema, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Candidate {
pub number: u8,
#[schema(value_type = u32)]
pub number: CandidateNumber,
pub initials: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[schema(nullable = false)]
Expand Down Expand Up @@ -108,12 +114,12 @@ pub(crate) mod tests {
/// Create a test election with some political groups.
/// The number of political groups is the length of the `political_groups_candidates` slice.
/// The number of candidates in each political group is equal to the value in the slice at that index.
pub fn election_fixture(political_groups_candidates: &[u8]) -> Election {
pub fn election_fixture(political_groups_candidates: &[u32]) -> Election {
let political_groups = political_groups_candidates
.iter()
.enumerate()
.map(|(i, &candidates)| PoliticalGroup {
number: u8::try_from(i + 1).unwrap(),
number: u32::try_from(i + 1).unwrap(),
name: format!("Political group {}", i + 1),
candidates: (0..candidates)
.map(|j| Candidate {
Expand Down
9 changes: 5 additions & 4 deletions backend/templates/model-na-31-2.typ
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#import "common/style.typ": conf, title, mono
#import "common/scripts.typ": *
#let input = json("inputs/model-na-31-2.json")
#set text(lang: "nl")

#show: doc => conf(input, doc, footer: [
#input.creation_date_time. Digitale vingerafdruk van EML-telbestand bij dit proces-verbaal (SHA-256): \
Expand Down Expand Up @@ -97,7 +98,7 @@ was. Indien er meerdere zittingslocaties waren, vermeld dan per lid de locatie.]
grid.cell(text(size: 8pt, [Locatie])),
grid.cell()[]
)),
table.cell(align: horizon, stack(dir: ltr, spacing: 3pt, time_input(time: ""), align(top, [-]), time_input(time: ""))),
table.cell(align: horizon, stack(dir: ltr, spacing: 3pt, time_input(time: ""), "-", time_input(time: ""))),
)}).flatten(),
)

Expand All @@ -117,7 +118,7 @@ was. Indien er meerdere zittingslocaties waren, vermeld dan per lid de locatie.]
..input.polling_stations.map(polling_station => {(
[#polling_station.number],
[
#if polling_station.polling_station_type == "Mobile" [
#if "polling_station_type" in polling_station and polling_station.polling_station_type == "Mobile" [
_(Mobiel stembureau)_
] else [
#polling_station.address \
Expand Down Expand Up @@ -340,8 +341,8 @@ Naam leden
naam hebben genoteerd, ondertekenen het proces-verbaal. Houd hierbij de volgorde aan van rubriek 12.
]

#block(width: 100%, align(right + horizon, stack(dir: ltr, spacing: 15pt,
[Datum: ],
#block(width: 100%, align(right, stack(dir: ltr, spacing: 15pt,
align(horizon, [Datum: ]),
date_input(date: none, top_label: ([Dag], [Maand], [Jaar])),
time_input(time: none, top_label: "Tijd")
)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ In de onderstaande tabel is aangegeven welke partijen of instanties fungeren als
| Tweede kamer | 1-20 | gemeentes | Den Haag, ACStM[^1] | gemeente | kieskring | Kiesraad |
| Europees Parlement | 1 (heel NL) | gemeentes | Den Haag, ACStM | gemeente | 20 HSBs | Kiesraad |
| Provinciale Staten | 1-19 | gemeentes | nvt | gemeente | als meerdere kieskringen in één provincie | 1 gemeente per provincie |
| Waterschappen | per waterschap | gemeentes | nvt | gemeente | nvt | 1 gemeente per waterschap |
| Waterschappen | per waterschap | gemeentes | nvt | gemeente | nvt | Waterschap |
| Kiescolleges Eerste Kamer | per openbaar lichaam | openbare lichamen | nvt | nvt | nvt | per eiland |
| | buitenland | nvt | ja | nvt | nvt | Zuid-Holland |
| Eerste Kamer | per provincie | statenvergadering | nvt | nvt | nvt | Kiesraad |
Expand Down
1 change: 1 addition & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ test-results
coverage
mock
test-report.junit.xml
playwright-report/

# Automatically created: https://github.com/mswjs/msw/discussions/1015#discussioncomment-1747585
mockServiceWorker.js
3 changes: 3 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ npm run test:ladle
npm run test:e2e
# run tests, expect builds and database to be available:
npm run test:e2e-dev

# view reports and traces, e.g. the ones saved by our pipeline:
npx playwright show-report <path-to-unzipped-report-folder>
```

### UI Component development
Expand Down
2 changes: 2 additions & 0 deletions frontend/e2e-tests/authentication.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ test.describe("authentication", () => {
await page.getByLabel("Wachtwoord").fill(password);
await page.getByRole("button", { name: "Inloggen" }).click();

await page.waitForURL("/account/setup");

// TODO: use new page object when we know which page to render
await expect(page.getByRole("alert")).toContainText("Inloggen gelukt");
});
Expand Down
5 changes: 2 additions & 3 deletions frontend/playwright.common.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ const commonConfig: PlaywrightTestConfig = defineConfig({
workers: process.env.CI ? "100%" : undefined,
// Increase the test timeout on CI, which is usually slower
timeout: process.env.CI ? 30_000 : 10_000,
// Use the list reporter even on CI, to get immediate feedback
reporter: "list",
fullyParallel: true,
use: {
trace: "retain-on-failure",
// Local runs don't have retries, so we have a trace of each failure. On CI we do have retries, so keeping the trace of the first failure allows us to investigate flaky tests.
trace: "retain-on-first-failure",
testIdAttribute: "id",
},
projects: [
Expand Down
1 change: 1 addition & 0 deletions frontend/playwright.e2e.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function returnWebserverCommand(): string {

const config: PlaywrightTestConfig = defineConfig({
...commonConfig,
reporter: process.env.CI ? [["list"], ["html", { open: "never" }]] : "list",
testDir: "./e2e-tests",
outputDir: "./test-results/e2e-tests",
testMatch: /\.e2e\.ts/,
Expand Down
2 changes: 2 additions & 0 deletions frontend/playwright.ladle.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import commonConfig from "./playwright.common.config";

const config: PlaywrightTestConfig = defineConfig({
...commonConfig,
// Use the list reporter even on CI, to get immediate feedback
reporter: "list",
testDir: "./lib/ui",
outputDir: "./test-results/ladle",
testMatch: /\.e2e\.ts/,
Expand Down
Loading

0 comments on commit 9d96510

Please sign in to comment.