Skip to content

Commit 47d8620

Browse files
authored
Merge branch 'develop' into snyk-fix-c6b6942cc217110f0c83ca28d48b1d21
2 parents 857e5ab + 225402b commit 47d8620

42 files changed

Lines changed: 433 additions & 102 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.rubocop.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ Metrics/AbcSize:
122122
- 'app/controllers/admin/suppliers_controller.rb'
123123
- 'app/controllers/v1/tasks_controller.rb'
124124
- 'app/controllers/admin/users_controller.rb'
125+
- 'app/jobs/cleanup_submission_entries_job.rb'
125126

126127
Layout/LineLength:
127128
Max: 120

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# Change Log
22

3+
## [release-171] - 2025-10-23
4+
5+
- NRMI-178: Clean up non-active validation_failed entries job
6+
7+
## [release-170] - 2025-10-13
8+
9+
- NRMI-143: fix unpermitted aria attribute accessibility error
10+
- NRMI-152: QA observation of inconsistent banner colours
11+
- NRMI-172: set up github actions
12+
- NRMI-166: Add archived agreement status
13+
- NRMI-177: Delete validation failed submission entries when resolved
14+
315
## [release-169] - 2025-08-18
416

517
- NRMI-153: fix for overflowing supplier page table
@@ -1121,6 +1133,8 @@ this should have been released in release 45 but wasn't actually merged
11211133

11221134
Initial release
11231135

1136+
[release-171]: https://github.com/Crown-Commercial-Service/DataSubmissionServiceAPI/compare/release-170...release-171
1137+
[release-170]: https://github.com/Crown-Commercial-Service/DataSubmissionServiceAPI/compare/release-169...release-170
11241138
[release-169]: https://github.com/Crown-Commercial-Service/DataSubmissionServiceAPI/compare/release-168...release-169
11251139
[release-168]: https://github.com/Crown-Commercial-Service/DataSubmissionServiceAPI/compare/release-167...release-168
11261140
[release-167]: https://github.com/Crown-Commercial-Service/DataSubmissionServiceAPI/compare/release-166...release-167

Gemfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ gem 'email_validator', require: 'email_validator/strict'
1717

1818
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
1919
# gem 'jbuilder', '~> 2.7', '>= 2.7.0'
20-
gem 'jbuilder', '~> 2.12', '>= 2.12.0'
20+
gem 'jbuilder', '~> 2.13', '>= 2.13.0'
2121

2222
# JSON API
2323
gem 'jsonapi-rails'
@@ -77,7 +77,7 @@ gem 'sidekiq-cron', '>= 2.3.1'
7777
# Alpine and Windows do not include zoneinfo files, so bundle the tzinfo-data gem
7878
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby ruby]
7979

80-
gem 'activerecord-import', '>= 1.5.0'
80+
gem 'activerecord-import', '>= 1.5.1'
8181

8282
gem 'parslet'
8383

@@ -128,7 +128,7 @@ group :test do
128128
gem 'jsonapi-rspec', require: false
129129
gem 'ruby_event_store-rspec'
130130
gem 'rspec-json_expectations'
131-
gem 'shoulda-matchers', '~> 5.1', '>= 5.1.0'
131+
gem 'shoulda-matchers', '~> 6.0', '>= 6.0.0'
132132
gem 'capybara', '~> 3.40', '>= 3.40.0'
133133
gem 'climate_control'
134134
gem 'webmock', '>= 3.20.0'

