Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
ca3cce2
Remove logging from autocomplete JS unit test
paulrobertlloyd Sep 3, 2025
f9e356f
Add sticky JavaScript component module
paulrobertlloyd Sep 3, 2025
506570b
Add missing class name and aria label to AppSecondaryNavigationComponent
paulrobertlloyd Sep 3, 2025
b765069
Add AppStickyNavigationComponent
paulrobertlloyd Sep 3, 2025
d26f443
Consistently use stick Javascript module component
paulrobertlloyd Sep 3, 2025
72d8425
Add `source` to vaccination record UI
alistair-white-horne-tng Sep 3, 2025
a1932d8
Rename FHIR fixtures
alistair-white-horne-tng Sep 3, 2025
c7acef0
Add `read_immunisation`
alistair-white-horne-tng Sep 3, 2025
6637065
Add `read_immunisation_by_nhs_immunisations_api_id`
alistair-white-horne-tng Sep 3, 2025
873fe5b
Make `read_immunisation` a thin wrapper around `read_immunisation_by_…
alistair-white-horne-tng Sep 3, 2025
9d3957f
Create `read-imms-api` CLI tool
alistair-white-horne-tng Sep 3, 2025
f2ddab1
Send pseudo-postcode to Imms API when patient postcode nil
mikejamesthompson Sep 9, 2025
d768e11
Factor out default postcode for FHIR record into named constant
mikejamesthompson Sep 10, 2025
adfe862
Ensure emails don't end with a dot
thomasleese Sep 10, 2025
47c7f89
Add migration for emails with trailing dots
thomasleese Sep 10, 2025
6b80503
Ensure incorrect vaccine given message isn't shown
thomasleese Sep 4, 2025
c2f60f9
Create job to make search request, and handle response
alistair-white-horne-tng Aug 22, 2025
1934dbd
Add digest to manifest path to enable long-lived caching
paulrobertlloyd Sep 11, 2025
a8b5426
Rename `SessionAttendance`
thomasleese Sep 8, 2025
b186b98
Remove `PatientSession#todays_attendance`
thomasleese Sep 4, 2025
d17b3ff
Remove session association from `AttendanceRecord`
thomasleese Sep 8, 2025
da06777
Merge pull request #4496 from nhsuk/sticky
thomasleese Sep 11, 2025
d68ad50
Merge pull request #4599 from nhsuk/web-app-manifest-digest
thomasleese Sep 11, 2025
9be1a05
Merge pull request #4578 from nhsuk/email-validation-trailing-dot
thomasleese Sep 11, 2025
265643d
Merge pull request #4571 from nhsuk/use-pseudo-postcode-when-nil
mikejamesthompson Sep 11, 2025
ce26876
Merge pull request #4553 from nhsuk/prepare-registration-status-location
thomasleese Sep 11, 2025
dad56ea
Merge pull request #4520 from nhsuk/fix-warning-not-matching
thomasleese Sep 11, 2025
6e5644b
Merge pull request #4515 from nhsuk/display-vaccination-record-source…
alistair-white-horne-tng Sep 11, 2025
9c955d3
Merge pull request #4480 from nhsuk/imms-api-read-request
alistair-white-horne-tng Sep 11, 2025
84f707e
Merge pull request #4481 from nhsuk/imms-api-read-request-cli
alistair-white-horne-tng Sep 11, 2025
3d8fbbc
Merge pull request #4477 from nhsuk/imms-api-search-job
alistair-white-horne-tng Sep 11, 2025
a3d0d79
Add `ImmunisationsAPIJob`
thomasleese Sep 11, 2025
b145491
Merge pull request #4606 from nhsuk/fix-immunisations-api-jobs
thomasleese Sep 11, 2025
8f86591
Add `dump.rdb` to Git ignore
thomasleese Sep 11, 2025
0e89e37
Add PDS match rate validation for patient imports
murugapl Sep 1, 2025
f06fd16
Fix AppImportErrorsComponent spec
murugapl Sep 2, 2025
7b2c589
Use I18n pluralisation for PSD eligibility message
jhenderson Sep 11, 2025
dc26ab8
Display tallies on record vaccinations tab
jhenderson Sep 9, 2025
2f4df3c
Merge pull request #4605 from nhsuk/dump-rdb
thomasleese Sep 11, 2025
e751a75
Merge pull request #4604 from nhsuk/psd-bulk-add-page-singular-plural…
jhenderson Sep 11, 2025
bfd9486
Merge pull request #4570 from nhsuk/vaccination-tallies
jhenderson Sep 11, 2025
f16d74d
Refactor `AppPatientSessionRecordComponent#render?`
thomasleese Sep 11, 2025
578510a
Prevent deletion of vaccination records from NHS immunisations API
samcoy3 Aug 26, 2025
30ab6cc
Merge pull request #4607 from nhsuk/view-completed-patient-no-session…
thomasleese Sep 11, 2025
2fbd5b3
Merge pull request #4460 from nhsuk/fail-imports-with-low-pds-match-r…
thomasleese Sep 11, 2025
3a33f67
Merge pull request #4405 from nhsuk/prevent-modification-of-searched-…
samcoy3 Sep 11, 2025
1633055
Order team workgroups by name in output
thomasleese Sep 11, 2025
a758679
Tidy up CLI stats tests
thomasleese Sep 11, 2025
7878996
Add `PatientArchiver`
thomasleese Sep 11, 2025
3d6a04e
Delete school moves when archiving patients
thomasleese Sep 11, 2025
ca7cf64
Add `remove_school_moves_from_archived_patients`
thomasleese Sep 11, 2025
d897264
Enable SystmOne for all programmes
thomasleese Sep 11, 2025
1f53419
Add MenQuadfi SystmOne code
thomasleese Sep 11, 2025
c0beba7
Don’t reserve space for non-existent actions on app patient session s…
paulrobertlloyd Sep 11, 2025
7f1ce10
Bump aws-sdk-rds from 1.292.0 to 1.293.0
dependabot[bot] Sep 11, 2025
48eb869
Update method for adjusting space between summary list and button group
paulrobertlloyd Sep 11, 2025
c6c9ebf
Fix styling of button_to within button groups
paulrobertlloyd Sep 11, 2025
89f1c0c
Don’t render a list if there is only one next activity
paulrobertlloyd Sep 11, 2025
d8d1e86
Link patient session search result card component when not shown with…
paulrobertlloyd Sep 11, 2025
e213d42
Merge pull request #4609 from nhsuk/dependabot/bundler/next/aws-sdk-r…
thomasleese Sep 11, 2025
374ce28
Update Reporting API credentials in staging
tvararu Sep 12, 2025
3a04276
Merge pull request #4610 from nhsuk/design-tweaks-sept
thomasleese Sep 12, 2025
f336517
Merge pull request #4611 from nhsuk/reporting-staging
tvararu Sep 12, 2025
4712e52
Merge pull request #4602 from nhsuk/cli-stats-order-workgroups
thomasleese Sep 12, 2025
53bf1e3
Merge pull request #4603 from nhsuk/doubles-systmone
thomasleese Sep 12, 2025
6cf0541
Merge pull request #4608 from nhsuk/delete-school-moves-on-archive
thomasleese Sep 12, 2025
50fbb57
Fix NoMethodError in vaccination tally when session data is inconsistent
jhenderson Sep 12, 2025
b7bfb75
ACU were increased to 64 for operational requirements
TheOneFromNorway Sep 12, 2025
b909c5d
Merge pull request #4614 from nhsuk/persist_acu_scaling
thomasleese Sep 12, 2025
b31f7a1
Trigger an Imms API search on NHS number change
alistair-white-horne-tng Sep 3, 2025
75a5d6b
Trigger an Imms API search during bulk imports
alistair-white-horne-tng Sep 10, 2025
93c56e7
Merge pull request #4613 from nhsuk/fix-tally-table
jhenderson Sep 12, 2025
6390dca
Merge pull request #4479 from nhsuk/trigger-imms-api-search-on-nhs-nu…
alistair-white-horne-tng Sep 12, 2025
2174320
Fix flaky check import test
thomasleese Sep 12, 2025
1463a68
Allow tests to exist in spec/support
misaka Sep 3, 2025
b2b92f3
Add within_academic_year rspec helper
misaka Aug 29, 2025
253ab34
Add EnqueueVaccinationsSearchInNHSSJob
misaka Sep 1, 2025
d0e9ae9
Add EnqueueVaccinationsSearchInNHSJob Sidekiq cron
misaka Sep 11, 2025
2b9ae88
Merge pull request #4612 from nhsuk/imms-api-enqueue-searches
misaka Sep 12, 2025
3119630
Merge pull request #4615 from nhsuk/gias-import-flaky-test
thomasleese Sep 12, 2025
2c5d919
Filter vaccination tallies to show only today's records
jhenderson Sep 12, 2025
6641d45
Remove remaining code related to offline working
thomasleese Sep 12, 2025
6759c8f
Merge pull request #4617 from nhsuk/remove-offline-working
thomasleese Sep 12, 2025
06c96d7
Merge pull request #4616 from nhsuk/scope-tallies-on-the-day
jhenderson Sep 12, 2025
e428d81
Bump pundit from 2.5.0 to 2.5.1
dependabot[bot] Sep 12, 2025
003a5a5
Merge pull request #4619 from nhsuk/dependabot/bundler/next/pundit-2.5.1
thomasleese Sep 14, 2025
45c11ea
Refactor AppVaccinationsSummaryTableComponent
thomasleese Sep 15, 2025
38130fb
Hide discontinued vaccines from summary table
thomasleese Sep 15, 2025
8e56119
Call `StatusUpdater` after Imms API search
alistair-white-horne-tng Sep 15, 2025
c6ed0be
Merge pull request #4622 from nhsuk/fix-search-status-updater
alistair-white-horne-tng Sep 15, 2025
726c38d
Merge pull request #4620 from nhsuk/hide-discontinued-vaccines
thomasleese Sep 15, 2025
995ef5b
Remove duplicate code
thomasleese Sep 15, 2025
980b05f
Fix flaky PDS import test
thomasleese Sep 15, 2025
4d0422a
Merge pull request #4623 from nhsuk/fix-pds-flaky-test
thomasleese Sep 15, 2025
99aed43
Use `mail_to` helper
thomasleese Sep 15, 2025
19e7216
Merge pull request #4621 from nhsuk/mail-to
thomasleese Sep 15, 2025
a5a038b
Fix half doses for flu records
alistair-white-horne-tng Sep 15, 2025
3704e47
Merge pull request #4625 from nhsuk/fix-fhir-half-dose
alistair-white-horne-tng Sep 15, 2025
b01112a
Hide vaccination tallies table for admins
jhenderson Sep 15, 2025
b04f2f9
Merge pull request #4624 from nhsuk/hide-vaccinations-summary-table-f…
thomasleese Sep 15, 2025
d77569e
Handle merging patients with PSDs
thomasleese Sep 15, 2025
016ce8a
Merge pull request #4627 from nhsuk/merge-patient-specific-direction
thomasleese Sep 15, 2025
ea95a06
Disable enqueue_vaccinations_search_in_nhs_job until the implementati…
benilovj Sep 15, 2025
304541b
Merge pull request #4630 from nhsuk/disable-enqueue_vaccinations_sear…
benilovj Sep 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,6 @@ tests/dist
# These files are autogenerated by the deploy GitHub Action
public/sha
public/ref