Gemfile.lock

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ GEM
6161
activemodel (= 8.0.3)
6262
activesupport (= 8.0.3)
6363
timeout (>= 0.4.0)
64-
activerecord-import (1.5.0)
64+
activerecord-import (2.2.0)
6565
activerecord (>= 4.2)
6666
activestorage (8.0.3)
6767
actionpack (= 8.0.3)
@@ -128,7 +128,7 @@ GEM
128128
nokogiri (~> 1, >= 1.10.8)
129129
base64 (0.3.0)
130130
benchmark (0.4.1)
131-
bigdecimal (3.3.0)
131+
bigdecimal (3.3.1)
132132
bootsnap (1.11.1)
133133
msgpack (~> 1.2)
134134
brakeman (4.3.1)
@@ -173,7 +173,7 @@ GEM
173173
drb (2.2.3)
174174
email_validator (2.2.4)
175175
activemodel
176-
erb (5.0.3)
176+
erb (5.1.1)
177177
erubi (1.13.1)
178178
et-orbi (1.4.0)
179179
tzinfo
@@ -212,7 +212,7 @@ GEM
212212
ffi-compiler (1.0.1)
213213
ffi (>= 1.0.0)
214214
rake
215-
fugit (1.12.0)
215+
fugit (1.12.1)
216216
et-orbi (~> 1.4)
217217
raabro (~> 1.4)
218218
globalid (1.3.0)
@@ -248,9 +248,9 @@ GEM
248248
pp (>= 0.6.0)
249249
rdoc (>= 4.0.0)
250250
reline (>= 0.4.2)
251-
jbuilder (2.12.0)
252-
actionview (>= 5.0.0)
253-
activesupport (>= 5.0.0)
251+
jbuilder (2.14.1)
252+
actionview (>= 7.0.0)
253+
activesupport (>= 7.0.0)
254254
jmespath (1.6.1)
255255
jquery-rails (4.6.0)
256256
rails-dom-testing (>= 1, < 3)
@@ -375,7 +375,7 @@ GEM
375375
pp (0.6.3)
376376
prettyprint
377377
prettyprint (0.2.0)
378-
prism (1.5.1)
378+
prism (1.5.2)
379379
progress_bar (1.2.0)
380380
highline (~> 1.6)
381381
options (~> 2.3.0)
@@ -392,11 +392,11 @@ GEM
392392
nio4r (~> 2.0)
393393
raabro (1.4.0)
394394
racc (1.8.1)
395-
rack (3.2.2)
395+
rack (3.2.3)
396396
rack-maintenance (3.0.0)
397397
rack (>= 2.1.4)
398398
rake (>= 12.3.3)
399-
rack-protection (4.1.1)
399+
rack-protection (4.2.1)
400400
base64 (>= 0.1.0)
401401
logger (>= 1.6.0)
402402
rack (>= 3.0.0, < 4)
@@ -483,7 +483,7 @@ GEM
483483
diff-lcs (>= 1.2.0, < 2.0)
484484
rspec-support (~> 3.13.0)
485485
rspec-json_expectations (2.1.0)
486-
rspec-mocks (3.13.5)
486+
rspec-mocks (3.13.6)
487487
diff-lcs (>= 1.2.0, < 2.0)
488488
rspec-support (~> 3.13.0)
489489
rspec-rails (7.1.1)
@@ -542,7 +542,7 @@ GEM
542542
sprockets-rails
543543
tilt
544544
securerandom (0.4.1)
545-
shoulda-matchers (5.3.0)
545+
shoulda-matchers (6.5.0)
546546
activesupport (>= 5.2.0)
547547
sidekiq (8.0.8)
548548
connection_pool (>= 2.5.0)
@@ -618,7 +618,7 @@ PLATFORMS
618618

619619
DEPENDENCIES
620620
aasm
621-
activerecord-import (>= 1.5.0)
621+
activerecord-import (>= 1.5.1)
622622
akami (>= 1.3.2)
623623
auth0 (~> 5.1, >= 5.1.2)
624624
aws-sdk-cloudwatch
@@ -637,7 +637,7 @@ DEPENDENCIES
637637
haml-rails (>= 3.0.0)
638638
hashdiff
639639
http (>= 4.0.0)
640-
jbuilder (~> 2.12, >= 2.12.0)
640+
jbuilder (~> 2.13, >= 2.13.0)
641641
jquery-rails (>= 4.5.0)
642642
jsonapi-rails
643643
jsonapi-rspec
@@ -669,7 +669,7 @@ DEPENDENCIES
669669
rubyXL (~> 3.4, >= 3.4.26)
670670
ruby_event_store-rspec
671671
sass-rails (~> 6.0, >= 6.0.0)
672-
shoulda-matchers (~> 5.1, >= 5.1.0)
672+
shoulda-matchers (~> 6.0, >= 6.0.0)
673673
sidekiq-cron (>= 2.3.1)
674674
sidekiq-pro!
675675
simple_form (>= 5.3.1)

app/controllers/admin/frameworks_controller.rb

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
class Admin::FrameworksController < AdminController
2-
before_action :find_framework, only: %i[show edit update update_fdl publish download_template]
2+
# rubocop:disable Layout/LineLength
3+
before_action :find_framework,
4+
only: %i[show edit update update_fdl publish download_template archive_confirmation archive unarchive_confirmation
5+
unarchive]
36
before_action :prevent_republish, only: :publish
7+
# rubocop:enable Layout/LineLength
48

59
def index
610
@frameworks = Framework.order(:short_name).all
@@ -74,6 +78,30 @@ def publish
7478
redirect_to admin_framework_path(@framework)
7579
end
7680

81+
def archive_confirmation; end
82+
83+
def archive
84+
if @framework.can_be_archived?
85+
@framework.archive!
86+
flash[:success] = 'Framework archived successfully.'
87+
else
88+
flash[:failure] = 'Framework cannot be archived. Ensure it is published and has no active agreements.'
89+
end
90+
redirect_to admin_framework_path(@framework)
91+
end
92+
93+
def unarchive_confirmation; end
94+
95+
def unarchive
96+
if @framework.archived?
97+
@framework.publish!
98+
flash[:success] = 'Framework unarchived successfully.'
99+
else
100+
flash[:failure] = 'Error unarchiving framework.'
101+
end
102+
redirect_to admin_framework_path(@framework)
103+
end
104+
77105
def download_template
78106
resp = s3_client.get_object(bucket: bucket, key: @framework.file_key)
79107
send_data resp.body.read, filename: "#{@framework.full_name} Template#{File.extname(@framework.file_name.to_s)}"
@@ -84,7 +112,7 @@ def download_template
84112
def filter_framework_status(status_param)
85113
return if status_param.size == 2
86114

87-
@frameworks = @frameworks.unpublished if status_param.include? 'New'
115+
@frameworks = @frameworks.new_state if status_param.include? 'New'
88116
@frameworks = @frameworks.published if status_param.include? 'Published'
89117
end
90118

app/helpers/frameworks_helper.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
module FrameworksHelper
22
def published_status(framework)
3-
framework.published ? 'Published' : 'New'
3+
framework.aasm_state
4+
end
5+
6+
def framework_not_archived?(framework)
7+
framework.aasm_state != 'archived'
48
end
59
end
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
class CleanupSubmissionEntriesJob < ApplicationJob
2+
ENTRIES_BATCH_SIZE = 1000
3+
4+
# rubocop:disable Metrics/CyclomaticComplexity
5+
# rubocop:disable Metrics/PerceivedComplexity
6+
def perform(dry_run: false, max_run_time: 5.hours, task_batch_sixe: 100)
7+
start_time = Time.current
8+
total_deleted_entries = 0
9+
10+
tasks_with_unprocessed_submissions = Task.joins(:submissions)
11+
.where(submissions: { aasm_state: 'validation_failed',
12+
cleanup_processed: false })
13+
.distinct
14+
15+
tasks_with_unprocessed_submissions.find_in_batches(batch_size: task_batch_sixe) do |tasks_batch|
16+
tasks_batch.each do |task|
17+
active_id = task.active_submission&.id
18+
19+
failed_submissions = task.submissions
20+
.where(aasm_state: 'validation_failed', cleanup_processed: false)
21+
.where.not(id: active_id)
22+
23+
next if failed_submissions.empty?
24+
25+
deleted_for_task = 0
26+
27+
failed_submissions.find_each do |submission|
28+
begin
29+
submission.entries.in_batches(of: ENTRIES_BATCH_SIZE) do |entries_batch|
30+
if dry_run
31+
Rollbar.info("Dry run: would delete #{entries_batch.count} entries for Submission ID #{submission.id}.")
32+
else
33+
deleted_count = entries_batch.delete_all
34+
deleted_for_task += deleted_count
35+
total_deleted_entries += deleted_count
36+
# rubocop:disable Layout/LineLength
37+
Rollbar.info("Task ID #{task.id}: Processed #{failed_submissions.count} failed submissions, deleted #{deleted_for_task} entries.")
38+
# rubocop:enable Layout/LineLength
39+
end
40+
end
41+
42+
submission.update(cleanup_processed: true) unless dry_run
43+
rescue StandardError => e
44+
Rollbar.error(e, "Error processing Submission ID #{submission.id} for Task ID #{task.id}: #{e.message}")
45+
end
46+
47+
break if Time.current - start_time >= max_run_time
48+
end
49+
end
50+
51+
break if Time.current - start_time >= max_run_time
52+
53+
sleep 1 # To avoid overwhelming the database
54+
end
55+
end
56+
# rubocop:enable Metrics/CyclomaticComplexity
57+
# rubocop:enable Metrics/PerceivedComplexity
58+
end