# Redis
dump.rdb
4 changes: 2 additions & 2 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ terraform/.terraform
*.tfstate
*.tfstate.backup
scratchpad
spec/fixtures/files/fhir/immunisation-create.json
spec/fixtures/files/fhir/immunisation-update.json
spec/fixtures/files/fhir/immunisation_create.json
spec/fixtures/files/fhir/immunisation_update.json
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,5 @@ group :test do
gem "shoulda-matchers"
gem "simplecov", require: false
gem "webmock"
gem "rack_session_access"
end
10 changes: 7 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ GEM
ast (2.4.3)
attr_required (1.0.2)
aws-eventstream (1.4.0)
aws-partitions (1.1156.0)
aws-partitions (1.1159.0)
aws-sdk-accessanalyzer (1.78.0)
aws-sdk-core (~> 3, >= 3.231.0)
aws-sigv4 (~> 1.5)
Expand All @@ -137,7 +137,7 @@ GEM
aws-sdk-kms (1.112.0)
aws-sdk-core (~> 3, >= 3.231.0)
aws-sigv4 (~> 1.5)
aws-sdk-rds (1.292.0)
aws-sdk-rds (1.293.0)
aws-sdk-core (~> 3, >= 3.231.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.199.0)
Expand Down Expand Up @@ -456,7 +456,7 @@ GEM
public_suffix (6.0.2)
puma (7.0.2)
nio4r (~> 2.0)
pundit (2.5.0)
pundit (2.5.1)
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.8.1)
Expand All @@ -477,6 +477,9 @@ GEM
rack (>= 3.0.0)
rack-test (2.2.0)
rack (>= 1.3)
rack_session_access (0.2.0)
builder (>= 2.0.0)
rack (>= 1.0.0)
rackup (2.2.1)
rack (>= 3)
rails (8.0.2.1)
Expand Down Expand Up @@ -820,6 +823,7 @@ DEPENDENCIES
pry-rails
puma
pundit
rack_session_access
rails (~> 8.0.2)
rails_semantic_logger
rainbow
Expand Down
5 changes: 5 additions & 0 deletions app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {

import { Autocomplete } from "./components/autocomplete.js";
import { UpgradedRadios as Radios } from "./components/radios.js";
import { Sticky } from "./components/sticky.js";

// Configure Turbo
Turbo.session.drive = false;
Expand Down Expand Up @@ -41,6 +42,10 @@ function initialiseComponents() {
createAll(Autocomplete);
}

if (!isInitialised("app-sticky")) {
createAll(Sticky);
}

if (!isInitialised("nhsuk-button")) {
createAll(Button, { preventDoubleClick: true });
}
Expand Down
1 change: 0 additions & 1 deletion app/assets/javascripts/components/autocomplete.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ describe("Autocomplete", () => {

// Check that matching options are shown
const visibleOptions = listbox.querySelectorAll("li");
console.log(visibleOptions[0].innerHTML.trim());
expect(visibleOptions.length).toEqual(1);

// Option display hint text
Expand Down
63 changes: 63 additions & 0 deletions app/assets/javascripts/components/sticky.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Component } from "nhsuk-frontend";

/**
* Sticky component
*/
export class Sticky extends Component {
/**
* @param {Element | null} $root - HTML element to use for component
*/
constructor($root) {
super($root);

this.stickyElement = $root;
this.stickyElementStyle = null;
this.stickyElementTop = 0;

this.determineStickyState = this.determineStickyState.bind(this);
this.throttledStickyState = this.throttle(this.determineStickyState, 100);

this.stickyElementStyle = window.getComputedStyle($root);
this.stickyElementTop = parseInt(this.stickyElementStyle.top, 10);

window.addEventListener("scroll", this.throttledStickyState);

this.determineStickyState();
}

/**
* Name for the component used when initialising using data-module attributes
*/
static moduleName = "app-sticky";

/**
* Determine element’s sticky state
*/
determineStickyState() {
const currentTop = this.stickyElement.getBoundingClientRect().top;

this.stickyElement.dataset.stuck = String(
currentTop <= this.stickyElementTop,
);
}

/**
* Throttle
*
* @param {Function} callback - Function to throttle
* @param {number} limit - Minimum time interval (in milliseconds)
* @returns {Function} Throttled function
*/
throttle(callback, limit) {
let inThrottle;
return function () {
const args = arguments;
const context = this;
if (!inThrottle) {
callback.apply(context, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
}
194 changes: 194 additions & 0 deletions app/assets/javascripts/components/sticky.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { Sticky } from "./sticky.js";

describe("Sticky Component", () => {
let mockElement;
let stickyInstance;
let scrollEventListener;

beforeEach(() => {
document.body.classList.add("nhsuk-frontend-supported");

// Create a mock DOM element
document.body.innerHTML = `<div id="mock-element" style="position: sticky; top: 20px;"></div>`;
mockElement = document.getElementById("mock-element");

// Mock getBoundingClientRect
Object.defineProperty(mockElement, "getBoundingClientRect", {
value: jest.fn(() => ({ top: 0 })),
configurable: true,
});

// Mock window.getComputedStyle
Object.defineProperty(window, "getComputedStyle", {
value: jest.fn(() => ({
top: "20px",
})),
writable: true,
});

// Mock window.addEventListener and capture scroll listener
const originalAddEventListener = window.addEventListener;
window.addEventListener = jest.fn((event, listener) => {
if (event === "scroll") {
scrollEventListener = listener;
}
originalAddEventListener.call(window, event, listener);
});
});

afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});

describe("Initialization", () => {
test("should initialize with correct properties", () => {
stickyInstance = new Sticky(mockElement);

expect(stickyInstance.stickyElement).toBe(mockElement);
expect(stickyInstance.stickyElementTop).toBe(20);
expect(window.getComputedStyle).toHaveBeenCalledWith(mockElement);
expect(window.addEventListener).toHaveBeenCalledWith(
"scroll",
expect.any(Function),
);
});

test("should have correct moduleName", () => {
expect(Sticky.moduleName).toBe("app-sticky");
});

test("should call determineStickyState on initialization", () => {
const spy = jest.spyOn(Sticky.prototype, "determineStickyState");
stickyInstance = new Sticky(mockElement);
expect(spy).toHaveBeenCalled();
});
});

describe("determineStickyState method", () => {
beforeEach(() => {
stickyInstance = new Sticky(mockElement);
});

test("should set data-stuck to `true` when element is at or above threshold", () => {
// Element is at the top (currentTop = 0, threshold = 20)
mockElement.getBoundingClientRect.mockReturnValue({ top: 0 });

stickyInstance.determineStickyState();

expect(mockElement.dataset.stuck).toBe("true");
});

test("should set data-stuck to `true` when element above threshold", () => {
mockElement.getBoundingClientRect.mockReturnValue({ top: 10 });

stickyInstance.determineStickyState();

expect(mockElement.dataset.stuck).toBe("true");
});

test("should set data-stuck to `true` when element at threshold", () => {
mockElement.getBoundingClientRect.mockReturnValue({ top: 20 });

stickyInstance.determineStickyState();

expect(mockElement.dataset.stuck).toBe("true");
});

test("should set data-stuck to `false` when element below threshold", () => {
mockElement.getBoundingClientRect.mockReturnValue({ top: 30 });

stickyInstance.determineStickyState();

expect(mockElement.dataset.stuck).toBe("false");
});
});

describe("Scroll behavior", () => {
beforeEach(() => {
jest.useFakeTimers();

// Clear any existing event listeners
window.removeEventListener = jest.fn();

stickyInstance = new Sticky(mockElement);
});

afterEach(() => {
jest.useRealTimers();
});

test("should respond to scroll events", () => {
mockElement.getBoundingClientRect.mockReturnValue({ top: 10 });

// Make sure we have the listener
expect(scrollEventListener).toBeDefined();

// Trigger scroll event
scrollEventListener();

expect(mockElement.dataset.stuck).toBe("true");
});
});

describe("Throttle functionality", () => {
beforeEach(() => {
jest.useFakeTimers();
stickyInstance = new Sticky(mockElement);
});

afterEach(() => {
jest.useRealTimers();
});

test("should throttle function calls", () => {
const mockCallback = jest.fn();
const throttledCallback = stickyInstance.throttle(mockCallback, 100);

// Call multiple times rapidly
throttledCallback();
throttledCallback();
throttledCallback();

// Should only be called once
expect(mockCallback).toHaveBeenCalledTimes(1);

// Fast forward past throttle limit
jest.advanceTimersByTime(100);

// Now should allow another call
throttledCallback();
expect(mockCallback).toHaveBeenCalledTimes(2);
});

test("should preserve context and arguments in throttled function", () => {
const mockCallback = jest.fn();
const throttledCallback = stickyInstance.throttle(mockCallback, 100);

throttledCallback("arg1", "arg2");

expect(mockCallback).toHaveBeenCalledWith("arg1", "arg2");
});
});

describe("Integration with different CSS top values", () => {
test("should handle different top values correctly", () => {
// Mock different computed style top value
window.getComputedStyle.mockReturnValue({ top: "50px" });

stickyInstance = new Sticky(mockElement);

expect(stickyInstance.stickyElementTop).toBe(50);

// Test with element above new threshold
mockElement.getBoundingClientRect.mockReturnValue({ top: 30 });
stickyInstance.determineStickyState();
expect(mockElement.dataset.stuck).toBe("true");

// Test with element below new threshold
mockElement.getBoundingClientRect.mockReturnValue({ top: 60 });
stickyInstance.determineStickyState();
expect(mockElement.dataset.stuck).toBe("false");
});
});
});
17 changes: 15 additions & 2 deletions app/assets/stylesheets/components/_button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,24 @@ $_secondary-warning-button-hover-colour: color.change(
$alpha: 0.1
);

.button_to {
display: contents;
}

.nhsuk-button {
.button_to &,
.nhsuk-table & {
.button_to & {
margin-bottom: 0;
}

.nhsuk-button-group .button_to & {
$horizontal-gap: nhsuk-spacing(4);
$vertical-gap: nhsuk-spacing(3);
margin-bottom: $vertical-gap + $_button-shadow-size;

@include nhsuk-media-query($from: tablet) {
margin-right: $horizontal-gap;
}
}
}

.app-button--secondary-warning {
Expand Down
4 changes: 0 additions & 4 deletions app/assets/stylesheets/components/_card.scss
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,6 @@
.app-card--compact {
@include nhsuk-responsive-margin(3, "bottom");

.nhsuk-button-group {
margin-top: nhsuk-spacing(-4);
}

.nhsuk-card__heading {
@include nhsuk-responsive-margin(1, "bottom");
}
Expand Down
1 change: 1 addition & 0 deletions app/assets/stylesheets/components/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
@forward "highlight";
@forward "search-input";
@forward "secondary-navigation";
@forward "sticky-navigation";
@forward "status";
@forward "summary-list";
@forward "tables";
Expand Down
Loading