app/models/framework.rb

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
11
class Framework < ApplicationRecord
2+
include AASM
3+
4+
aasm column: 'aasm_state' do
5+
state :new, initial: true
6+
state :published
7+
state :archived
8+
9+
event :publish do
10+
transitions from: %i[new archived], to: :published, guard: :load_lots!
11+
end
12+
event :archive do
13+
transitions from: %i[published], to: :archived
14+
end
15+
end
16+
217
has_many :lots, dependent: :nullify, class_name: 'FrameworkLot'
318
has_many :submissions, dependent: :nullify
419

@@ -10,8 +25,9 @@ class Framework < ApplicationRecord
1025

1126
validates :definition_source, fdl: true, allow_nil: true
1227

13-
scope :published, -> { where(published: true) }
14-
scope :unpublished, -> { where(published: false) }
28+
scope :published, -> { where(aasm_state: 'published') }
29+
scope :archived, -> { where(aasm_state: 'archived') }
30+
scope :new_state, -> { where(aasm_state: 'new') }
1531

1632
has_one_attached :template_file
1733

@@ -63,11 +79,6 @@ def update_from_fdl(definition_source)
6379
end
6480
end
6581

66-
def publish!
67-
load_lots!
68-
update(published: true)
69-
end
70-
7182
def load_lots!
7283
generator = Framework::Definition::Generator.new(definition_source, Rails.logger)
7384
fdl_lots = generator.definition.lots || {}
@@ -95,4 +106,8 @@ def lot_has_suppliers_onboarded?(definition_source)
95106
update(definition_source: definition_source)
96107
end
97108
end
109+
110+
def can_be_archived?
111+
published? && agreements.active.none?
112+
end
98113
end

app/models/submission.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
class Submission < ApplicationRecord
22
ERRORED_ROW_LIMIT = 10
3+
REPLACEMENT_STATES = %i[validation_failed ingest_failed in_review completed].freeze
4+
5+
after_save :cleanup_prev_failed_entries, if: :state_changed_to_replacement?
36

47
include AASM
58

@@ -123,6 +126,17 @@ def call_workday(workday_reference)
123126

124127
private
125128

129+
def cleanup_prev_failed_entries
130+
task.submissions.where(aasm_state: 'validation_failed').where.not(id: id).find_each do |s|
131+
s.entries.destroy_all
132+
s.staging_entries.destroy_all
133+
end
134+
end
135+
136+
def state_changed_to_replacement?
137+
saved_change_to_aasm_state? && REPLACEMENT_STATES.include?(aasm_state.to_sym)
138+
end
139+
126140
def enqueue_reversal_invoice_creation_job(user)
127141
SubmissionReversalInvoiceCreationJob.perform_later(self, user)
128142
end
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.govuk-grid-row
2+
.govuk-grid-column-two-thirds
3+
= link_to 'Back', admin_framework_path(@framework), { class: 'govuk-back-link govuk-!-margin-bottom-5', title: 'Back to agreement' }
4+
5+
%h2.govuk-heading-l
6+
Are you sure you want to archive
7+
= "#{@framework.short_name} #{@framework.name}?"
8+
9+
= form_with url: archive_admin_framework_path(@framework), method: :post do |f|
10+
= f.submit "Confirm", class: "govuk-button"

0 commit comments

Comments
 (0)