From 57c1421f12a20a35e563bfc29e02acfa182e76aa Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Mon, 27 Oct 2025 15:52:44 +0000 Subject: [PATCH 01/92] docs: initial diagram --- docs/accessioning.mermaid | 206 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 docs/accessioning.mermaid diff --git a/docs/accessioning.mermaid b/docs/accessioning.mermaid new file mode 100644 index 0000000000..2a8246d42e --- /dev/null +++ b/docs/accessioning.mermaid @@ -0,0 +1,206 @@ +--- +title: Accessioning Dataflow +--- +%%{ init: { + 'flowchart': { 'curve': 'curvy' }, + 'theme': 'default' + } +}%% +flowchart LR + %% Legend + subgraph Legend [Legend] + direction TB + L_User(fa:fa-user User) + L_External(fa:fa-globe External System) + L_API(fa:fa-arrow-right-to-bracket API) + L_Interface(fa:fa-computer-mouse User Interface) + L_Model(fa:fa-square-caret-down Model) + L_Validation(fa:fa-check Validation) + L_Controller(fa:fa-arrows-spin Controller) + L_Function(fa:fa-caret-right Function) + L_Config(fa:fa-screwdriver-wrench Configuration) + L_Resource(fa:fa-file Resource) + L_Library(fa:fa-book Library) + L_Async(fa:fa-clock Asynchronous Process) + + L_User ~~~ L_External ~~~ L_API ~~~ L_Interface ~~~ L_Resource ~~~ L_Async + L_Config ~~~ L_Controller ~~~ L_Model ~~~ L_Validation ~~~ L_Function ~~~ L_Library + + InvisibleNodeA[ ] -. Config flow .-> InvisibleNodeB[ ] -- Process flow --> InvisibleNodeC[ ] == Accessioning path ==> InvisibleNodeD[ ] + end + + Legend ~~~ Providers + %% End Legend + + %% Nodes + %% Users + User_SeqOps(fa:fa-user SeqOps) + User_Neil(fa:fa-user Neil) + User_SSR(fa:fa-user SSRs) + User_LB_Users(fa:fa-user LB Users) + User_Developers(fa:fa-user Developers) + User_SS_Users(fa:fa-user SS Users) + External_EBI(fa:fa-globe EBI) + + %% User Interfaces + UI_SS_Sample_GAN(fa:fa-computer-mouse Generate Accession Number) + UI_SS_Study_GAN(fa:fa-computer-mouse Generate Accession Number) + UI_SS_Study_AAS(fa:fa-computer-mouse Accession all Samples) + UI_SS_Manifest_Upload(fa:fa-computer-mouse Manifest Upload) + + %% Models + MD_SS_Order(fa:fa-square-caret-down Order) + %% MD_SS_AccessionService(fa:fa-square-caret-down AccessionService) + MD_SS_Submission_AccessionBehaviour(fa:fa-square-caret-down Submission::AccessionBehaviour) + %% MD_SS_Sample(fa:fa-square-caret-down Sample) + %% MD_SS_Study(fa:fa-square-caret-down Study) + MD_SS_SampleManifest_Uploader(fa:fa-square-caret-down SampleManifest::Uploader) + + %% Controllers + CT_SS_Samples(fa:fa-arrows-spin Samples Controller) + %% CT_SS_Studies(fa:fa-arrows-spin Studies Controller) + + %% Functions + FN_SS_AccessionService_submit_sample_for_user(fa:fa-caret-right submit_sample_for_user) + FN_SS_AccessionService_submit_study_for_user(fa:fa-caret-right submit_study_for_user) + FN_SS_AccessionService_submit(fa:fa-caret-right submit) + FN_SS_Sample_accession(fa:fa-caret-right accession) + FN_SS_Sample_validate_accessionable(fa:fa-caret-right validate_accessionable!) + FN_SS_Study_accession_all_samples(fa:fa-caret-right accession_all_samples) + FN_SS_Studies_accession_all_samples(fa:fa-caret-right accession_all_samples) + FN_SS_Studies_accession(fa:fa-caret-right accession) + FN_SS_Studies_validate_ena_required_fields(fa:fa-caret-right validate_ena_required_fields!) + + %% Other Components + API_SS_OrderResource(fa:fa-arrow-right-to-bracket Order Resource) + CP_SS_Delayed_Job_Accessioning(fa:fa-clock DelayedJob::SampleAccessioningJob) + CP_SS_AccessionSubmission(fa:fa-book Accession::Submission) + CP_SS_SampleManifestExcel_Upload(SampleManifestExcel::Upload) + + %% Config + CF_SS_accession_samples(fa:fa-screwdriver-wrench accession_samples) + CF_SS_disable_accession_check(fa:fa-screwdriver-wrench disable_accession_check) + + %% Resources + RES_Manifest(fa:fa-file Manifest) + RES_Labware(Charge and Pass) + + %% Groupings of nodes + subgraph Providers + User_SeqOps + User_Neil + User_SSR + end + subgraph Limber + RES_Labware + end + subgraph Sequencescape + CF_SS_disable_accession_check + CF_SS_accession_samples + CP_SS_Delayed_Job_Accessioning + CP_SS_SampleManifestExcel_Upload + CP_SS_AccessionSubmission + MD_SS_Order + MD_SS_SampleManifest_Uploader + UI_SS_Manifest_Upload + + subgraph SS_API["SS API"] + API_SS_OrderResource + end + subgraph Samples + UI_SS_Sample_GAN + CT_SS_Samples + subgraph MD_SS_Sample[fa:fa-square-caret-down Sample] + FN_SS_Sample_accession + FN_SS_Sample_validate_accessionable + end + end + subgraph Studies + UI_SS_Study_GAN + UI_SS_Study_AAS + subgraph CT_SS_Studies[fa:fa-arrows-spin Studies Controller] + FN_SS_Studies_accession_all_samples + FN_SS_Studies_accession + FN_SS_Studies_validate_ena_required_fields + end + subgraph MD_SS_Study[fa:fa-square-caret-down Study] + FN_SS_Study_accession_all_samples + end + end + subgraph Submissions + MD_SS_Submission_AccessionBehaviour + end + subgraph MD_SS_AccessionService[fa:fa-square-caret-down AccessionService] + FN_SS_AccessionService_submit_sample_for_user + FN_SS_AccessionService_submit_study_for_user + FN_SS_AccessionService_submit + end + end + subgraph Consumers + User_LB_Users + User_SS_Users + User_Developers + end + + %% Edge connections between nodes + + %% Limber-related + User_SeqOps ---> RES_Labware --> User_LB_Users + RES_Labware --> API_SS_OrderResource --> MD_SS_Order + MD_SS_Order ---> MD_SS_Submission_AccessionBehaviour + CF_SS_disable_accession_check -.-> MD_SS_Submission_AccessionBehaviour + MD_SS_Submission_AccessionBehaviour -- exception email --> User_Developers + + %% Manifest upload + User_SSR --> RES_Manifest --> UI_SS_Manifest_Upload --> MD_SS_SampleManifest_Uploader --> CP_SS_SampleManifestExcel_Upload ---> FN_SS_Sample_accession + + FN_SS_Sample_validate_accessionable == > NO FEEDBACK > ==> CP_SS_Delayed_Job_Accessioning + CP_SS_Delayed_Job_Accessioning -- exception email --> User_Developers + CP_SS_AccessionSubmission -- exception email --> User_Developers + CP_SS_Delayed_Job_Accessioning <==> CP_SS_AccessionSubmission <==> External_EBI + + %% Sample generate accession number + User_Neil --> UI_SS_Sample_GAN + CF_SS_accession_samples -.-> CT_SS_Samples + UI_SS_Sample_GAN --> CT_SS_Samples + CT_SS_Samples <==> FN_SS_AccessionService_submit_sample_for_user <==> FN_SS_AccessionService_submit <==> External_EBI + CT_SS_Samples -- flash message --> User_SS_Users + + %% Study accession all samples + User_SSR --> UI_SS_Study_AAS + User_Neil --> UI_SS_Study_AAS + UI_SS_Study_AAS --> FN_SS_Studies_accession_all_samples + FN_SS_Studies_accession_all_samples <--> FN_SS_Study_accession_all_samples + FN_SS_Studies_accession_all_samples -- flash message --> User_SS_Users + FN_SS_Study_accession_all_samples <---> FN_SS_Sample_accession + FN_SS_Sample_accession <--> FN_SS_Sample_validate_accessionable + CF_SS_accession_samples -.-> FN_SS_Sample_accession + + %% Study generate accession number + User_Neil --> UI_SS_Study_GAN + UI_SS_Study_GAN --> FN_SS_Studies_accession --> FN_SS_Studies_validate_ena_required_fields + FN_SS_Studies_validate_ena_required_fields -- flash message --> User_SS_Users + CF_SS_accession_samples -.-> FN_SS_AccessionService_submit_study_for_user <==> FN_SS_AccessionService_submit + FN_SS_Studies_validate_ena_required_fields <--> FN_SS_AccessionService_submit_study_for_user + + %% User_Developers -. slack / email .-> User_SS_Users + + %% Subgraph styling + classDef invisible fill:transparent,stroke:transparent; + classDef legendTransparent fill:transparent,stroke:#333,stroke-width:1px; + classDef Application fill:#adecff; + classDef Configuration fill:#fffbad; + classDef Sequencescape fill:#adccf6; + classDef SequencescapeSection fill:#ADDCFB; + classDef SequencescapeObject fill:#ADACEC; + classDef Users fill:#FFD6F1; + + class InvisibleNodeA,InvisibleNodeB,InvisibleNodeC,InvisibleNodeD invisible; + class Legend legendTransparent; + class Providers,Consumers Users; + class Limber Application; + class Sequencescape Sequencescape; + class L_Config,CF_SS_disable_accession_check,CF_SS_accession_samples Configuration; + class SS_API,DelayedJob,Samples,Studies,Submissions SequencescapeSection; + class MD_SS_Study,MD_SS_AccessionService,MD_SS_Sample,CT_SS_Studies SequencescapeObject; + class Providers,Consumers Users; From e371f3f0ea73356baada492e6ae3f3e5136303b2 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 28 Oct 2025 14:31:21 +0000 Subject: [PATCH 02/92] docs: update diagram --- docs/accessioning.mermaid | 181 +++++++++++++++++++++++--------------- 1 file changed, 110 insertions(+), 71 deletions(-) diff --git a/docs/accessioning.mermaid b/docs/accessioning.mermaid index 2a8246d42e..eb764603dd 100644 --- a/docs/accessioning.mermaid +++ b/docs/accessioning.mermaid @@ -1,9 +1,9 @@ --- -title: Accessioning Dataflow +title: Accessioning Call Graph --- %%{ init: { 'flowchart': { 'curve': 'curvy' }, - 'theme': 'default' + 'theme': 'neutral' } }%% flowchart LR @@ -26,7 +26,7 @@ flowchart LR L_User ~~~ L_External ~~~ L_API ~~~ L_Interface ~~~ L_Resource ~~~ L_Async L_Config ~~~ L_Controller ~~~ L_Model ~~~ L_Validation ~~~ L_Function ~~~ L_Library - InvisibleNodeA[ ] -. Config flow .-> InvisibleNodeB[ ] -- Process flow --> InvisibleNodeC[ ] == Accessioning path ==> InvisibleNodeD[ ] + InvisibleNodeA[ ] -..-> | Data flow | InvisibleNodeB[ ] -- Function calls --> InvisibleNodeC[ ] == Accessioning function calls ==> InvisibleNodeD[ ] end Legend ~~~ Providers @@ -43,6 +43,7 @@ flowchart LR External_EBI(fa:fa-globe EBI) %% User Interfaces + UI_LB_Charge_and_Pass(fa:fa-computer-mouse Charge and Pass) UI_SS_Sample_GAN(fa:fa-computer-mouse Generate Accession Number) UI_SS_Study_GAN(fa:fa-computer-mouse Generate Accession Number) UI_SS_Study_AAS(fa:fa-computer-mouse Accession all Samples) @@ -57,7 +58,7 @@ flowchart LR MD_SS_SampleManifest_Uploader(fa:fa-square-caret-down SampleManifest::Uploader) %% Controllers - CT_SS_Samples(fa:fa-arrows-spin Samples Controller) + %% CT_SS_Samples(fa:fa-arrows-spin Samples Controller) %% CT_SS_Studies(fa:fa-arrows-spin Studies Controller) %% Functions @@ -66,15 +67,19 @@ flowchart LR FN_SS_AccessionService_submit(fa:fa-caret-right submit) FN_SS_Sample_accession(fa:fa-caret-right accession) FN_SS_Sample_validate_accessionable(fa:fa-caret-right validate_accessionable!) + FN_SS_Sample_validate_ena_required_fields(fa:fa-caret-right validate_ena_required_fields!) + FN_SS_Samples_accession(fa:fa-caret-right accession) FN_SS_Study_accession_all_samples(fa:fa-caret-right accession_all_samples) FN_SS_Studies_accession_all_samples(fa:fa-caret-right accession_all_samples) FN_SS_Studies_accession(fa:fa-caret-right accession) FN_SS_Studies_validate_ena_required_fields(fa:fa-caret-right validate_ena_required_fields!) + FN_SS_Studies_rescue_accession_errors(fa:fa-caret-right rescue_accession_errors) %% Other Components API_SS_OrderResource(fa:fa-arrow-right-to-bracket Order Resource) CP_SS_Delayed_Job_Accessioning(fa:fa-clock DelayedJob::SampleAccessioningJob) CP_SS_AccessionSubmission(fa:fa-book Accession::Submission) + CP_SS_AccessionRequest(fa:fa-book Accession::Request) CP_SS_SampleManifestExcel_Upload(SampleManifestExcel::Upload) %% Config @@ -83,7 +88,6 @@ flowchart LR %% Resources RES_Manifest(fa:fa-file Manifest) - RES_Labware(Charge and Pass) %% Groupings of nodes subgraph Providers @@ -91,50 +95,55 @@ flowchart LR User_Neil User_SSR end - subgraph Limber - RES_Labware + subgraph Application_Limber + UI_LB_Charge_and_Pass end - subgraph Sequencescape - CF_SS_disable_accession_check - CF_SS_accession_samples - CP_SS_Delayed_Job_Accessioning + subgraph Application_Sequencescape + UI_SS_Manifest_Upload + MD_SS_SampleManifest_Uploader CP_SS_SampleManifestExcel_Upload - CP_SS_AccessionSubmission MD_SS_Order - MD_SS_SampleManifest_Uploader - UI_SS_Manifest_Upload + MD_SS_Submission_AccessionBehaviour + CF_SS_accession_samples + CF_SS_disable_accession_check + CP_SS_Delayed_Job_Accessioning - subgraph SS_API["SS API"] - API_SS_OrderResource - end subgraph Samples UI_SS_Sample_GAN - CT_SS_Samples - subgraph MD_SS_Sample[fa:fa-square-caret-down Sample] - FN_SS_Sample_accession - FN_SS_Sample_validate_accessionable - end end subgraph Studies UI_SS_Study_GAN UI_SS_Study_AAS - subgraph CT_SS_Studies[fa:fa-arrows-spin Studies Controller] - FN_SS_Studies_accession_all_samples - FN_SS_Studies_accession - FN_SS_Studies_validate_ena_required_fields - end - subgraph MD_SS_Study[fa:fa-square-caret-down Study] - FN_SS_Study_accession_all_samples - end end - subgraph Submissions - MD_SS_Submission_AccessionBehaviour + subgraph SS_API["SS API"] + API_SS_OrderResource + end + subgraph CT_SS_Studies[fa:fa-arrows-spin Studies Controller] + FN_SS_Studies_accession_all_samples + FN_SS_Studies_accession + FN_SS_Studies_validate_ena_required_fields + FN_SS_Studies_rescue_accession_errors + end + subgraph MD_SS_Study[fa:fa-square-caret-down Study Model] + FN_SS_Study_accession_all_samples end - subgraph MD_SS_AccessionService[fa:fa-square-caret-down AccessionService] + subgraph CT_SS_Samples[fa:fa-arrows-spin Samples Controller] + FN_SS_Samples_accession + end + subgraph LB_SS_Accession[fa:fa-book Accession - newer] + CP_SS_AccessionSubmission + CP_SS_AccessionRequest + end + subgraph MD_SS_AccessionService[fa:fa-square-caret-down AccessionService Model - older] FN_SS_AccessionService_submit_sample_for_user FN_SS_AccessionService_submit_study_for_user FN_SS_AccessionService_submit end + subgraph MD_SS_Sample[fa:fa-square-caret-down Sample Model] + FN_SS_Sample_accession + FN_SS_Sample_validate_accessionable + FN_SS_Sample_validate_ena_required_fields + end end subgraph Consumers User_LB_Users @@ -144,63 +153,93 @@ flowchart LR %% Edge connections between nodes + Providers ~~~ Application_Limber ~~~ Consumers + Providers ~~~ Application_Sequencescape ~~~ Consumers + %% Limber-related - User_SeqOps ---> RES_Labware --> User_LB_Users - RES_Labware --> API_SS_OrderResource --> MD_SS_Order - MD_SS_Order ---> MD_SS_Submission_AccessionBehaviour - CF_SS_disable_accession_check -.-> MD_SS_Submission_AccessionBehaviour - MD_SS_Submission_AccessionBehaviour -- exception email --> User_Developers + User_SeqOps --> UI_LB_Charge_and_Pass -..-> User_LB_Users + UI_LB_Charge_and_Pass --> API_SS_OrderResource --> MD_SS_Order + MD_SS_Order --> MD_SS_Submission_AccessionBehaviour + MD_SS_Submission_AccessionBehaviour --> CF_SS_disable_accession_check + MD_SS_Submission_AccessionBehaviour -..-> | exception email | User_Developers %% Manifest upload - User_SSR --> RES_Manifest --> UI_SS_Manifest_Upload --> MD_SS_SampleManifest_Uploader --> CP_SS_SampleManifestExcel_Upload ---> FN_SS_Sample_accession - - FN_SS_Sample_validate_accessionable == > NO FEEDBACK > ==> CP_SS_Delayed_Job_Accessioning - CP_SS_Delayed_Job_Accessioning -- exception email --> User_Developers - CP_SS_AccessionSubmission -- exception email --> User_Developers - CP_SS_Delayed_Job_Accessioning <==> CP_SS_AccessionSubmission <==> External_EBI + User_SSR --> RES_Manifest -..-> UI_SS_Manifest_Upload --> MD_SS_SampleManifest_Uploader + MD_SS_SampleManifest_Uploader --> CP_SS_SampleManifestExcel_Upload ---> FN_SS_Sample_accession %% Sample generate accession number User_Neil --> UI_SS_Sample_GAN - CF_SS_accession_samples -.-> CT_SS_Samples - UI_SS_Sample_GAN --> CT_SS_Samples - CT_SS_Samples <==> FN_SS_AccessionService_submit_sample_for_user <==> FN_SS_AccessionService_submit <==> External_EBI - CT_SS_Samples -- flash message --> User_SS_Users + FN_SS_Samples_accession --> | 1. |CF_SS_accession_samples + UI_SS_Sample_GAN --> FN_SS_Samples_accession + FN_SS_Samples_accession --> | 2. | FN_SS_Sample_validate_ena_required_fields + FN_SS_Samples_accession ==> | 3. Validation Passed | FN_SS_AccessionService_submit_sample_for_user + FN_SS_AccessionService_submit_sample_for_user ==> FN_SS_AccessionService_submit + FN_SS_AccessionService_submit ==> External_EBI + FN_SS_Sample_validate_ena_required_fields -- Validation Failed --> FN_SS_Samples_accession + External_EBI -..-> | FROM AccessionService.submit | FN_SS_AccessionService_submit + FN_SS_AccessionService_submit -..-> | FROM submit_sample_for_user | FN_SS_Samples_accession + FN_SS_Samples_accession -..-> | flash message | User_SS_Users %% Study accession all samples User_SSR --> UI_SS_Study_AAS User_Neil --> UI_SS_Study_AAS UI_SS_Study_AAS --> FN_SS_Studies_accession_all_samples - FN_SS_Studies_accession_all_samples <--> FN_SS_Study_accession_all_samples - FN_SS_Studies_accession_all_samples -- flash message --> User_SS_Users - FN_SS_Study_accession_all_samples <---> FN_SS_Sample_accession - FN_SS_Sample_accession <--> FN_SS_Sample_validate_accessionable - CF_SS_accession_samples -.-> FN_SS_Sample_accession + FN_SS_Studies_accession_all_samples --> FN_SS_Study_accession_all_samples + FN_SS_Study_accession_all_samples -..-> | error messages | FN_SS_Studies_accession_all_samples + FN_SS_Studies_accession_all_samples -..-> | flash message | User_SS_Users + FN_SS_Study_accession_all_samples --> FN_SS_Sample_accession + FN_SS_Sample_accession -..-> | FROM accession_all_samples, error messages | FN_SS_Study_accession_all_samples + FN_SS_Sample_accession --> | 1. | CF_SS_accession_samples + FN_SS_Sample_accession --> | 2. | FN_SS_Sample_validate_accessionable + FN_SS_Sample_accession ==> | 3. > NO FEEDBACK > | CP_SS_Delayed_Job_Accessioning + CP_SS_Delayed_Job_Accessioning -..-> | exception email | User_Developers + CP_SS_Delayed_Job_Accessioning ==> CP_SS_AccessionSubmission ==> CP_SS_AccessionRequest ==> External_EBI + External_EBI -..-> | FROM Accession::Request | CP_SS_Delayed_Job_Accessioning %% Study generate accession number User_Neil --> UI_SS_Study_GAN - UI_SS_Study_GAN --> FN_SS_Studies_accession --> FN_SS_Studies_validate_ena_required_fields - FN_SS_Studies_validate_ena_required_fields -- flash message --> User_SS_Users - CF_SS_accession_samples -.-> FN_SS_AccessionService_submit_study_for_user <==> FN_SS_AccessionService_submit - FN_SS_Studies_validate_ena_required_fields <--> FN_SS_AccessionService_submit_study_for_user - - %% User_Developers -. slack / email .-> User_SS_Users + UI_SS_Study_GAN --> FN_SS_Studies_accession + FN_SS_Studies_accession --> | 1. | FN_SS_Studies_validate_ena_required_fields + FN_SS_Studies_accession --> | 2. | FN_SS_AccessionService_submit_study_for_user + FN_SS_Studies_accession -..-> | 3. IF success -- flash message | User_SS_Users + FN_SS_Studies_accession --> | IF error | FN_SS_Studies_rescue_accession_errors + FN_SS_AccessionService_submit_study_for_user --> CF_SS_accession_samples + FN_SS_AccessionService_submit_study_for_user ==> FN_SS_AccessionService_submit + FN_SS_AccessionService_submit -..-> | FROM submit_study_for_user | FN_SS_Studies_accession + FN_SS_Studies_rescue_accession_errors -..-> | flash message | User_SS_Users + + User_Developers -.-> | slack / email | User_SS_Users %% Subgraph styling + + %% lemon-chiffon: #fbf8cc + %% champagne-pink: #fde4cf + %% tea-rose-red: #ffcfd2 + %% pink-lavender: #f1c0e8 + %% mauve: #cfbaf0 + %% jordy-blue: #a3c4f3 + %% non-photo-blue: #90dbf4 + %% electric-blue: #8eecf5 + %% aquamarine: #98f5e1 + %% celadon: #b9fbc0 + + classDef default fill:#fafafa,stroke:#333,stroke-width:1px; + classDef invisible fill:transparent,stroke:transparent; classDef legendTransparent fill:transparent,stroke:#333,stroke-width:1px; - classDef Application fill:#adecff; - classDef Configuration fill:#fffbad; - classDef Sequencescape fill:#adccf6; - classDef SequencescapeSection fill:#ADDCFB; - classDef SequencescapeObject fill:#ADACEC; - classDef Users fill:#FFD6F1; + classDef application fill:#90dbf4; + classDef configuration fill:#fbf8cc; + classDef railsModel fill:#98f5e1; + classDef railsController fill:#b9fbc0; + classDef users fill:#f1c0e8; + classDef userInterface fill:#a3c4f3; class InvisibleNodeA,InvisibleNodeB,InvisibleNodeC,InvisibleNodeD invisible; class Legend legendTransparent; - class Providers,Consumers Users; - class Limber Application; - class Sequencescape Sequencescape; - class L_Config,CF_SS_disable_accession_check,CF_SS_accession_samples Configuration; + class Application_Sequencescape,Application_Limber application; + class L_Config,CF_SS_disable_accession_check,CF_SS_accession_samples configuration; class SS_API,DelayedJob,Samples,Studies,Submissions SequencescapeSection; - class MD_SS_Study,MD_SS_AccessionService,MD_SS_Sample,CT_SS_Studies SequencescapeObject; - class Providers,Consumers Users; + class L_Model,MD_SS_Study,MD_SS_AccessionService,MD_SS_Sample,MD_SS_SampleManifest_Uploader,MD_SS_Order,MD_SS_Submission_AccessionBehaviour railsModel; + class L_Controller,CT_SS_Samples,CT_SS_Studies railsController; + class L_User,User_SeqOps,User_Neil,User_SSR,User_LB_Users,User_SS_Users,User_Developers users; + class L_Interface,UI_LB_Charge_and_Pass,UI_SS_Sample_GAN,UI_SS_Study_GAN,UI_SS_Study_AAS,UI_SS_Manifest_Upload userInterface; From 6ce96cfa982b09a420b2b4bd303b4310b8e86f2d Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 28 Oct 2025 15:40:21 +0000 Subject: [PATCH 03/92] docs: only show call links --- .../accessioning-calls.mermaid} | 71 +++++-------------- 1 file changed, 17 insertions(+), 54 deletions(-) rename docs/{accessioning.mermaid => accessioning/accessioning-calls.mermaid} (74%) diff --git a/docs/accessioning.mermaid b/docs/accessioning/accessioning-calls.mermaid similarity index 74% rename from docs/accessioning.mermaid rename to docs/accessioning/accessioning-calls.mermaid index eb764603dd..1d570e5560 100644 --- a/docs/accessioning.mermaid +++ b/docs/accessioning/accessioning-calls.mermaid @@ -15,7 +15,6 @@ flowchart LR L_API(fa:fa-arrow-right-to-bracket API) L_Interface(fa:fa-computer-mouse User Interface) L_Model(fa:fa-square-caret-down Model) - L_Validation(fa:fa-check Validation) L_Controller(fa:fa-arrows-spin Controller) L_Function(fa:fa-caret-right Function) L_Config(fa:fa-screwdriver-wrench Configuration) @@ -24,26 +23,20 @@ flowchart LR L_Async(fa:fa-clock Asynchronous Process) L_User ~~~ L_External ~~~ L_API ~~~ L_Interface ~~~ L_Resource ~~~ L_Async - L_Config ~~~ L_Controller ~~~ L_Model ~~~ L_Validation ~~~ L_Function ~~~ L_Library + L_Config ~~~ L_Controller ~~~ L_Model ~~~ L_Function ~~~ L_Library InvisibleNodeA[ ] -..-> | Data flow | InvisibleNodeB[ ] -- Function calls --> InvisibleNodeC[ ] == Accessioning function calls ==> InvisibleNodeD[ ] end - Legend ~~~ Providers + Legend ~~~ User_Any %% End Legend %% Nodes %% Users - User_SeqOps(fa:fa-user SeqOps) - User_Neil(fa:fa-user Neil) - User_SSR(fa:fa-user SSRs) - User_LB_Users(fa:fa-user LB Users) - User_Developers(fa:fa-user Developers) - User_SS_Users(fa:fa-user SS Users) + User_Any(fa:fa-user Users) External_EBI(fa:fa-globe EBI) %% User Interfaces - UI_LB_Charge_and_Pass(fa:fa-computer-mouse Charge and Pass) UI_SS_Sample_GAN(fa:fa-computer-mouse Generate Accession Number) UI_SS_Study_GAN(fa:fa-computer-mouse Generate Accession Number) UI_SS_Study_AAS(fa:fa-computer-mouse Accession all Samples) @@ -90,14 +83,6 @@ flowchart LR RES_Manifest(fa:fa-file Manifest) %% Groupings of nodes - subgraph Providers - User_SeqOps - User_Neil - User_SSR - end - subgraph Application_Limber - UI_LB_Charge_and_Pass - end subgraph Application_Sequencescape UI_SS_Manifest_Upload MD_SS_SampleManifest_Uploader @@ -145,70 +130,48 @@ flowchart LR FN_SS_Sample_validate_ena_required_fields end end - subgraph Consumers - User_LB_Users - User_SS_Users - User_Developers - end %% Edge connections between nodes - - Providers ~~~ Application_Limber ~~~ Consumers - Providers ~~~ Application_Sequencescape ~~~ Consumers + Legend ~~~ User_Any + User_Any ~~~ Application_Sequencescape + CT_SS_Studies ~~~ MD_SS_Study ~~~ CT_SS_Samples ~~~ MD_SS_Sample %% Limber-related - User_SeqOps --> UI_LB_Charge_and_Pass -..-> User_LB_Users - UI_LB_Charge_and_Pass --> API_SS_OrderResource --> MD_SS_Order + User_Any --> API_SS_OrderResource --> MD_SS_Order MD_SS_Order --> MD_SS_Submission_AccessionBehaviour MD_SS_Submission_AccessionBehaviour --> CF_SS_disable_accession_check - MD_SS_Submission_AccessionBehaviour -..-> | exception email | User_Developers %% Manifest upload - User_SSR --> RES_Manifest -..-> UI_SS_Manifest_Upload --> MD_SS_SampleManifest_Uploader + User_Any --> RES_Manifest -..-> UI_SS_Manifest_Upload --> MD_SS_SampleManifest_Uploader MD_SS_SampleManifest_Uploader --> CP_SS_SampleManifestExcel_Upload ---> FN_SS_Sample_accession %% Sample generate accession number - User_Neil --> UI_SS_Sample_GAN - FN_SS_Samples_accession --> | 1. |CF_SS_accession_samples - UI_SS_Sample_GAN --> FN_SS_Samples_accession + User_Any --> UI_SS_Sample_GAN + FN_SS_Samples_accession ----> | 1. |CF_SS_accession_samples + UI_SS_Sample_GAN ----> FN_SS_Samples_accession FN_SS_Samples_accession --> | 2. | FN_SS_Sample_validate_ena_required_fields FN_SS_Samples_accession ==> | 3. Validation Passed | FN_SS_AccessionService_submit_sample_for_user FN_SS_AccessionService_submit_sample_for_user ==> FN_SS_AccessionService_submit FN_SS_AccessionService_submit ==> External_EBI - FN_SS_Sample_validate_ena_required_fields -- Validation Failed --> FN_SS_Samples_accession - External_EBI -..-> | FROM AccessionService.submit | FN_SS_AccessionService_submit - FN_SS_AccessionService_submit -..-> | FROM submit_sample_for_user | FN_SS_Samples_accession - FN_SS_Samples_accession -..-> | flash message | User_SS_Users %% Study accession all samples - User_SSR --> UI_SS_Study_AAS - User_Neil --> UI_SS_Study_AAS + User_Any --> UI_SS_Study_AAS UI_SS_Study_AAS --> FN_SS_Studies_accession_all_samples FN_SS_Studies_accession_all_samples --> FN_SS_Study_accession_all_samples - FN_SS_Study_accession_all_samples -..-> | error messages | FN_SS_Studies_accession_all_samples - FN_SS_Studies_accession_all_samples -..-> | flash message | User_SS_Users FN_SS_Study_accession_all_samples --> FN_SS_Sample_accession - FN_SS_Sample_accession -..-> | FROM accession_all_samples, error messages | FN_SS_Study_accession_all_samples FN_SS_Sample_accession --> | 1. | CF_SS_accession_samples FN_SS_Sample_accession --> | 2. | FN_SS_Sample_validate_accessionable - FN_SS_Sample_accession ==> | 3. > NO FEEDBACK > | CP_SS_Delayed_Job_Accessioning - CP_SS_Delayed_Job_Accessioning -..-> | exception email | User_Developers + FN_SS_Sample_accession ==> | 3. | CP_SS_Delayed_Job_Accessioning CP_SS_Delayed_Job_Accessioning ==> CP_SS_AccessionSubmission ==> CP_SS_AccessionRequest ==> External_EBI - External_EBI -..-> | FROM Accession::Request | CP_SS_Delayed_Job_Accessioning %% Study generate accession number - User_Neil --> UI_SS_Study_GAN + User_Any --> UI_SS_Study_GAN UI_SS_Study_GAN --> FN_SS_Studies_accession FN_SS_Studies_accession --> | 1. | FN_SS_Studies_validate_ena_required_fields FN_SS_Studies_accession --> | 2. | FN_SS_AccessionService_submit_study_for_user - FN_SS_Studies_accession -..-> | 3. IF success -- flash message | User_SS_Users FN_SS_Studies_accession --> | IF error | FN_SS_Studies_rescue_accession_errors - FN_SS_AccessionService_submit_study_for_user --> CF_SS_accession_samples - FN_SS_AccessionService_submit_study_for_user ==> FN_SS_AccessionService_submit - FN_SS_AccessionService_submit -..-> | FROM submit_study_for_user | FN_SS_Studies_accession - FN_SS_Studies_rescue_accession_errors -..-> | flash message | User_SS_Users - - User_Developers -.-> | slack / email | User_SS_Users + FN_SS_AccessionService_submit_study_for_user --> | 1. | CF_SS_accession_samples + FN_SS_AccessionService_submit_study_for_user ==> | 2. | FN_SS_AccessionService_submit %% Subgraph styling @@ -241,5 +204,5 @@ flowchart LR class SS_API,DelayedJob,Samples,Studies,Submissions SequencescapeSection; class L_Model,MD_SS_Study,MD_SS_AccessionService,MD_SS_Sample,MD_SS_SampleManifest_Uploader,MD_SS_Order,MD_SS_Submission_AccessionBehaviour railsModel; class L_Controller,CT_SS_Samples,CT_SS_Studies railsController; - class L_User,User_SeqOps,User_Neil,User_SSR,User_LB_Users,User_SS_Users,User_Developers users; + class L_User,User_Any users; class L_Interface,UI_LB_Charge_and_Pass,UI_SS_Sample_GAN,UI_SS_Study_GAN,UI_SS_Study_AAS,UI_SS_Manifest_Upload userInterface; From 77d88d9eb6f409ea3479301248bd0ac5a1d58260 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 28 Oct 2025 15:13:48 +0000 Subject: [PATCH 04/92] docs: add accessioning users --- docs/accessioning/accessioning-users.mermaid | 142 +++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 docs/accessioning/accessioning-users.mermaid diff --git a/docs/accessioning/accessioning-users.mermaid b/docs/accessioning/accessioning-users.mermaid new file mode 100644 index 0000000000..925158729b --- /dev/null +++ b/docs/accessioning/accessioning-users.mermaid @@ -0,0 +1,142 @@ +--- +title: Accessioning User Graph +--- +%%{ init: { + 'flowchart': { 'curve': 'curvy' }, + 'theme': 'neutral' + } +}%% +flowchart LR + %% Legend + subgraph Legend [Legend] + direction TB + L_User(fa:fa-user User) + L_API(fa:fa-arrow-right-to-bracket API) + L_Interface(fa:fa-computer-mouse User Interface) + L_Model(fa:fa-square-caret-down Model) + L_Controller(fa:fa-arrows-spin Controller) + L_Function(fa:fa-caret-right Function) + L_Resource(fa:fa-file Resource) + + L_User ~~~ L_Interface ~~~ L_API ~~~ L_Resource + L_Controller ~~~ L_Model ~~~ L_Function + + InvisibleNodeA[ ] -..-> | Data flow | InvisibleNodeB[ ] -- Function calls --> InvisibleNodeC[ ] + end + %% End Legend + + %% Nodes + %% Users + User_SeqOps(fa:fa-user SeqOps) + User_Neil(fa:fa-user Neil) + User_SSR(fa:fa-user SSRs) + + %% User Interfaces + UI_LB_Charge_and_Pass(fa:fa-computer-mouse Charge and Pass) + UI_SS_Sample_GAN(fa:fa-computer-mouse Generate Accession Number) + UI_SS_Study_GAN(fa:fa-computer-mouse Generate Accession Number) + UI_SS_Study_AAS(fa:fa-computer-mouse Accession all Samples) + UI_SS_Manifest_Upload(fa:fa-computer-mouse Manifest Upload) + + %% Models + MD_SS_Order(fa:fa-square-caret-down Order) + MD_SS_SampleManifest_Uploader(fa:fa-square-caret-down SampleManifest::Uploader) + + %% Functions + FN_SS_Samples_accession(fa:fa-caret-right accession) + FN_SS_Studies_accession_all_samples(fa:fa-caret-right accession_all_samples) + FN_SS_Studies_accession(fa:fa-caret-right accession) + + %% Other Components + API_SS_OrderResource(fa:fa-arrow-right-to-bracket Order Resource) + + %% Resources + RES_Manifest(fa:fa-file Manifest) + + %% Groupings of nodes + subgraph Providers + User_SeqOps + User_Neil + User_SSR + end + subgraph Application_Limber + UI_LB_Charge_and_Pass + end + subgraph Application_Sequencescape + UI_SS_Manifest_Upload + MD_SS_SampleManifest_Uploader + MD_SS_Order + + subgraph Samples + UI_SS_Sample_GAN + end + subgraph Studies + UI_SS_Study_GAN + UI_SS_Study_AAS + end + subgraph SS_API["SS API"] + API_SS_OrderResource + end + subgraph CT_SS_Studies[fa:fa-arrows-spin Studies Controller] + FN_SS_Studies_accession_all_samples + FN_SS_Studies_accession + end + subgraph CT_SS_Samples[fa:fa-arrows-spin Samples Controller] + FN_SS_Samples_accession + end + end + + %% Edge connections between nodes + Legend ~~~ Providers + + %% Limber-related + User_SeqOps --> UI_LB_Charge_and_Pass + UI_LB_Charge_and_Pass --> API_SS_OrderResource --> MD_SS_Order + + %% Manifest upload + User_SSR --> RES_Manifest -.-> UI_SS_Manifest_Upload --> MD_SS_SampleManifest_Uploader + + %% Sample generate accession number + User_SSR --> UI_SS_Sample_GAN + User_Neil --> UI_SS_Sample_GAN + UI_SS_Sample_GAN --> FN_SS_Samples_accession + + %% Study accession all samples + User_SSR --> UI_SS_Study_AAS + User_Neil --> UI_SS_Study_AAS + UI_SS_Study_AAS --> FN_SS_Studies_accession_all_samples + + %% Study generate accession number + User_Neil --> UI_SS_Study_GAN + UI_SS_Study_GAN --> FN_SS_Studies_accession + + %% Subgraph styling + + %% lemon-chiffon: #fbf8cc + %% champagne-pink: #fde4cf + %% tea-rose-red: #ffcfd2 + %% pink-lavender: #f1c0e8 + %% mauve: #cfbaf0 + %% jordy-blue: #a3c4f3 + %% non-photo-blue: #90dbf4 + %% electric-blue: #8eecf5 + %% aquamarine: #98f5e1 + %% celadon: #b9fbc0 + + classDef default fill:#fafafa,stroke:#333,stroke-width:1px; + + classDef invisible fill:transparent,stroke:transparent; + classDef legendTransparent fill:transparent,stroke:#333,stroke-width:1px; + classDef application fill:#90dbf4; + classDef railsModel fill:#98f5e1; + classDef railsController fill:#b9fbc0; + classDef users fill:#f1c0e8; + classDef userInterface fill:#a3c4f3; + + class InvisibleNodeA,InvisibleNodeB,InvisibleNodeC,InvisibleNodeD invisible; + class Legend legendTransparent; + class Application_Sequencescape,Application_Limber application; + class L_Model,MD_SS_SampleManifest_Uploader,MD_SS_Order railsModel; + class L_Controller,CT_SS_Samples,CT_SS_Studies railsController; + class L_User,User_SeqOps,User_Neil,User_SSR users; + class L_Interface,UI_LB_Charge_and_Pass,UI_SS_Sample_GAN,UI_SS_Study_GAN,UI_SS_Study_AAS,UI_SS_Manifest_Upload userInterface; From 9d5901621626baa6276ae4c8ed545227f53a44e1 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Wed, 29 Oct 2025 09:40:45 +0000 Subject: [PATCH 05/92] docs: update master diagram --- docs/accessioning/.gitignore | 3 + docs/accessioning/accessioning.mermaid | 248 +++++++++++++++++++++++++ 2 files changed, 251 insertions(+) create mode 100644 docs/accessioning/.gitignore create mode 100644 docs/accessioning/accessioning.mermaid diff --git a/docs/accessioning/.gitignore b/docs/accessioning/.gitignore new file mode 100644 index 0000000000..6cd6fa8833 --- /dev/null +++ b/docs/accessioning/.gitignore @@ -0,0 +1,3 @@ +# Ignore generated diagrams from Mermaid +*.png +*.svg diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid new file mode 100644 index 0000000000..6cab20d33e --- /dev/null +++ b/docs/accessioning/accessioning.mermaid @@ -0,0 +1,248 @@ +--- +title: Accessioning Call Graph +--- +%%{ init: { + 'flowchart': { 'curve': 'curvy' }, + 'theme': 'neutral' + } +}%% +flowchart LR + %% Legend + subgraph Legend [Legend] + direction TB + L_User(fa:fa-user User) + L_External(fa:fa-globe External System) + L_API(fa:fa-arrow-right-to-bracket API) + L_Interface(fa:fa-computer-mouse User Interface) + L_Model(fa:fa-square-caret-down Model) + L_Controller(fa:fa-arrows-spin Controller) + L_Function(fa:fa-caret-right Function) + L_Config(fa:fa-screwdriver-wrench Configuration) + L_Resource(fa:fa-file Resource) + L_Library(fa:fa-book Library) + L_Async(fa:fa-clock Asynchronous Process) + + L_User ~~~ L_External ~~~ L_API ~~~ L_Interface ~~~ L_Resource ~~~ L_Async + L_Config ~~~ L_Controller ~~~ L_Model ~~~ L_Function ~~~ L_Library + + InvisibleNodeA[ ] -..-> | Data flow | InvisibleNodeB[ ] -- Function calls --> InvisibleNodeC[ ] == Accessioning function calls ==> InvisibleNodeD[ ] + end + + Legend ~~~ Providers + %% End Legend + + %% Nodes + %% Users + User_SeqOps(fa:fa-user SeqOps) + User_Neil(fa:fa-user Neil) + User_SSR(fa:fa-user SSRs) + User_LB_Users(fa:fa-user LB Users) + User_Developers(fa:fa-user Developers) + User_SS_Users(fa:fa-user SS Users) + External_EBI(fa:fa-globe EBI) + + %% User Interfaces + UI_LB_Charge_and_Pass(fa:fa-computer-mouse Charge and Pass) + UI_SS_Sample_GAN(fa:fa-computer-mouse Generate Accession Number) + UI_SS_Study_GAN(fa:fa-computer-mouse Generate Accession Number) + UI_SS_Study_AAS(fa:fa-computer-mouse Accession all Samples) + UI_SS_Manifest_Upload(fa:fa-computer-mouse Manifest Upload) + + %% Models + MD_SS_Order(fa:fa-square-caret-down Order) + %% MD_SS_AccessionService(fa:fa-square-caret-down AccessionService) + MD_SS_Submission_AccessionBehaviour(fa:fa-square-caret-down Submission::AccessionBehaviour) + %% MD_SS_Sample(fa:fa-square-caret-down Sample) + %% MD_SS_Study(fa:fa-square-caret-down Study) + MD_SS_SampleManifest_Uploader(fa:fa-square-caret-down SampleManifest::Uploader) + + %% Controllers + %% CT_SS_Samples(fa:fa-arrows-spin Samples Controller) + %% CT_SS_Studies(fa:fa-arrows-spin Studies Controller) + + %% Functions + FN_SS_AccessionService_submit_sample_for_user(fa:fa-caret-right submit_sample_for_user) + FN_SS_AccessionService_submit_study_for_user(fa:fa-caret-right submit_study_for_user) + FN_SS_AccessionService_submit(fa:fa-caret-right submit) + FN_SS_Sample_accession(fa:fa-caret-right accession) + FN_SS_Sample_validate_accessionable(fa:fa-caret-right validate_accessionable!) + FN_SS_Sample_validate_ena_required_fields(fa:fa-caret-right validate_ena_required_fields!) + FN_SS_Samples_accession(fa:fa-caret-right accession) + FN_SS_Study_accession_all_samples(fa:fa-caret-right accession_all_samples) + FN_SS_Studies_accession_all_samples(fa:fa-caret-right accession_all_samples) + FN_SS_Studies_accession(fa:fa-caret-right accession) + FN_SS_Studies_validate_ena_required_fields(fa:fa-caret-right validate_ena_required_fields!) + FN_SS_Studies_rescue_accession_errors(fa:fa-caret-right rescue_accession_errors) + + %% Other Components + API_SS_OrderResource(fa:fa-arrow-right-to-bracket Order Resource) + CP_SS_Delayed_Job_Accessioning(fa:fa-clock DelayedJob::SampleAccessioningJob) + CP_SS_AccessionSubmission(fa:fa-book Accession::Submission) + CP_SS_AccessionRequest(fa:fa-book Accession::Request) + CP_SS_SampleManifestExcel_Upload(SampleManifestExcel::Upload) + + %% Config + CF_SS_accession_samples(fa:fa-screwdriver-wrench accession_samples) + CF_SS_disable_accession_check(fa:fa-screwdriver-wrench disable_accession_check) + + %% Resources + RES_Manifest(fa:fa-file Manifest) + + %% Groupings of nodes + subgraph Providers + User_SeqOps + User_Neil + User_SSR + end + subgraph Application_Limber[Limber Application] + UI_LB_Charge_and_Pass + end + subgraph Application_Sequencescape[Sequencescape Application] + UI_SS_Manifest_Upload + MD_SS_SampleManifest_Uploader + CP_SS_SampleManifestExcel_Upload + MD_SS_Order + MD_SS_Submission_AccessionBehaviour + CF_SS_accession_samples + CF_SS_disable_accession_check + CP_SS_Delayed_Job_Accessioning + + subgraph Samples + UI_SS_Sample_GAN + end + subgraph Studies + UI_SS_Study_GAN + UI_SS_Study_AAS + end + subgraph SS_API["SS API"] + API_SS_OrderResource + end + subgraph CT_SS_Studies[fa:fa-arrows-spin Studies Controller] + FN_SS_Studies_accession_all_samples + FN_SS_Studies_accession + FN_SS_Studies_validate_ena_required_fields + FN_SS_Studies_rescue_accession_errors + end + subgraph MD_SS_Study[fa:fa-square-caret-down Study Model] + FN_SS_Study_accession_all_samples + end + subgraph CT_SS_Samples[fa:fa-arrows-spin Samples Controller] + FN_SS_Samples_accession + end + subgraph LB_SS_Accession[fa:fa-book Accession - newer] + CP_SS_AccessionSubmission + CP_SS_AccessionRequest + end + subgraph MD_SS_AccessionService[fa:fa-square-caret-down AccessionService Model - older] + FN_SS_AccessionService_submit_sample_for_user + FN_SS_AccessionService_submit_study_for_user + FN_SS_AccessionService_submit + end + subgraph MD_SS_Sample[fa:fa-square-caret-down Sample Model] + FN_SS_Sample_accession + FN_SS_Sample_validate_accessionable + FN_SS_Sample_validate_ena_required_fields + end + end + subgraph Consumers + User_LB_Users + User_SS_Users + User_Developers + end + + %% Edge connections between nodes + + Providers ~~~ Application_Limber ~~~ Consumers + Providers ~~~ Application_Sequencescape ~~~ Consumers + + CT_SS_Studies ~~~ MD_SS_Study ~~~ CT_SS_Samples ~~~ MD_SS_Sample + FN_SS_Samples_accession ~~~ FN_SS_AccessionService_submit_study_for_user + + %% Limber-related + User_SeqOps --> UI_LB_Charge_and_Pass -...-> User_LB_Users + UI_LB_Charge_and_Pass --> API_SS_OrderResource --> MD_SS_Order + MD_SS_Order --> MD_SS_Submission_AccessionBehaviour + MD_SS_Submission_AccessionBehaviour --> CF_SS_disable_accession_check + MD_SS_Submission_AccessionBehaviour -..-> | exception email | User_Developers + + %% Manifest upload + User_SSR --> RES_Manifest -..-> UI_SS_Manifest_Upload --> MD_SS_SampleManifest_Uploader + MD_SS_SampleManifest_Uploader --> CP_SS_SampleManifestExcel_Upload ---> FN_SS_Sample_accession + + %% Sample generate accession number + User_SSR --> UI_SS_Sample_GAN + User_Neil --> UI_SS_Sample_GAN + FN_SS_Samples_accession --> | 1. |CF_SS_accession_samples + UI_SS_Sample_GAN --> FN_SS_Samples_accession + FN_SS_Samples_accession --> | 2. | FN_SS_Sample_validate_ena_required_fields + FN_SS_Samples_accession ==> | 3. Validation Passed | FN_SS_AccessionService_submit_sample_for_user + FN_SS_AccessionService_submit_sample_for_user ==> FN_SS_AccessionService_submit + FN_SS_AccessionService_submit ==> External_EBI + FN_SS_Sample_validate_ena_required_fields -- Validation Failed --> FN_SS_Samples_accession + External_EBI -..-> | FROM AccessionService.submit | FN_SS_AccessionService_submit + FN_SS_AccessionService_submit -..-> | FROM submit_sample_for_user | FN_SS_Samples_accession + FN_SS_Samples_accession -..-> | flash message | User_SS_Users + + %% Study accession all samples + User_SSR --> UI_SS_Study_AAS + User_Neil --> UI_SS_Study_AAS + UI_SS_Study_AAS --> FN_SS_Studies_accession_all_samples + FN_SS_Studies_accession_all_samples --> FN_SS_Study_accession_all_samples + FN_SS_Study_accession_all_samples -..-> | error messages | FN_SS_Studies_accession_all_samples + FN_SS_Studies_accession_all_samples -..-> | flash message | User_SS_Users + FN_SS_Study_accession_all_samples --> FN_SS_Sample_accession + FN_SS_Sample_accession -..-> | FROM accession_all_samples, error messages | FN_SS_Study_accession_all_samples + FN_SS_Sample_accession --> | 1. | CF_SS_accession_samples + FN_SS_Sample_accession --> | 2. | FN_SS_Sample_validate_accessionable + FN_SS_Sample_accession ==> | 3. > NO FEEDBACK > | CP_SS_Delayed_Job_Accessioning + CP_SS_Delayed_Job_Accessioning -..-> | exception email | User_Developers + CP_SS_Delayed_Job_Accessioning ==> CP_SS_AccessionSubmission ==> CP_SS_AccessionRequest ==> External_EBI + External_EBI -..-> | FROM Accession::Request | CP_SS_Delayed_Job_Accessioning + + %% Study generate accession number + User_Neil --> UI_SS_Study_GAN + UI_SS_Study_GAN --> FN_SS_Studies_accession + FN_SS_Studies_accession --> | 1. | FN_SS_Studies_validate_ena_required_fields + FN_SS_Studies_accession --> | 2. | FN_SS_AccessionService_submit_study_for_user + FN_SS_Studies_accession -..-> | 3. IF success -- flash message | User_SS_Users + FN_SS_Studies_accession --> | IF error | FN_SS_Studies_rescue_accession_errors + FN_SS_AccessionService_submit_study_for_user --> CF_SS_accession_samples + FN_SS_AccessionService_submit_study_for_user ==> FN_SS_AccessionService_submit + FN_SS_AccessionService_submit -..-> | FROM submit_study_for_user | FN_SS_Studies_accession + FN_SS_Studies_rescue_accession_errors -..-> | flash message | User_SS_Users + + User_Developers -.-> | slack / email | User_SS_Users + + %% Subgraph styling + + %% lemon-chiffon: #fbf8cc + %% champagne-pink: #fde4cf + %% tea-rose-red: #ffcfd2 + %% pink-lavender: #f1c0e8 + %% mauve: #cfbaf0 + %% jordy-blue: #a3c4f3 + %% non-photo-blue: #90dbf4 + %% electric-blue: #8eecf5 + %% aquamarine: #98f5e1 + %% celadon: #b9fbc0 + + classDef default fill:#fafafa,stroke:#333,stroke-width:1px; + + classDef invisible fill:transparent,stroke:transparent; + classDef legendTransparent fill:transparent,stroke:#333,stroke-width:1px; + classDef application fill:#90dbf4; + classDef configuration fill:#fbf8cc; + classDef railsModel fill:#98f5e1; + classDef railsController fill:#b9fbc0; + classDef users fill:#f1c0e8; + classDef userInterface fill:#a3c4f3; + + class InvisibleNodeA,InvisibleNodeB,InvisibleNodeC,InvisibleNodeD invisible; + class Legend legendTransparent; + class Application_Sequencescape,Application_Limber application; + class L_Config,CF_SS_disable_accession_check,CF_SS_accession_samples configuration; + class SS_API,DelayedJob,Samples,Studies,Submissions SequencescapeSection; + class L_Model,MD_SS_Study,MD_SS_AccessionService,MD_SS_Sample,MD_SS_SampleManifest_Uploader,MD_SS_Order,MD_SS_Submission_AccessionBehaviour railsModel; + class L_Controller,CT_SS_Samples,CT_SS_Studies railsController; + class L_User,User_SeqOps,User_Neil,User_SSR,User_LB_Users,User_SS_Users,User_Developers users; + class L_Interface,UI_LB_Charge_and_Pass,UI_SS_Sample_GAN,UI_SS_Study_GAN,UI_SS_Study_AAS,UI_SS_Manifest_Upload userInterface; From 002d797d18e614e4ffd4f06b9660cfc0cde375e6 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Wed, 29 Oct 2025 10:18:35 +0000 Subject: [PATCH 06/92] docs: fix confusing diagram caused by reverse arrow --- docs/accessioning/accessioning.mermaid | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index 6cab20d33e..637d8dbfae 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -197,7 +197,8 @@ flowchart LR FN_SS_Sample_accession ==> | 3. > NO FEEDBACK > | CP_SS_Delayed_Job_Accessioning CP_SS_Delayed_Job_Accessioning -..-> | exception email | User_Developers CP_SS_Delayed_Job_Accessioning ==> CP_SS_AccessionSubmission ==> CP_SS_AccessionRequest ==> External_EBI - External_EBI -..-> | FROM Accession::Request | CP_SS_Delayed_Job_Accessioning + %% External_EBI .-> | FROM Accession::Request | CP_SS_Delayed_Job_Accessioning ...struggling to reverse the arrow here... + CP_SS_Delayed_Job_Accessioning -.- | <-- FROM Accession::Request | External_EBI %% Study generate accession number User_Neil --> UI_SS_Study_GAN From 04e29149ae27e759ad8af4268884b9b567361155 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Thu, 30 Oct 2025 10:18:22 +0000 Subject: [PATCH 07/92] docs: update overview diagram title --- docs/accessioning/accessioning.mermaid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index 637d8dbfae..853fe14622 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -1,5 +1,5 @@ --- -title: Accessioning Call Graph +title: Accessioning Call Graph Overview --- %%{ init: { 'flowchart': { 'curve': 'curvy' }, From 5ad395ffee5b48d2f53b6e335d2b59d71c6eed5c Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Thu, 30 Oct 2025 15:18:03 +0000 Subject: [PATCH 08/92] docs: adjust display of validation failed arrow --- docs/accessioning/accessioning.mermaid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index 853fe14622..fc3bde2865 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -178,7 +178,7 @@ flowchart LR FN_SS_Samples_accession ==> | 3. Validation Passed | FN_SS_AccessionService_submit_sample_for_user FN_SS_AccessionService_submit_sample_for_user ==> FN_SS_AccessionService_submit FN_SS_AccessionService_submit ==> External_EBI - FN_SS_Sample_validate_ena_required_fields -- Validation Failed --> FN_SS_Samples_accession + FN_SS_Sample_validate_ena_required_fields -..-> | Validation Failed | FN_SS_Samples_accession External_EBI -..-> | FROM AccessionService.submit | FN_SS_AccessionService_submit FN_SS_AccessionService_submit -..-> | FROM submit_sample_for_user | FN_SS_Samples_accession FN_SS_Samples_accession -..-> | flash message | User_SS_Users From 6fc67ef9e842ce99ee2348619af3b2df90bd5a0c Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal Date: Tue, 23 Dec 2025 11:58:17 +0000 Subject: [PATCH 09/92] Update stocks? method to return true for library assets. --- app/models/sample_manifest/core_behaviour.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/sample_manifest/core_behaviour.rb b/app/models/sample_manifest/core_behaviour.rb index 7513cee410..7b6e3142e8 100644 --- a/app/models/sample_manifest/core_behaviour.rb +++ b/app/models/sample_manifest/core_behaviour.rb @@ -74,7 +74,7 @@ def generate_sample_and_aliquot(sanger_sample_id, receptacle) end def stocks? - false + true end end From 6a3cbd1b5bd0c4a65de42b2fee8e977f2d8ae508 Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal Date: Tue, 23 Dec 2025 12:12:47 +0000 Subject: [PATCH 10/92] feat: Add and update SampleManifest specs to verify stocks behavior for library asset types. --- spec/models/library_assets_stocks_spec.rb | 24 +++++++++++++++++++++++ spec/models/sample_manifest_spec.rb | 4 ++++ 2 files changed, 28 insertions(+) create mode 100644 spec/models/library_assets_stocks_spec.rb diff --git a/spec/models/library_assets_stocks_spec.rb b/spec/models/library_assets_stocks_spec.rb new file mode 100644 index 0000000000..febc26dd80 --- /dev/null +++ b/spec/models/library_assets_stocks_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +require 'rails_helper' + +RSpec.describe SampleManifest, type: :model do + describe 'Library behaviours' do + let(:study) { create(:study) } + + context 'when asset_type is library' do + let(:manifest) { create(:sample_manifest, study: study, asset_type: 'library') } + + it 'has stocks? set to true in core_behaviour' do + expect(manifest.core_behaviour.stocks?).to be true + end + end + + context 'when asset_type is library_plate' do + let(:manifest) { create(:sample_manifest, study: study, asset_type: 'library_plate') } + + it 'has stocks? set to true in core_behaviour' do + expect(manifest.core_behaviour.stocks?).to be true + end + end + end +end diff --git a/spec/models/sample_manifest_spec.rb b/spec/models/sample_manifest_spec.rb index 0401da72a3..8b052e0c1e 100644 --- a/spec/models/sample_manifest_spec.rb +++ b/spec/models/sample_manifest_spec.rb @@ -234,6 +234,10 @@ expect { manifest.generate }.to change(BroadcastEvent, :count).by(1) end + it 'has stocks behavior' do + expect(manifest.core_behaviour.stocks?).to be true + end + context 'once generated' do before { manifest.generate } From 0e2775d00858bbf96a40df02cb25eff919cdadc3 Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal Date: Tue, 23 Dec 2025 14:58:09 +0000 Subject: [PATCH 11/92] feat: Add labware_type method to determine asset type for library resources --- app/models/sample_manifest/core_behaviour.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/sample_manifest/core_behaviour.rb b/app/models/sample_manifest/core_behaviour.rb index 7b6e3142e8..9e1eb220ca 100644 --- a/app/models/sample_manifest/core_behaviour.rb +++ b/app/models/sample_manifest/core_behaviour.rb @@ -76,6 +76,10 @@ def generate_sample_and_aliquot(sanger_sample_id, receptacle) def stocks? true end + + def labware_type + asset_type == 'well' ? 'library_plate_well' : 'library_tube' + end end def self.included(base) From 59034155c8379f429ee7fa0f0c105f13efdac27c Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal Date: Tue, 23 Dec 2025 16:05:12 +0000 Subject: [PATCH 12/92] feat: Update stock resource registration for library assets and add stocks? method --- app/models/library_plate_well.rb | 6 ++++++ app/models/sample_manifest/core_behaviour.rb | 7 ++----- app/models/sample_manifest/library_plate_behaviour.rb | 4 ++++ app/models/sample_manifest/library_tube_behaviour.rb | 4 ++++ .../sample_manifest/multiplexed_library_behaviour.rb | 4 ++++ .../sample_manifest_excel/upload/base.rb | 2 +- 6 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 app/models/library_plate_well.rb diff --git a/app/models/library_plate_well.rb b/app/models/library_plate_well.rb new file mode 100644 index 0000000000..f466d8eaa1 --- /dev/null +++ b/app/models/library_plate_well.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +class LibraryPlateWell < Well + def subject_type + 'library_plate_well' + end +end diff --git a/app/models/sample_manifest/core_behaviour.rb b/app/models/sample_manifest/core_behaviour.rb index 9e1eb220ca..af93f5da45 100644 --- a/app/models/sample_manifest/core_behaviour.rb +++ b/app/models/sample_manifest/core_behaviour.rb @@ -74,11 +74,7 @@ def generate_sample_and_aliquot(sanger_sample_id, receptacle) end def stocks? - true - end - - def labware_type - asset_type == 'well' ? 'library_plate_well' : 'library_tube' + false end end @@ -100,6 +96,7 @@ def core_behaviour # rubocop:todo Metrics/MethodLength def behaviour_module # rubocop:todo Metrics/CyclomaticComplexity + # asset_type comes from the query params for the new/create manifest actions page. case asset_type when '1dtube' 'SampleTubeBehaviour' diff --git a/app/models/sample_manifest/library_plate_behaviour.rb b/app/models/sample_manifest/library_plate_behaviour.rb index 3b7ab0ef6b..29697aebbe 100644 --- a/app/models/sample_manifest/library_plate_behaviour.rb +++ b/app/models/sample_manifest/library_plate_behaviour.rb @@ -5,5 +5,9 @@ module SampleManifest::LibraryPlateBehaviour # it sets library_id on aliquots in wells and doesn't generate stock assets. class Core < SampleManifest::PlateBehaviour::Base include SampleManifest::CoreBehaviour::LibraryAssets + + def stocks? + true + end end end diff --git a/app/models/sample_manifest/library_tube_behaviour.rb b/app/models/sample_manifest/library_tube_behaviour.rb index 1c1d5e30e8..57687d00a5 100644 --- a/app/models/sample_manifest/library_tube_behaviour.rb +++ b/app/models/sample_manifest/library_tube_behaviour.rb @@ -53,5 +53,9 @@ def default_purpose def included_resources [{ sample: :sample_metadata, asset: %i[barcodes aliquots] }] end + + def stocks? + true + end end end diff --git a/app/models/sample_manifest/multiplexed_library_behaviour.rb b/app/models/sample_manifest/multiplexed_library_behaviour.rb index 582ae5ee87..9f7d8fcb98 100644 --- a/app/models/sample_manifest/multiplexed_library_behaviour.rb +++ b/app/models/sample_manifest/multiplexed_library_behaviour.rb @@ -76,5 +76,9 @@ def printables def included_resources [{ sample: :sample_metadata, asset: [:barcodes, :aliquots, { requests: :target_asset }] }] end + + def stocks? + true + end end end diff --git a/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb b/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb index 5b54ec4974..ad6ea41df8 100644 --- a/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb +++ b/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb @@ -113,7 +113,7 @@ def trigger_accessioning(event_user) "Skipping accessioning of samples in manifest #{sample_manifest.id}." end - # If samples have been created, and it's not a library plate/tube, register a stock_resource record in the MLWH + # If samples have been created, register a stock_resource record in the MLWH def register_stock_resources stock_receptacles_to_be_registered.each(&:register_stock!) end From be720c8d5fe952f4c453693376a3aeb7df53a048 Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal Date: Wed, 24 Dec 2025 11:14:45 +0000 Subject: [PATCH 13/92] feat: Update stock receptacles registration to set subject type for library assets --- app/models/well.rb | 4 +++- .../sample_manifest_excel/upload/base.rb | 12 +++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/models/well.rb b/app/models/well.rb index 2607705fa7..78893303a8 100644 --- a/app/models/well.rb +++ b/app/models/well.rb @@ -208,9 +208,11 @@ def stock_wells_for_downstream_wells end def subject_type - 'well' + @subject_type ||= 'well' end + attr_writer :subject_type + def outer_request(submission_id) outer_requests.order(id: :desc).find_by(submission_id:) end diff --git a/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb b/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb index ad6ea41df8..0453e8e03d 100644 --- a/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb +++ b/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb @@ -190,10 +190,20 @@ def changed_labware @changed_labware ||= rows.select(&:changed?).reduce(Set.new) { |set, row| set << row.labware } end + def update_subject_type_for_library_receptacles!(asset) + return unless sample_manifest.core_behaviour.to_s.include?('LibraryPlateBehaviour') + + asset.subject_type = 'library_plate_well' + end + def stock_receptacles_to_be_registered return [] unless sample_manifest.core_behaviour.stocks? - @stock_receptacles_to_be_registered ||= rows.select(&:sample_created?).map(&:asset) + @stock_receptacles_to_be_registered ||= rows.select(&:sample_created?) + .map(&:asset) + .each do |asset| + update_subject_type_for_library_receptacles!(asset) + end end end end From 1237d5ede60cd5b1a7ec48ad27295ffb9dbf541a Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal Date: Wed, 24 Dec 2025 11:23:28 +0000 Subject: [PATCH 14/92] feat: Add subject_type method for library tube and multiplexed library tube --- app/models/library_tube.rb | 4 ++++ app/models/multiplexed_library_tube.rb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/app/models/library_tube.rb b/app/models/library_tube.rb index 55570e19f3..e3424f05ae 100644 --- a/app/models/library_tube.rb +++ b/app/models/library_tube.rb @@ -13,6 +13,10 @@ def self.stock_asset_purpose Tube::Purpose.stock_library_tube end + def subject_type + 'library_tube' + end + def library_information # rubocop:todo Metrics/AbcSize tag = aliquots.first.tag tag2 = aliquots.first.tag2 diff --git a/app/models/multiplexed_library_tube.rb b/app/models/multiplexed_library_tube.rb index 9d5453273b..e0f5fe7d19 100644 --- a/app/models/multiplexed_library_tube.rb +++ b/app/models/multiplexed_library_tube.rb @@ -17,6 +17,10 @@ def asset_type_for_request_types LibraryTube end + def subject_type + 'multiplexed_library_tube' + end + def team creation_requests.first&.product_line end From e25dcb971e43967a9036df3af2ff834f54512420 Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal Date: Wed, 24 Dec 2025 11:47:37 +0000 Subject: [PATCH 15/92] feat: Update lab_event_spec to use 'library_tube' as subject type for sequencing source labware --- spec/models/broadcast_event/lab_event_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/broadcast_event/lab_event_spec.rb b/spec/models/broadcast_event/lab_event_spec.rb index 1f06d87ce6..a35d4ed8c0 100644 --- a/spec/models/broadcast_event/lab_event_spec.rb +++ b/spec/models/broadcast_event/lab_event_spec.rb @@ -70,7 +70,7 @@ }, { 'role_type' => 'sequencing_source_labware', - 'subject_type' => 'tube', + 'subject_type' => 'library_tube', 'uuid' => stock_asset.uuid, 'friendly_name' => stock_asset.human_barcode } @@ -100,7 +100,7 @@ }, { 'role_type' => 'sequencing_source_labware', - 'subject_type' => 'tube', + 'subject_type' => 'library_tube', 'uuid' => stock_asset.uuid, 'friendly_name' => stock_asset.human_barcode } From 6cbf223a91a35b2e949e09c1b68f6e9738e36424 Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal Date: Fri, 2 Jan 2026 12:02:38 +0000 Subject: [PATCH 16/92] feat: Add generate method to create plates and update sample manifest with barcodes --- app/models/sample_manifest/plate_behaviour.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/models/sample_manifest/plate_behaviour.rb b/app/models/sample_manifest/plate_behaviour.rb index 70e2eaea54..bad48f1a40 100644 --- a/app/models/sample_manifest/plate_behaviour.rb +++ b/app/models/sample_manifest/plate_behaviour.rb @@ -11,6 +11,15 @@ def initialize(manifest) @plates = [] end + # Generates plates and associated data for the sample manifest. + # + # Steps: + # 1. Generates new plates for the given purpose. + # 2. Inserts Sanger sample IDs for the plates. + # 3. Builds well data mapping plates, maps, and sample IDs. + # 4. Enqueues asynchronous jobs to build wells for each plate. + # 5. Constructs an array of details for each well. + # 6. Updates the manifest with the barcodes of the generated plates. def generate @plates = generate_plates(purpose) From 56a1ef7ef0c578bb081fa4637a3385d964567bcc Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal Date: Mon, 5 Jan 2026 11:28:56 +0000 Subject: [PATCH 17/92] feat: Refactor process method to include item processing and subject type assignment for library plates --- .../subscriber/queue_broadcast_consumer.rb | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/app/models/warren/subscriber/queue_broadcast_consumer.rb b/app/models/warren/subscriber/queue_broadcast_consumer.rb index 1ef7b59e73..88bf366d8f 100644 --- a/app/models/warren/subscriber/queue_broadcast_consumer.rb +++ b/app/models/warren/subscriber/queue_broadcast_consumer.rb @@ -24,14 +24,35 @@ class Warren::Subscriber::QueueBroadcastConsumer < Warren::Subscriber::Base # Handles message processing. Messages are acknowledged automatically # on return from the method assuming they haven't been handled already. # In the event of an uncaught exception, the message will be dead-lettered. + # + # For example, if the message is `["Well", 1]`, this method will find the Well + # with ID 1 and call `broadcast` on it. def process klass = json.first.constantize - klass.find(json.last).broadcast + item = process_item(klass, json) + item.broadcast rescue ActiveRecord::RecordNotFound # This may indicate that the record has been deleted debug "#{payload} not found." end + # Finds the record for the given class and JSON payload, checks if its asset type is 'library_plate', + # and if the class is Well, sets the subject_type to 'library_plate_well'. + # + # @param klass [Class] The ActiveRecord class to query (e.g., Well, Plate) + # @param json [Array, nil] The parsed JSON payload, expected to contain the class name and record ID + # @return [ActiveRecord::Base] The found record, possibly modified + def process_item(klass, json = nil) + item = klass.find(json&.last) + asset_type = SampleManifestAsset.find_by(asset_id: item.id)&.sample_manifest&.asset_type + + if asset_type.present? && asset_type == 'library_plate' && klass == Well + item.subject_type = 'library_plate_well' + end + + item + end + def json @json ||= extract_json end From 9a43826c3fac76c9f2939cd939186fa1516ff5a6 Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal Date: Mon, 5 Jan 2026 11:30:03 +0000 Subject: [PATCH 18/92] feat: Simplify process_item method by removing json parameter and adjusting item retrieval --- app/models/warren/subscriber/queue_broadcast_consumer.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/warren/subscriber/queue_broadcast_consumer.rb b/app/models/warren/subscriber/queue_broadcast_consumer.rb index 88bf366d8f..5416aa057c 100644 --- a/app/models/warren/subscriber/queue_broadcast_consumer.rb +++ b/app/models/warren/subscriber/queue_broadcast_consumer.rb @@ -29,7 +29,7 @@ class Warren::Subscriber::QueueBroadcastConsumer < Warren::Subscriber::Base # with ID 1 and call `broadcast` on it. def process klass = json.first.constantize - item = process_item(klass, json) + item = process_item(klass) item.broadcast rescue ActiveRecord::RecordNotFound # This may indicate that the record has been deleted @@ -42,8 +42,8 @@ def process # @param klass [Class] The ActiveRecord class to query (e.g., Well, Plate) # @param json [Array, nil] The parsed JSON payload, expected to contain the class name and record ID # @return [ActiveRecord::Base] The found record, possibly modified - def process_item(klass, json = nil) - item = klass.find(json&.last) + def process_item(klass) + item = klass.find(json.last) asset_type = SampleManifestAsset.find_by(asset_id: item.id)&.sample_manifest&.asset_type if asset_type.present? && asset_type == 'library_plate' && klass == Well From 1ae521886c670bcb2cf5b8e889f1355afe86e987 Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal Date: Mon, 5 Jan 2026 11:31:08 +0000 Subject: [PATCH 19/92] Fix rubocop --- app/models/warren/subscriber/queue_broadcast_consumer.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/models/warren/subscriber/queue_broadcast_consumer.rb b/app/models/warren/subscriber/queue_broadcast_consumer.rb index 5416aa057c..933c70c628 100644 --- a/app/models/warren/subscriber/queue_broadcast_consumer.rb +++ b/app/models/warren/subscriber/queue_broadcast_consumer.rb @@ -46,9 +46,7 @@ def process_item(klass) item = klass.find(json.last) asset_type = SampleManifestAsset.find_by(asset_id: item.id)&.sample_manifest&.asset_type - if asset_type.present? && asset_type == 'library_plate' && klass == Well - item.subject_type = 'library_plate_well' - end + item.subject_type = 'library_plate_well' if asset_type.present? && asset_type == 'library_plate' && klass == Well item end From e32f0ac7c28691758ff1b470e927b254afc820f1 Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal Date: Mon, 5 Jan 2026 11:44:29 +0000 Subject: [PATCH 20/92] feat: Remove unused json parameter from process_item method in queue_broadcast_consumer --- app/models/warren/subscriber/queue_broadcast_consumer.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/warren/subscriber/queue_broadcast_consumer.rb b/app/models/warren/subscriber/queue_broadcast_consumer.rb index 933c70c628..89b4ac8954 100644 --- a/app/models/warren/subscriber/queue_broadcast_consumer.rb +++ b/app/models/warren/subscriber/queue_broadcast_consumer.rb @@ -40,7 +40,6 @@ def process # and if the class is Well, sets the subject_type to 'library_plate_well'. # # @param klass [Class] The ActiveRecord class to query (e.g., Well, Plate) - # @param json [Array, nil] The parsed JSON payload, expected to contain the class name and record ID # @return [ActiveRecord::Base] The found record, possibly modified def process_item(klass) item = klass.find(json.last) From 80893c87e61fff7fb6cbf4f53fbaf46e9d1d694e Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal Date: Mon, 5 Jan 2026 13:49:58 +0000 Subject: [PATCH 21/92] feat: Update process_item method to handle item target type and adjust subject type assignment for library plates --- app/models/warren/subscriber/queue_broadcast_consumer.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/models/warren/subscriber/queue_broadcast_consumer.rb b/app/models/warren/subscriber/queue_broadcast_consumer.rb index 89b4ac8954..b9738ca8b5 100644 --- a/app/models/warren/subscriber/queue_broadcast_consumer.rb +++ b/app/models/warren/subscriber/queue_broadcast_consumer.rb @@ -43,9 +43,12 @@ def process # @return [ActiveRecord::Base] The found record, possibly modified def process_item(klass) item = klass.find(json.last) - asset_type = SampleManifestAsset.find_by(asset_id: item.id)&.sample_manifest&.asset_type - item.subject_type = 'library_plate_well' if asset_type.present? && asset_type == 'library_plate' && klass == Well + if item.target_type == 'Receptacle' + asset_type = SampleManifestAsset.find_by(asset_id: item.target_id)&.sample_manifest&.asset_type + + item.subject_type = 'library_plate_well' if asset_type.present? && asset_type == 'library_plate' && klass == Well + end item end From f351497c22545646a28ab7ef3fc8ca3ffa3514e1 Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal Date: Mon, 12 Jan 2026 11:22:48 +0000 Subject: [PATCH 22/92] feat: Enhance message processing for receptacle targets and set labware type for library plates --- app/models/messenger.rb | 43 ++++++++++++++++++- .../subscriber/queue_broadcast_consumer.rb | 20 +-------- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/app/models/messenger.rb b/app/models/messenger.rb index f12e3bf46b..82d7148de0 100644 --- a/app/models/messenger.rb +++ b/app/models/messenger.rb @@ -15,7 +15,48 @@ def routing_key end def as_json(_options = {}) - { root => render_class.to_hash(target), 'lims' => configatron.amqp.lims_id! } + message = render_class.to_hash(target) + { root => process_receptacles(message), 'lims' => configatron.amqp.lims_id! } + end + + # Processes the message for receptacle targets, setting labware type if applicable. + # @param message [Hash] The message to process. + # @return [Hash] The processed message. + def process_receptacles(message) + return message unless receptacle_target? + + asset_type = fetch_asset_type + set_labware_type(message, asset_type) if library_plate?(asset_type) + message + end + + private + + # Checks if the target type is 'Receptacle'. + # @return [Boolean] True if target type is 'Receptacle', false otherwise. + def receptacle_target? + target_type == 'Receptacle' + end + + # Fetches the asset type for the current target. + # @return [String, nil] The asset type or nil if not found. + def fetch_asset_type + SampleManifestAsset.find_by(asset_id: target_id)&.sample_manifest&.asset_type + end + + # Determines if the asset type is a library plate. + # @param asset_type [String, nil] The asset type to check. + # @return [Boolean] True if asset type is 'library_plate', false otherwise. + def library_plate?(asset_type) + asset_type.present? && asset_type == 'library_plate' + end + + # Sets the labware type in the message if the asset is a library plate. + # @param message [Hash] The message to update. + # @param asset_type [String, nil] The asset type (unused). + # @return [void] + def set_labware_type(message, asset_type) + message['labware_type'] = 'library_plate_well' end def template diff --git a/app/models/warren/subscriber/queue_broadcast_consumer.rb b/app/models/warren/subscriber/queue_broadcast_consumer.rb index b9738ca8b5..d403f9f4fe 100644 --- a/app/models/warren/subscriber/queue_broadcast_consumer.rb +++ b/app/models/warren/subscriber/queue_broadcast_consumer.rb @@ -29,30 +29,12 @@ class Warren::Subscriber::QueueBroadcastConsumer < Warren::Subscriber::Base # with ID 1 and call `broadcast` on it. def process klass = json.first.constantize - item = process_item(klass) - item.broadcast + klass.find(json.last).broadcast rescue ActiveRecord::RecordNotFound # This may indicate that the record has been deleted debug "#{payload} not found." end - # Finds the record for the given class and JSON payload, checks if its asset type is 'library_plate', - # and if the class is Well, sets the subject_type to 'library_plate_well'. - # - # @param klass [Class] The ActiveRecord class to query (e.g., Well, Plate) - # @return [ActiveRecord::Base] The found record, possibly modified - def process_item(klass) - item = klass.find(json.last) - - if item.target_type == 'Receptacle' - asset_type = SampleManifestAsset.find_by(asset_id: item.target_id)&.sample_manifest&.asset_type - - item.subject_type = 'library_plate_well' if asset_type.present? && asset_type == 'library_plate' && klass == Well - end - - item - end - def json @json ||= extract_json end From 4a284e570cee37675718076516462c5b2b21da3d Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal Date: Mon, 12 Jan 2026 11:37:56 +0000 Subject: [PATCH 23/92] feat: Refactor template and resend methods for consistency and clarity --- app/models/messenger.rb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/models/messenger.rb b/app/models/messenger.rb index 82d7148de0..a469408c80 100644 --- a/app/models/messenger.rb +++ b/app/models/messenger.rb @@ -30,6 +30,17 @@ def process_receptacles(message) message end + def template + # Replace IO with Io to match the class name + # This is a consequence of the zeitwerk renaming for the message modules from IO to Io + # This ensures that the correct class is loaded for historical messages + read_attribute(:template).gsub(/IO$/, 'Io') + end + + def resend + Warren.handler << Warren::Message::Short.new(self) + end + private # Checks if the target type is 'Receptacle'. @@ -55,18 +66,7 @@ def library_plate?(asset_type) # @param message [Hash] The message to update. # @param asset_type [String, nil] The asset type (unused). # @return [void] - def set_labware_type(message, asset_type) + def set_labware_type(message, _asset_type) message['labware_type'] = 'library_plate_well' end - - def template - # Replace IO with Io to match the class name - # This is a consequence of the zeitwerk renaming for the message modules from IO to Io - # This ensures that the correct class is loaded for historical messages - read_attribute(:template).gsub(/IO$/, 'Io') - end - - def resend - Warren.handler << Warren::Message::Short.new(self) - end end From 00765500ab7893c14f3422e1862c131de2569ae2 Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal Date: Mon, 12 Jan 2026 11:49:07 +0000 Subject: [PATCH 24/92] feat: Update labware type assignment for library plates in message processing --- app/models/messenger.rb | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/app/models/messenger.rb b/app/models/messenger.rb index a469408c80..aa3f66d0e6 100644 --- a/app/models/messenger.rb +++ b/app/models/messenger.rb @@ -26,7 +26,7 @@ def process_receptacles(message) return message unless receptacle_target? asset_type = fetch_asset_type - set_labware_type(message, asset_type) if library_plate?(asset_type) + message['labware_type'] = 'library_plate_well' if library_plate?(asset_type) message end @@ -61,12 +61,4 @@ def fetch_asset_type def library_plate?(asset_type) asset_type.present? && asset_type == 'library_plate' end - - # Sets the labware type in the message if the asset is a library plate. - # @param message [Hash] The message to update. - # @param asset_type [String, nil] The asset type (unused). - # @return [void] - def set_labware_type(message, _asset_type) - message['labware_type'] = 'library_plate_well' - end end From 159551c1ff5d822a5a347a0a4d612d601d8153ba Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal Date: Fri, 16 Jan 2026 13:53:35 +0000 Subject: [PATCH 25/92] feat:adding logs to troubleshoot --- app/models/messenger.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/messenger.rb b/app/models/messenger.rb index aa3f66d0e6..d8ee8b6d3b 100644 --- a/app/models/messenger.rb +++ b/app/models/messenger.rb @@ -16,6 +16,7 @@ def routing_key def as_json(_options = {}) message = render_class.to_hash(target) + Rails.logger.info("Publishing message: #{message}") { root => process_receptacles(message), 'lims' => configatron.amqp.lims_id! } end From 2f266a4dcc8e3c13d5b41a84137b7c50c8d84da4 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Fri, 16 Jan 2026 16:48:30 +0000 Subject: [PATCH 26/92] test: move sample schema xml validation to rspec test --- .../data_release/12400603_update_accession.feature | 9 --------- features/support/step_definitions/samples_steps.rb | 11 ----------- spec/lib/accession/sample_spec.rb | 12 ++++++++++++ 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/features/data_release/12400603_update_accession.feature b/features/data_release/12400603_update_accession.feature index 830b48990f..f22a4f088d 100644 --- a/features/data_release/12400603_update_accession.feature +++ b/features/data_release/12400603_update_accession.feature @@ -13,13 +13,6 @@ Feature: object with an accession should be modifiable When I am on the event history page for study "study" Then I should see "Assigned study accession number" - Scenario: A sample XML should validate with the ENA schema - Given a sample named "sample" exists for accession - Given an accessioning webservice exists which returns a sample accession number "E-ERA-16" - Given I am on the show page for sample "sample" - And the UUID for the sample "sample" is "11111111-1111-1111-1111-1111111111" - When I create an accession number for sample "sample" - Then the XML sent for sample "sample" validates with the schema "test/data/xsd/SRA.sample.xsd" Scenario: A sample without an accession number should not send public name as alias but a uuid Given a sample named "sample" exists for accession @@ -29,7 +22,6 @@ Feature: object with an accession should be modifiable When I create an accession number for sample "sample" Then the XML root attribute "alias" sent to the accession service for sample "sample" should be "11111111-1111-1111-1111-1111111111" - And the XML sent for sample "sample" validates with the schema "test/data/xsd/SRA.sample.xsd" Scenario Outline: A sample should send a title element with sample public name falling back to sanger id if not present Given a sample named "sample" exists for accession @@ -40,7 +32,6 @@ Feature: object with an accession should be modifiable When I create an accession number for sample "sample" Then the XML tag "TITLE" sent to the accession service for sample "sample" should be - And the XML sent for sample "sample" validates with the schema "test/data/xsd/SRA.sample.xsd" Examples: | sample_public_name | sanger_sample_id | title | diff --git a/features/support/step_definitions/samples_steps.rb b/features/support/step_definitions/samples_steps.rb index 700fa68f82..a28e1a66a3 100644 --- a/features/support/step_definitions/samples_steps.rb +++ b/features/support/step_definitions/samples_steps.rb @@ -58,17 +58,6 @@ assert_equal(value, Nokogiri(xml).xpath("/SAMPLE_SET/SAMPLE/@#{xml_attr}").map(&:to_s)[0]) end -Then /^the XML sent for sample "([^"]+)" validates with the schema "([^"]+)"$/ do |sample_name, schema| - sample = Sample.find_by(name: sample_name) or - raise StandardError, "Cannot find sample with name #{sample_name.inspect}" - xml = FakeAccessionService.instance.sent.last['SAMPLE'].to_s - - # Schema downloaded from http://www.ebi.ac.uk/ena/submit/data-formats - xsd = Nokogiri::XML.Schema(File.open(schema)) - result = xsd.validate(Nokogiri(xml)) - assert(result.length == 0, result.map(&:message).join('')) -end - # rubocop:todo Layout/LineLength Then /^the XML tag "([^"]+)" sent to the accession service for sample "([^"]+)" should be not present$/ do |xml_attr, sample_name| # rubocop:enable Layout/LineLength diff --git a/spec/lib/accession/sample_spec.rb b/spec/lib/accession/sample_spec.rb index cd663963fc..fa817c9e65 100644 --- a/spec/lib/accession/sample_spec.rb +++ b/spec/lib/accession/sample_spec.rb @@ -370,6 +370,18 @@ def find_value_at_tag(xml_received, tag_name) expect(unexpected_tags).to be_empty, "Unexpected tags found in XML: '#{unexpected_tags.join("', '")}'" end + + it 'validates against the SAMPLE XSD schema' do + # Schema downloaded from https://ena-docs.readthedocs.io/en/latest/submit/general-guide/webin-v1.html + xsd_path = Rails.root.join('test/data/xsd/SRA.sample.xsd') + xsd_file = File.open(xsd_path) + schema = Nokogiri::XML::Schema(xsd_file) + document = Nokogiri::XML(xml) + errors = schema.validate(document) + expect(errors).to be_empty, "XML did not validate against XSD schema: #{errors.map(&:message).join('; ')}" + ensure + xsd_file.close + end end context 'with an OPEN study' do # study emphasised for easy test failure identification From 250e018117d166968d8ab7e0f0f208e1963ceaf8 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Thu, 15 Jan 2026 16:48:19 +0000 Subject: [PATCH 27/92] fix: remove y25_286 feature flag --- app/controllers/samples_controller.rb | 26 ++----- app/models/sample.rb | 7 -- config/feature_flags.yml | 1 - spec/controllers/samples_controller_spec.rb | 79 +++++++-------------- 4 files changed, 31 insertions(+), 82 deletions(-) diff --git a/app/controllers/samples_controller.rb b/app/controllers/samples_controller.rb index a1ac7978cf..724fe05863 100644 --- a/app/controllers/samples_controller.rb +++ b/app/controllers/samples_controller.rb @@ -149,7 +149,7 @@ def show_accession end end - def accession # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity + def accession # rubocop:disable Metrics/AbcSize,Metrics/MethodLength # @sample needs to be set before initially for use in the ensure block @sample = Sample.find(params[:id]) @@ -158,18 +158,11 @@ def accession # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Met raise AccessionService::AccessioningDisabledError, 'Accessioning is not enabled in this environment.' end + # Must check if an accession number is assigned _before_ performing the accession accession_action = @sample.accession_number? ? :update : :create - if Flipper.enabled?(:y25_286_accession_individual_samples_with_sample_accessioning_job) - # Synchronously perform accessioning job - Accession.accession_sample(@sample, current_user, perform_now: true) - else - # TODO: when removing the y25_286_accession_individual_samples_with_sample_accessioning_job feature flag - # and this accessioning path also remove the AccessionService and ActiveRecord errors below - @sample.validate_sample_for_accessioning! - accession_service = AccessionService.select_for_sample(@sample) - accession_service.submit_sample_for_user(@sample, current_user) - end + # Synchronously perform accessioning job + Accession.accession_sample(@sample, current_user, perform_now: true) if accession_action == :create flash[:notice] = "Accession number generated: #{@sample.sample_metadata.sample_ebi_accession_number}" @@ -178,17 +171,12 @@ def accession # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Met end # Handle errors for both synchronous and asynchronous accessioning - # When the feature flag above (y25_286_accession_individual_samples_with_sample_accessioning_job) is removed, - # the AccessionService and ActiveRecord errors should also be removed. These errors are only raised in the old - # synchronous accessioning code path and are not required for the updated SampleAccessioningJob path. - rescue ActiveRecord::RecordInvalid, Accession::InternalValidationError + rescue Accession::InternalValidationError flash[:error] = "Please fill in the required fields: #{@sample.errors.full_messages.join(', ')}" redirect_to(edit_sample_path(@sample)) # send the user to edit the sample - rescue AccessionService::NumberNotRequired => e - flash[:warning] = e.message || 'An accession number is not required for this study' - rescue AccessionService::NumberNotGenerated, Accession::ExternalValidationError => e + rescue Accession::ExternalValidationError => e flash[:warning] = "No accession number was generated: #{e.message}" - rescue AccessionService::AccessionServiceError, Accession::Error => e + rescue AccessionService::AccessioningDisabledError, Accession::Error => e flash[:error] = "Accessioning Service Failed: #{e.message}" rescue Faraday::Error => e flash[:error] = "Accessioning failed with a network error: #{e.message}" diff --git a/app/models/sample.rb b/app/models/sample.rb index 2384c605e8..0f9b3a2c5c 100644 --- a/app/models/sample.rb +++ b/app/models/sample.rb @@ -214,13 +214,6 @@ class Current < ActiveSupport::CurrentAttributes validates_associated :sample_metadata, on: %i[accession EGA ENA] - # TODO: should be removed along with the removal of the accessioning feature flag - # y25_286_accession_individual_samples_with_sample_accessioning_job - def tags - accession_service = AccessionService.select_for_sample(self) - self.class.tags.select { |tag| tag.for?(accession_service.provider) } - end - def self.tags @tags ||= [] end diff --git a/config/feature_flags.yml b/config/feature_flags.yml index 2f787db5fc..846c5d8318 100644 --- a/config/feature_flags.yml +++ b/config/feature_flags.yml @@ -11,6 +11,5 @@ y25_442_make_api_key_mandatory: Makes API key mandatory for all API requests # Accessioning y25_714_skip_accessioning_tag_validation: Skips internal validation of accessioning tags prior to sample accessioning -y25_286_accession_individual_samples_with_sample_accessioning_job: Accession individual samples with SampleAccessioningJob y25_705_notify_on_internal_accessioning_validation_failures: Sends email to developers when internal validation fails during accessioning y25_705_notify_on_external_accessioning_validation_failures: Sends email to developers when external validation fails during accessioning diff --git a/spec/controllers/samples_controller_spec.rb b/spec/controllers/samples_controller_spec.rb index 14fb256aa3..3625962f9c 100644 --- a/spec/controllers/samples_controller_spec.rb +++ b/spec/controllers/samples_controller_spec.rb @@ -111,21 +111,11 @@ end describe '#accession' do - let(:accession_individual_samples_with_sample_accessioning_job) { false } - before do - if accession_individual_samples_with_sample_accessioning_job - Flipper.enable :y25_286_accession_individual_samples_with_sample_accessioning_job - - create(:user, api_key: configatron.accession_local_key) # create contact user - allow(Accession::Submission).to receive(:client).and_return( - stub_accession_client(:submit_and_fetch_accession_number, return_value: 'EGA00001000240') - ) - else - Flipper.disable :y25_286_accession_individual_samples_with_sample_accessioning_job - - allow_any_instance_of(RestClient::Resource).to receive(:post).and_return(successful_sample_accession_response) - end + create(:user, api_key: configatron.accession_local_key) # create contact user + allow(Accession::Submission).to receive(:client).and_return( + stub_accession_client(:submit_and_fetch_accession_number, return_value: 'EGA00001000240') + ) get :accession, params: { id: sample.id }, @@ -187,8 +177,7 @@ it 'displays an error message indicating the validation failure' do expect(flash[:error]).to eq(<<~MSG.squish) Please fill in the required fields: - Sample metadata gender is required, Sample metadata phenotype is required, - Sample metadata donor is required + Sample does not have the required metadata: donor-id, gender, and phenotype. MSG end end @@ -200,56 +189,36 @@ before { sample.reload } # Reload to get updated accession number - context 'when the accession_individual_samples_with_sample_accessioning_job feature flag is disabled' do - let(:accession_individual_samples_with_sample_accessioning_job) { false } - - it 'assigns an accession number to the sample' do - expect(sample.ebi_accession_number).to eq('EGA00001000240') - end + it 'assigns an accession number to the sample' do + expect(sample.ebi_accession_number).to eq('EGA00001000240') + end - it 'redirects to the sample page' do - expect(response).to redirect_to(sample_path(sample.id)) - end + it 'redirects to the sample page' do + expect(response).to redirect_to(sample_path(sample.id)) + end - it 'displays a notice message with the generated accession number' do - expect(flash[:notice]).to eq("Accession number generated: #{sample.ebi_accession_number}") - end + it 'displays a notice message with the generated accession number' do + expect(flash[:notice]).to eq("Accession number generated: #{sample.ebi_accession_number}") end - context 'when the accession_individual_samples_with_sample_accessioning_job feature flag is enabled' do - let(:accession_individual_samples_with_sample_accessioning_job) { true } + context 'when a network error occurs during accessioning' do + before do + allow(Accession::Submission).to receive(:client).and_return( + stub_accession_client(:submit_and_fetch_accession_number, + raise_error: Faraday::ConnectionFailed.new('Network connection failed')) + ) - it 'assigns an accession number to the sample' do - expect(sample.ebi_accession_number).to eq('EGA00001000240') + get :accession, + params: { id: sample.id }, + session: { user: current_user.id } end it 'redirects to the sample page' do expect(response).to redirect_to(sample_path(sample.id)) end - it 'displays a notice message with the generated accession number' do - expect(flash[:notice]).to eq("Accession number generated: #{sample.ebi_accession_number}") - end - - context 'when a network error occurs during accessioning' do - before do - allow(Accession::Submission).to receive(:client).and_return( - stub_accession_client(:submit_and_fetch_accession_number, - raise_error: Faraday::ConnectionFailed.new('Network connection failed')) - ) - - get :accession, - params: { id: sample.id }, - session: { user: current_user.id } - end - - it 'redirects to the sample page' do - expect(response).to redirect_to(sample_path(sample.id)) - end - - it 'displays an error message indicating a network error occurred' do - expect(flash[:error]).to eq('Accessioning failed with a network error: Network connection failed') - end + it 'displays an error message indicating a network error occurred' do + expect(flash[:error]).to eq('Accessioning failed with a network error: Network connection failed') end end From a2806da7f9ebedec8a13fef4d05b81c7e04877a5 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Thu, 15 Jan 2026 16:58:53 +0000 Subject: [PATCH 28/92] refactor: remove references to Accessionable::Sample --- app/models/accession_service.rb | 28 +--- app/models/accession_service/base_service.rb | 8 - app/models/accession_service/no_service.rb | 5 - .../accession_service/unsuitable_service.rb | 5 - app/models/accessionable/sample.rb | 129 --------------- app/models/accessionable/study.rb | 2 +- .../12400603_update_accession.feature | 53 ------ .../4491710_ega_integration.feature | 120 -------------- ...5177179_updating_released_samples_steps.rb | 19 --- spec/models/accession_service_spec.rb | 151 ------------------ test/unit/accession_service_test.rb | 137 ---------------- 11 files changed, 2 insertions(+), 655 deletions(-) delete mode 100644 app/models/accessionable/sample.rb delete mode 100644 features/data_release/4491710_ega_integration.feature delete mode 100644 features/support/step_definitions/35177179_updating_released_samples_steps.rb delete mode 100644 test/unit/accession_service_test.rb diff --git a/app/models/accession_service.rb b/app/models/accession_service.rb index e4b40b9ea5..e9cfaa89a2 100644 --- a/app/models/accession_service.rb +++ b/app/models/accession_service.rb @@ -15,7 +15,6 @@ # # Accessionables # -------------- -# {Accessionable::Sample} Represents information about the sample, maps to a Sequencescape {Sample}. # {Accessionable::Study} Represents information about the study. Indicates WHY the samples have been sequenced. # Maps to a Sequencescape {Study}. # {Accessionable::Submission} Wrapper object required by the submission service. We use one per accessionable. @@ -23,7 +22,7 @@ # {Accessionable::Dac} Data access committee. Information about who to contact to gain access to the data. (EGA) # {Accessionable::Policy} Details about how the data may be used. (EGA) # -# Accessioning of samples has been partially migrated to {Accession 'a separate accession library'} +# Accessioning of samples has been fully migrated to {Accession 'a separate accession library'} module AccessionService # Define custom error classes for the AccessionService AccessionServiceError = Class.new(StandardError) @@ -51,29 +50,4 @@ def self.select_for_study(study) AccessionService::NoService.new(study) end end - - # Return the highest priority accession service that this sample should use. - # - # @param sample [Sample] The sample for which to select the accession service. - # @return [AccessionService::BaseService] An instance of the selected accession service - def self.select_for_sample(sample) - services = sample.studies.group_by { |study| AccessionService.select_for_study(study).priority } - return AccessionService::UnsuitableService.new([]) if services.empty? - - highest_priority = services.keys.max - suitable_study = services[highest_priority].detect { |study| send_samples_to_service?(study) } - return AccessionService.select_for_study(suitable_study) if suitable_study - - AccessionService::UnsuitableService.new(services[highest_priority]) - end - - # Determines if samples from the given study should be sent to the accession service. - # - # @param study [Study] The study to check. - # @return [Boolean] True if the samples can be sent without the study requiring accessioning, - # or if the study has already been accessioned, false otherwise. - def self.send_samples_to_service?(study) - accession_service = select_for_study(study) - accession_service.no_study_accession_needed || (!study.study_metadata.never_release? && study.accession_number?) - end end diff --git a/app/models/accession_service/base_service.rb b/app/models/accession_service/base_service.rb index 160cd4912c..7d93b1fcb3 100644 --- a/app/models/accession_service/base_service.rb +++ b/app/models/accession_service/base_service.rb @@ -110,10 +110,6 @@ def submit(user, *accessionables) accessionables.map(&:accession_number) end - def submit_sample_for_user(sample, user) - submit(user, Accessionable::Sample.new(sample)) - end - def submit_study_for_user(study, user) unless study.accession_required? raise AccessionService::NumberNotRequired, @@ -144,10 +140,6 @@ def accession_study_xml(study) Accessionable::Study.new(study).xml end - def accession_sample_xml(sample) - Accessionable::Sample.new(sample).xml - end - def accession_policy_xml(study) policy = Accessionable::Policy.new(study) policy.xml diff --git a/app/models/accession_service/no_service.rb b/app/models/accession_service/no_service.rb index 1567b505b9..ae07f0d9e5 100644 --- a/app/models/accession_service/no_service.rb +++ b/app/models/accession_service/no_service.rb @@ -14,11 +14,6 @@ def submit(_user, *_accessionables) raise AccessionService::NumberNotRequired, I18n.t(:not_applicable_study, scope: 'accession_service.not_required') end - def submit_sample_for_user(_sample, _user) - raise AccessionService::NumberNotRequired, - I18n.t(:not_applicable_study_for_sample, scope: 'accession_service.not_required', study_id: @study_id) - end - def submit_study_for_user(_study, _user) raise AccessionService::NumberNotRequired, I18n.t(:not_applicable_study, scope: 'accession_service.not_required') end diff --git a/app/models/accession_service/unsuitable_service.rb b/app/models/accession_service/unsuitable_service.rb index 275bc10cea..a07cf238c0 100644 --- a/app/models/accession_service/unsuitable_service.rb +++ b/app/models/accession_service/unsuitable_service.rb @@ -16,11 +16,6 @@ def submit(_user, *_accessionables) I18n.t(:no_suitable_study, scope: 'accession_service.unsuitable', study_ids: @study_ids.to_sentence) end - def submit_sample_for_user(_sample, _user) - raise AccessionService::NumberNotGenerated, - I18n.t(:no_suitable_study, scope: 'accession_service.unsuitable', study_ids: @study_ids.to_sentence) - end - def submit_study_for_user(_study, _user) raise StandardError, # rubocop:todo Layout/LineLength diff --git a/app/models/accessionable/sample.rb b/app/models/accessionable/sample.rb deleted file mode 100644 index b50ccfe1e9..0000000000 --- a/app/models/accessionable/sample.rb +++ /dev/null @@ -1,129 +0,0 @@ -# frozen_string_literal: true -# Handles the submission of {Sample} information to the ENA or EGA -# It should have a 1 to 1 mapping with Sequencescape {Sample samples}. -module Accessionable - class Sample < Base - ARRAY_EXPRESS_FIELDS = %w[ - genotype - phenotype - strain_or_line - developmental_stage - sex - cell_type - disease_state - compound - dose - immunoprecipitate - growth_condition - rnai - organism_part - species - time_point - age - treatment - ].freeze - - attr_reader :common_name, :taxon_id, :links, :tags - - # rubocop:todo Metrics/MethodLength, Metrics/AbcSize - def initialize(sample) # rubocop:todo Metrics/CyclomaticComplexity - @sample = sample - super(sample.ebi_accession_number) - - sampname = sample.sample_metadata.sample_public_name - @name = sampname.presence || sample.name - @name = @name.gsub(/[^a-z\d]/i, '_') if @name.present? - - @common_name = sample.sample_metadata.sample_common_name - @taxon_id = sample.sample_metadata.sample_taxon_id - - # Tags from the 'ENA attributes' property group - # NOTE[xxx]: This used to also look for 'ENA links' and push them to the 'data[:links]' value, but group was empty - @links = [] - @tags = - sample.tags.map { |datum| Tag.new(label_scope, datum.name, sample.sample_metadata[datum.tag], datum.downcase) } - - # TODO: maybe unify this with the previous loop - # Don't send managed AE data to SRA - accession_service = AccessionService.select_for_sample(sample) - unless accession_service.private? - ARRAY_EXPRESS_FIELDS.each do |datum| - value = sample.sample_metadata.send(datum) - next if value.blank? - - @tags << ArrayExpressTag.new(label_scope, datum, value) - end - end - - sample_hold = sample.sample_metadata.sample_sra_hold - @hold = sample_hold.presence || 'hold' - end - - # rubocop:enable Metrics/AbcSize, Metrics/MethodLength - - def accessionable_id - @sample.id - end - - def alias - @sample.uuid - end - - def title - @sample.sample_metadata.sample_public_name || @sample.sanger_sample_id - end - - def sample_element_attributes - # In case the accession number is defined, we won't send the alias - { alias: self.alias, accession: accession_number }.tap { |obj| obj.delete(:alias) if accession_number.present? } - end - - # rubocop:todo Metrics/MethodLength - def xml # rubocop:todo Metrics/AbcSize - xml = Builder::XmlMarkup.new - xml.instruct! - xml.SAMPLE_SET('xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance') do - xml.SAMPLE(sample_element_attributes) do - xml.TITLE title unless title.nil? - xml.SAMPLE_NAME do - xml.COMMON_NAME common_name - xml.TAXON_ID taxon_id - end - xml.SAMPLE_ATTRIBUTES { tags.each { |tag| xml.SAMPLE_ATTRIBUTE { tag.build(xml) } } } if tags.present? - - xml.SAMPLE_LINKS {} if links.present? - end - end - xml.target! - end - - # rubocop:enable Metrics/MethodLength - - def update_accession_number!(user, accession_number) - @accession_number = accession_number - @sample.sample_metadata.sample_ebi_accession_number = accession_number - @sample.sample_metadata.save! # prevent an infinite loop due to after_save callbacks on sample.save - @sample.events.assigned_accession_number!('sample', accession_number, user) - end - - def protect?(service) - service.sample_visibility(@sample) == AccessionService::PROTECT - end - - delegate :released?, to: :@sample - end - - class ArrayExpressTag < Base::Tag - def label - default_tag = "ArrayExpress-#{I18n.t("#{@scope}.#{@name}.label").tr(' ', '_').camelize}" - I18n.t("#{@scope}.#{@name}.ena_label", default: default_tag) - end - end - - class EgaTag < Base::Tag - def label - default_tag = "EGA-#{I18n.t("#{@scope}.#{@name}.label").tr(' ', '_').camelize}" - I18n.t("#{@scope}.#{@name}.ena_label", default: default_tag) - end - end -end diff --git a/app/models/accessionable/study.rb b/app/models/accessionable/study.rb index 2c2a30b9c4..8ae129f812 100644 --- a/app/models/accessionable/study.rb +++ b/app/models/accessionable/study.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true # Handles submission of {Study} information to the EGA or ENA -# A study gathers together multiple {Accessionable::Sample samples} and essentially +# A study gathers together multiple samples and essentially # describes why they are being sequenced. It should have a 1 to 1 mapping with Sequencescape # {Study studies}. # A study can either be open (ENA) or managed (EGA) which determines which {AccessionService} it diff --git a/features/data_release/12400603_update_accession.feature b/features/data_release/12400603_update_accession.feature index f22a4f088d..55ec3b0145 100644 --- a/features/data_release/12400603_update_accession.feature +++ b/features/data_release/12400603_update_accession.feature @@ -12,56 +12,3 @@ Feature: object with an accession should be modifiable When I am on the event history page for study "study" Then I should see "Assigned study accession number" - - - Scenario: A sample without an accession number should not send public name as alias but a uuid - Given a sample named "sample" exists for accession - Given an accessioning webservice exists which returns a sample accession number "E-ERA-16" - Given I am on the show page for sample "sample" - And the UUID for the sample "sample" is "11111111-1111-1111-1111-1111111111" - When I create an accession number for sample "sample" - - Then the XML root attribute "alias" sent to the accession service for sample "sample" should be "11111111-1111-1111-1111-1111111111" - - Scenario Outline: A sample should send a title element with sample public name falling back to sanger id if not present - Given a sample named "sample" exists for accession - Given an accessioning webservice exists which returns a sample accession number "E-ERA-16" - Given I am on the show page for sample "sample" - And the metadata attribute "sample_public_name" of the sample "sample" is <sample_public_name> - And the attribute "sanger_sample_id" of the sample "sample" is <sanger_sample_id> - When I create an accession number for sample "sample" - - Then the XML tag "TITLE" sent to the accession service for sample "sample" should be <title> - - Examples: - | sample_public_name | sanger_sample_id | title | - | "this is a public name of sample" | "this is a sanger_sample_id of sample" | "this is a public name of sample" | - | "this is a public name of sample" | "empty" | "this is a public name of sample" | - | "empty" | "this is a sanger_sample_id of sample" | "this is a sanger_sample_id of sample" | - | "empty" | "empty" | not present | - - Scenario: A sample with already an accession number should add updated in the history - Given a sample named "sample" exists for accession - And the sample "sample" has the accession number "E-ERA-16" - Given an accessioning webservice exists which returns a sample accession number "E-ERA-16" - When I update an accession number for sample "sample" - - When I am on the event history page for sample "sample" - Then I should see "Assigned sample accession number" - And I should see "me" - - Scenario: A sample with already an accession number should update itself using its accession number - Given a sample named "sample" exists for accession - And the sample "sample" has the accession number "E-ERA-16" - Given an accessioning webservice exists which returns a sample accession number "E-ERA-16" - When I update an accession number for sample "sample" - - Then I should not have sent the attribute "alias" for the sample element to the accessioning service - And I should have sent the attribute "accession" for the sample element to the accessioning service - - Scenario: A sample without an accession number should update itself using its alias - Given a sample named "sample" exists for accession - Given an accessioning webservice exists which returns a sample accession number "E-ERA-16" - When I create an accession number for sample "sample" - - Then I should have sent the attribute "alias" for the sample element to the accessioning service diff --git a/features/data_release/4491710_ega_integration.feature b/features/data_release/4491710_ega_integration.feature deleted file mode 100644 index b675c6b6b9..0000000000 --- a/features/data_release/4491710_ega_integration.feature +++ /dev/null @@ -1,120 +0,0 @@ -@sample @accession_number @accession-service -Feature: Generate accession nubmers for a sample - Background: - Given I am logged in as "user123" - - Scenario: I am not the owner of a sample - Given a study named "Study 4491710" exists - Given study "Study 4491710" has a registered sample "Sample4491710" - And an accession number is required for study "Study 4491710" - Given I am on the show page for sample "Sample4491710" - Then I should not see "Generate Accession Number" - - Scenario: The sample has no study - Given a sample named "Sample4491710" exists - Given I am the owner of sample "Sample4491710" - Given I am on the show page for sample "Sample4491710" - Given an accessioning webservice exists which returns a sample accession number "EGAN00001000234" - When I follow "Generate Accession Number" - Then I should not see "Accession number generated: EGAN00001000234" - - Scenario: The sample doesn't have the required properties filled in - Given a study named "Study 4491710" exists - And the study "Study 4491710" is a "Whole Genome Sequencing" study - And the title of study "Study 4491710" is "Checking sample validation" - And the description of study "Study 4491710" is "The study is valid, the sample is not" - And the abstract of study "Study 4491710" is "Good study, bad sample" - - Given the study "Study 4491710" is a "genomic sequencing" study for data release - And the study "Study 4491710" has an open data release strategy - And the study "Study 4491710" data release timing is standard - - Given study "Study 4491710" has a registered sample "Sample4491710" - And an accession number is required for study "Study 4491710" - - Given I am the owner of sample "Sample4491710" - - Given I am on the show page for sample "Sample4491710" - Given an accessioning webservice exists which returns a sample accession number "EGAN00001000234" - When I follow "Generate Accession Number" - Then I should not see "Accession number generated: EGAN00001000234" - - Scenario: Study doesn't have any of the required properties filled in - Given a study named "Study 4491710" exists - And the abstract of study "Study 4491710" is "" - Given study "Study 4491710" has a registered sample "Sample4491710" - And an accession number is required for study "Study 4491710" - - Given I am the owner of sample "Sample4491710" - And the sample "Sample4491710" has the Taxon ID "99999" - And the sample "Sample4491710" has the common name "Human" - - Given I am on the show page for sample "Sample4491710" - Given an accessioning webservice exists which returns a sample accession number "EGAN00001000234" - When I follow "Generate Accession Number" - Then I should not see "Accession number generated: EGAN00001000234" - - - Scenario Outline: Study doesn't have some of the required data release properties filled in - Given a study named "Study 4491710" exists - And the study "Study 4491710" is a "<type>" study - And the title of study "Study 4491710" is "<title>" - And the description of study "Study 4491710" is "Description of study" - And the abstract of study "Study 4491710" is "<study_abstract>" - - Given the study "Study 4491710" is a "genomic sequencing" study for data release - And the study "Study 4491710" has a <Strategy> data release strategy - And the study "Study 4491710" data release timing is standard - - Given study "Study 4491710" has a registered sample "Sample4491710" - And an accession number is required for study "Study 4491710" - - Given I am the owner of sample "Sample4491710" - And the sample "Sample4491710" has the Taxon ID "99999" - And the sample "Sample4491710" has the common name "Human" - - Given I am on the show page for sample "Sample4491710" - Given an accessioning webservice exists which returns a sample accession number "EGAN00001000234" - When I follow "Generate Accession Number" - Then I should not see "Accession number generated: EGAN00001000234" - # NOTE: strategy, timing and description cannot be empty by definition - Examples: - | Strategy | title | type | study_abstract | - | open | My title | Not specified | abstract | - | managed | My title | Not specified | abstract | - - Scenario Outline: Sample is released to EBI - Given a study named "Study 4491710" exists - And the study "Study 4491710" is a "Whole Genome Sequencing" study - And the title of study "Study 4491710" is "My title" - And the description of study "Study 4491710" is "Description of study" - And the abstract of study "Study 4491710" is "My abstract" - And the faculty sponsor for study "Study 4491710" is "John Doe" - - Given the study "Study 4491710" is a "genomic sequencing" study for data release - And the study "Study 4491710" has a <data_release_strategy> data release strategy - And the study "Study 4491710" data release timing is standard - And the study "Study 4491710" has samples contaminated with human DNA - And the study "Study 4491710" contains human DNA - And the study "Study 4491710" contains samples commercially available - And study "Study 4491710" has an accession number - - Given study "Study 4491710" has a registered sample "Sample4491710" - And an accession number is required for study "Study 4491710" - - Given I am the owner of sample "Sample4491710" - And the sample "Sample4491710" has the Taxon ID "99999" - And the sample "Sample4491710" has the common name "Human" - And the sample "Sample4491710" has the phenotype "Healthy" - And the sample "Sample4491710" has the gender "Female" - And the sample "Sample4491710" has the donor id "D0N0R" - - Given I am on the show page for sample "Sample4491710" - Given an accessioning webservice exists which returns a sample accession number "<accession_number>" - When I follow "Generate Accession Number" - Then I should see "Accession number generated: <accession_number>" - - Examples: - | data_release_strategy | accession_number | - | open | EGAN00001000234 | - | managed | EGAN00001000234 | diff --git a/features/support/step_definitions/35177179_updating_released_samples_steps.rb b/features/support/step_definitions/35177179_updating_released_samples_steps.rb deleted file mode 100644 index a7e575f91c..0000000000 --- a/features/support/step_definitions/35177179_updating_released_samples_steps.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -Given /^the sample name "([^"]*)" has previously been released$/ do |name| - Sample.find_by(name:).release -end - -When /^ignoring "([^"]+)" the XML submission for the sample "([^"]*)" should be:$/ do |key_regexp, name, serialized_xml| - sample = Sample.find_by(name:) or raise StandardError, "Cannot find sample with name #{name.inspect}" - accession_service = AccessionService.select_for_sample(sample) - accessionable_sample = Accessionable::Sample.new(sample) - submission = Accessionable::Submission.new(accession_service, User.find_by(login: 'me'), accessionable_sample) - regexp = Regexp.new(key_regexp) - block = ->(key) { key.to_s =~ regexp } - assert_hash_equal( - sort_arrays(walk_hash_structure(Hash.from_xml(serialized_xml), &block)), - sort_arrays(walk_hash_structure(Hash.from_xml(submission.xml), &block)), - 'XML differs when decoded' - ) -end diff --git a/spec/models/accession_service_spec.rb b/spec/models/accession_service_spec.rb index 79ab43147e..394ce6bc44 100644 --- a/spec/models/accession_service_spec.rb +++ b/spec/models/accession_service_spec.rb @@ -27,155 +27,4 @@ end end end - - describe '.send_samples_to_service?' do - context 'when no study accession needed' do - let(:study) { create(:not_app_study) } - - it 'returns true' do - expect(described_class.send_samples_to_service?(study)).to be(true) - end - end - - context 'when study accession is required' do - let(:study) { create(:open_study, study_metadata:, accession_number:) } - - context 'when never release is false' do - let(:study_metadata) { create(:study_metadata, data_release_timing: Study::DATA_RELEASE_TIMING_STANDARD) } - - context 'when study accession number is present' do - let(:accession_number) { 'ENA123' } - - it 'returns true' do - expect(described_class.send_samples_to_service?(study)).to be(true) - end - end - - context 'when study accession number is absent' do - let(:accession_number) { nil } - - it 'returns false' do - expect(described_class.send_samples_to_service?(study)).to be(false) - end - end - end - - context 'when never release is true' do - let(:study_metadata) do - create(:study_metadata, - data_release_timing: Study::DATA_RELEASE_TIMING_NEVER, - data_release_prevention_reason: - 'Prevent harm (e.g sensitive studies or biosecurity) - DAC approval required') - end - - context 'when study accession number is present' do - let(:accession_number) { 'ENA123' } - - it 'returns false' do - expect(described_class.send_samples_to_service?(study)).to be(false) - end - end - - context 'when study accession number is absent' do - let(:accession_number) { nil } - - it 'returns false' do - expect(described_class.send_samples_to_service?(study)).to be(false) - end - end - end - end - end - - describe '.select_for_sample' do - let(:sample) { create(:sample, studies:) } - - context 'when sample has one open study' do - let(:open_study) { create(:open_study, accession_number: 'ENA123') } - let(:studies) { [open_study] } - - it 'returns an instance of the ENA service' do - expect(described_class.select_for_sample(sample)).to be_a(AccessionService::ENAService) - end - end - - context 'when sample has one managed study' do - let(:managed_study) { create(:managed_study, accession_number: 'EGA123') } - let(:studies) { [managed_study] } - - it 'returns an instance of the EGA service' do - expect(described_class.select_for_sample(sample)).to be_a(AccessionService::EGAService) - end - end - - context 'when sample has one un-accessioned study' do - let(:open_study) { create(:open_study) } - let(:studies) { [open_study] } - - it 'returns UnsuitableService' do - expect(described_class.select_for_sample(sample)).to be_a(AccessionService::UnsuitableService) - end - end - - context 'when sample has one study that does not require accessioning' do - let(:other_study) { create(:not_app_study) } - let(:studies) { [other_study] } - - it 'returns NoService' do - expect(described_class.select_for_sample(sample)).to be_a(AccessionService::NoService) - end - end - - context 'when sample has multiple eligible studies' do - let(:open_study) { create(:open_study, accession_number: 'ENA123') } - let(:managed_study) { create(:managed_study, accession_number: 'EGA123') } - let(:other_study) { create(:not_app_study) } - let(:studies) { [open_study, managed_study, other_study] } - - it 'returns an instance of the highest priority accession service' do - expect(described_class.select_for_sample(sample)).to be_a(AccessionService::EGAService) - end - end - - context 'when sample has no eligible studies' do - let(:sample) { create(:sample, studies: []) } - - it 'returns UnsuitableService' do - expect(described_class.select_for_sample(sample)).to be_a(AccessionService::UnsuitableService) - end - end - - # We prioritise the EGA, as its the more conservative of the two databases - # and it reduces the risk of accidentally making human data public - context 'when sample has an open study and a managed study' do - let(:open_study) { create(:open_study, accession_number: 'ENA123') } - let(:managed_study) { create(:managed_study, accession_number: 'EGA123') } - let(:studies) { [open_study, managed_study] } - - it 'returns an instance of the EGA service' do - expect(described_class.select_for_sample(sample)).to be_a(AccessionService::EGAService) - end - end - - context 'when sample has a managed study and an open study (in the other order)' do - let(:managed_study) { create(:managed_study, accession_number: 'EGA123') } - let(:open_study) { create(:open_study, accession_number: 'ENA123') } - let(:studies) { [managed_study, open_study] } - - it 'returns an instance of the EGA service' do - expect(described_class.select_for_sample(sample)).to be_a(AccessionService::EGAService) - end - end - - # We err on the side of caution here - inadvertently sending data to the ENA could be an issue. - context 'when sample has an accessioned open study but un-accessioned managed study' do - let(:open_study) { create(:open_study, accession_number: 'ENA123') } - let(:managed_study) { create(:managed_study) } - let(:studies) { [open_study, managed_study] } - - it 'returns UnsuitableService' do - expect(described_class.select_for_sample(sample)).to be_a(AccessionService::UnsuitableService) - end - end - end end diff --git a/test/unit/accession_service_test.rb b/test/unit/accession_service_test.rb deleted file mode 100644 index 2cb1746a21..0000000000 --- a/test/unit/accession_service_test.rb +++ /dev/null @@ -1,137 +0,0 @@ -# frozen_string_literal: true - -require "#{File.dirname(__FILE__)}/../test_helper" - -class AccessionServiceTest < ActiveSupport::TestCase - def assert_tag(tag_label, value) - acc = Accessionable::Sample.new(@sample) - tag = acc.tags.detect { |tag| tag.label == tag_label } - assert tag, "Could not find #{tag} in #{acc.tags.map(&:label).join(',')}" - subject_tag = { tag: tag.label, value: tag.value } - assert_equal({ tag: tag_label, value: value }, subject_tag) - end - - # temporary test for hotfix - context 'A sample with a strain' do - setup do - @study = create(:open_study, accession_number: 'accss') - @sample = create(:sample, studies: [@study]) - @sample.sample_metadata.sample_strain_att = 'my strain' - end - - should 'expose strain in ERA xml' do - assert_tag('strain', 'my strain') - end - end - - context 'A sample with a gender' do - setup do - @study = create(:managed_study, accession_number: 'accss') - @sample = create(:sample, studies: [@study]) - @sample.sample_metadata.gender = 'male' - end - - should 'expose gender in EGA xml' do - assert_tag('gender', 'male') - end - end - - context 'A sample with a donor_id' do - setup do - @study = create(:managed_study, accession_number: 'accss') - @sample = create(:sample, studies: [@study]) - @sample.sample_metadata.donor_id = '123456789' - end - - should 'expose donor_id as subject id in EGA xml' do - assert_tag('subject id', '123456789') - end - - should 'dupe test' do - assert_tag('subject id', '123456789') - end - end - - context 'A sample with a country_of_origin' do - setup do - @country = create(:insdc_country, name: 'Freedonia') - @study = create(:managed_study, accession_number: 'accss') - @sample = create(:sample, studies: [@study]) - end - - context 'with unexistent country' do - setup { @sample.sample_metadata.country_of_origin = 'Pepe' } - should 'send the default error value' do - assert_tag('geographic location (country and/or sea)', 'not provided') - end - end - - context 'with no country' do - should 'send the default error value' do - assert_tag('geographic location (country and/or sea)', 'not provided') - end - end - - context 'with right country' do - setup { @sample.sample_metadata.country_of_origin = 'Freedonia' } - should 'send the country name' do - assert_tag('geographic location (country and/or sea)', 'Freedonia') - end - end - - context 'with other defined values for country_of_origin' do - setup { @sample.sample_metadata.country_of_origin = 'not provided' } - should 'send the collection date' do - assert_tag('geographic location (country and/or sea)', 'not provided') - end - end - - context 'with missing for country_of_origin' do - setup { @sample.sample_metadata.country_of_origin = 'missing: endangered species' } - should 'send the collection date' do - assert_tag('geographic location (country and/or sea)', 'missing: endangered species') - end - end - end - - context 'A sample with a collection date' do - setup do - @study = create(:managed_study, accession_number: 'accss') - @sample = create(:sample, studies: [@study]) - end - - context 'with unexistent date_of_sample_collection' do - setup { @sample.sample_metadata.date_of_sample_collection = 'Pepe' } - should 'send the default error value' do - assert_tag('collection date', 'not provided') - end - end - - context 'with no date_of_sample_collection' do - should 'send the default error value' do - assert_tag('collection date', 'not provided') - end - end - - context 'with right date_of_sample_collection' do - setup { @sample.sample_metadata.date_of_sample_collection = '2023-04-25T00:00:00Z' } - should 'send the collection date' do - assert_tag('collection date', '2023-04-25T00:00:00Z') - end - end - - context 'with other defined values for date_of_sample_collection' do - setup { @sample.sample_metadata.date_of_sample_collection = 'not provided' } - should 'send the collection date' do - assert_tag('collection date', 'not provided') - end - end - - context 'with missing for date_of_sample_collection' do - setup { @sample.sample_metadata.date_of_sample_collection = 'missing: endangered species' } - should 'send the collection date' do - assert_tag('collection date', 'missing: endangered species') - end - end - end -end From 727c84f4c42baaadc47e85f9f9804b7caf31d06d Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Thu, 15 Jan 2026 17:07:47 +0000 Subject: [PATCH 29/92] refactor: remove references to accession_local_key and contact_user --- app/jobs/sample_accessioning_job.rb | 8 +---- config/config.rb | 2 -- lib/accession.rb | 2 -- lib/accession/contact.rb | 25 --------------- lib/accession/submission.rb | 8 ++--- spec/controllers/samples_controller_spec.rb | 1 - spec/controllers/studies_controller_spec.rb | 1 - spec/factories/accession/submissions.rb | 3 +- .../studies/accession_all_samples_spec.rb | 2 +- spec/jobs/sample_accessioning_job_spec.rb | 6 +--- spec/lib/accession/contact_spec.rb | 31 ------------------- spec/lib/accession/study_spec.rb | 1 - spec/lib/accession/submission_spec.rb | 26 +++++----------- spec/lib/accession_spec.rb | 4 --- spec/models/sample_manifest/uploader_spec.rb | 2 +- 15 files changed, 15 insertions(+), 107 deletions(-) delete mode 100644 lib/accession/contact.rb delete mode 100644 spec/lib/accession/contact_spec.rb diff --git a/app/jobs/sample_accessioning_job.rb b/app/jobs/sample_accessioning_job.rb index 1fdb7f8088..c8286ca44c 100644 --- a/app/jobs/sample_accessioning_job.rb +++ b/app/jobs/sample_accessioning_job.rb @@ -8,14 +8,8 @@ # @see Accession::Submission SampleAccessioningJob = Struct.new(:accessionable, :event_user) do - # Retrieve the contact user for accessioning submissions - def self.contact_user - User.find_by(api_key: configatron.accession_local_key) - end - def perform - contact_user = self.class.contact_user - submission = Accession::Submission.new(contact_user, accessionable) + submission = Accession::Submission.new(accessionable) accessionable.validate! # See Accession::Sample.validate! in lib/accession/sample.rb submission.submit_accession(event_user) rescue StandardError => e diff --git a/config/config.rb b/config/config.rb index 6d5e21de80..2567f22dc5 100644 --- a/config/config.rb +++ b/config/config.rb @@ -102,7 +102,6 @@ configatron.data_sharing_contact.name = 'Datasharing' configatron.data_sharing_contact.email = 'datasharing@example.com' - configatron.accession_local_key = 'abc' configatron.sequencescape_email = 'sequencescape@example.com' configatron.default_email_domain = 'example.com' configatron.run_information_url = 'http://example.com/' @@ -156,7 +155,6 @@ configatron.data_sharing_contact.name = 'Datasharing' configatron.data_sharing_contact.email = 'datasharing@example.com' - configatron.accession_local_key = 'abc' configatron.sequencescape_email = 'sequencescape@example.com' configatron.default_email_domain = 'example.com' configatron.run_information_url = 'http://example.com/' diff --git a/lib/accession.rb b/lib/accession.rb index 764b0701b4..086d13ead0 100644 --- a/lib/accession.rb +++ b/lib/accession.rb @@ -5,7 +5,6 @@ module Accession # check configuration settings, in particular: # configatron.proxy # configatron.accession url, ega.user, ega.password, ena.user, ena.password - # configatron.accession_local_key (authorised user uuid) # check that Sequencescape sample sample_metadata meets accessioning requirements # configatron.accession_samples flag should be set to true to automatically accession a sample after save # (app/models/sample.rb) @@ -48,7 +47,6 @@ def <=>(other) require_relative 'accession/core_extensions' require_relative 'accession/accessionable' - require_relative 'accession/contact' require_relative 'accession/service' require_relative 'accession/sample' require_relative 'accession/tag' diff --git a/lib/accession/contact.rb b/lib/accession/contact.rb deleted file mode 100644 index 107bad8e2c..0000000000 --- a/lib/accession/contact.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true -module Accession - # The contact will be the person who will be informed if accessioning errors - # or if the status needs to be checked. - # Is this ever used? - class Contact - attr_reader :user - - def initialize(user) - @user = user - end - - def name - @name ||= "#{user.first_name} #{user.last_name}" - end - - def email - @email ||= "#{user.login}@#{configatron.default_email_domain}" - end - - def to_h - { inform_on_error: email, inform_on_status: email, name: name } - end - end -end diff --git a/lib/accession/submission.rb b/lib/accession/submission.rb index 97de921854..5ad4182ff5 100644 --- a/lib/accession/submission.rb +++ b/lib/accession/submission.rb @@ -6,17 +6,16 @@ class Submission include ActiveModel::Model include Accession::Accessionable - attr_reader :sample, :service, :contact + attr_reader :sample, :service delegate :accessioned?, :ebi_alias, :ebi_alias_datestamped, to: :sample - validates_presence_of :contact, :sample + validates_presence_of :sample validate :check_sample, if: proc { |s| s.sample.present? } - def initialize(contact_user, sample) + def initialize(sample) @sample = sample @service = sample&.service - @contact = contact_user ? Contact.new(contact_user) : nil # only create Contact if user is present end # Define the client as a class method for easy test mocking @@ -32,7 +31,6 @@ def build_xml(xml) alias: sample.ebi_alias_datestamped, submission_date: date ) do - xml.CONTACTS { xml.CONTACT(contact.to_h) } actions(xml) end end diff --git a/spec/controllers/samples_controller_spec.rb b/spec/controllers/samples_controller_spec.rb index 3625962f9c..6ad752a29e 100644 --- a/spec/controllers/samples_controller_spec.rb +++ b/spec/controllers/samples_controller_spec.rb @@ -112,7 +112,6 @@ describe '#accession' do before do - create(:user, api_key: configatron.accession_local_key) # create contact user allow(Accession::Submission).to receive(:client).and_return( stub_accession_client(:submit_and_fetch_accession_number, return_value: 'EGA00001000240') ) diff --git a/spec/controllers/studies_controller_spec.rb b/spec/controllers/studies_controller_spec.rb index 4b9e909849..7f85847ad3 100644 --- a/spec/controllers/studies_controller_spec.rb +++ b/spec/controllers/studies_controller_spec.rb @@ -174,7 +174,6 @@ let(:study) { create(:open_study, accession_number: 'ENA123', samples: samples) } before do - create(:user, api_key: configatron.accession_local_key) # create contact user allow(Accession::Submission).to receive(:client).and_return( stub_accession_client(:submit_and_fetch_accession_number, return_value: 'EGA00001000240') ) diff --git a/spec/factories/accession/submissions.rb b/spec/factories/accession/submissions.rb index 50416b4400..78f9b854b7 100644 --- a/spec/factories/accession/submissions.rb +++ b/spec/factories/accession/submissions.rb @@ -2,10 +2,9 @@ FactoryBot.define do factory :accession_submission, class: 'Accession::Submission' do - user { create(:user) } sample { build(:accession_sample) } - initialize_with { new(user, sample) } + initialize_with { new(sample) } skip_create end end diff --git a/spec/features/studies/accession_all_samples_spec.rb b/spec/features/studies/accession_all_samples_spec.rb index 21596ef639..a5a2a9299b 100644 --- a/spec/features/studies/accession_all_samples_spec.rb +++ b/spec/features/studies/accession_all_samples_spec.rb @@ -5,7 +5,7 @@ describe 'Accession all samples', :accessioning_enabled, :un_delay_jobs do include AccessionV1ClientHelper - let!(:user) { create(:user, api_key: configatron.accession_local_key) } + let!(:user) { create(:user) } let!(:study) { create(:open_study, accession_number: 'ENA123', samples: create_list(:sample_for_accessioning, 5)) } before do diff --git a/spec/jobs/sample_accessioning_job_spec.rb b/spec/jobs/sample_accessioning_job_spec.rb index ad9b3de0b2..6804ee2c9d 100644 --- a/spec/jobs/sample_accessioning_job_spec.rb +++ b/spec/jobs/sample_accessioning_job_spec.rb @@ -6,7 +6,6 @@ RSpec.describe SampleAccessioningJob, type: :job do include AccessionV1ClientHelper - let(:contact_user) { create(:user, api_key: configatron.accession_local_key) } let(:sample_metadata) { create(:sample_metadata_for_accessioning) } let(:sample) { create(:sample_for_accessioning_with_open_study, sample_metadata:) } let(:accessionable) { create(:accession_sample, sample:) } @@ -21,10 +20,7 @@ end describe '#perform' do - before do - # An accession sample status is created when the job is queued - allow(described_class).to receive(:contact_user).and_return(contact_user) - end + # An accession sample status is created when the job is queued context 'when the submission fails validation' do let(:sample_metadata) { create(:sample_metadata_for_accessioning, sample_taxon_id: nil) } diff --git a/spec/lib/accession/contact_spec.rb b/spec/lib/accession/contact_spec.rb deleted file mode 100644 index ea8765ff72..0000000000 --- a/spec/lib/accession/contact_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Accession::Contact, :accession, type: :model do - subject { described_class.new(user) } - - before(:all) do - @email = configatron.default_email_domain - configatron.default_email_domain = 'example.com' - end - - let!(:user) { create(:user, login: 'user1', first_name: 'Santa', last_name: 'Claus') } - - after(:all) { configatron.default_email_domain = @email } - - it 'has a name' do - expect(subject.name).to eq('Santa Claus') - end - - it 'has an email' do - expect(subject.email).to eq('user1@example.com') - end - - it 'produces a hash for the xml' do - hsh = subject.to_h - expect(hsh[:inform_on_error]).to eq(subject.email) - expect(hsh[:inform_on_status]).to eq(subject.email) - expect(hsh[:name]).to eq(subject.name) - end -end diff --git a/spec/lib/accession/study_spec.rb b/spec/lib/accession/study_spec.rb index 9c10e3e4cf..03ed50020b 100644 --- a/spec/lib/accession/study_spec.rb +++ b/spec/lib/accession/study_spec.rb @@ -17,7 +17,6 @@ let(:non_accessionable_samples) { create_list(:sample, 3) } before do - create(:user, api_key: configatron.accession_local_key) allow(Accession::Submission).to receive(:client).and_return( stub_accession_client(:submit_and_fetch_accession_number, return_value: accession_number) ) diff --git a/spec/lib/accession/submission_spec.rb b/spec/lib/accession/submission_spec.rb index 6897f138ec..b4e3f011fe 100644 --- a/spec/lib/accession/submission_spec.rb +++ b/spec/lib/accession/submission_spec.rb @@ -5,21 +5,16 @@ RSpec.describe Accession::Submission, :accession, type: :model do include AccessionV1ClientHelper - let(:contact_user) { create(:user) } let(:sample) { build(:accession_sample) } context 'when validating' do - it 'is not valid without a contact user' do - expect(described_class.new(nil, sample)).not_to be_valid - end - it 'is not valid without an accession sample' do - expect(described_class.new(contact_user, nil)).not_to be_valid + expect(described_class.new(nil)).not_to be_valid end end describe '#to_xml' do - let(:submission) { described_class.new(contact_user, sample) } + let(:submission) { described_class.new(sample) } let(:xml) { Nokogiri::XML::Document.parse(submission.to_xml) } it 'creates some xml with valid attributes' do @@ -29,11 +24,6 @@ expect(submission_xml.attribute('alias').value).to eq(submission.sample.ebi_alias_datestamped) expect(submission_xml.attribute('submission_date').value).to eq(submission.date) - contact_xml = xml.at('CONTACT') - submission.contact.to_h.each do |attribute, value| - expect(contact_xml.attribute(attribute.to_s).value).to eq(value) - end - expect(xml.at(submission.service.visibility)).to be_present actions_xml = xml.at('ACTIONS') @@ -58,7 +48,7 @@ describe '#submit_accession' do let(:event_user) { create(:user) } - let(:submission) { described_class.new(contact_user, sample) } + let(:submission) { described_class.new(sample) } before do # Inject the mocked client into the controller @@ -109,14 +99,12 @@ end context 'when the submission fails validation' do - let(:invalid_submission) { described_class.new(nil, nil) } + let(:invalid_submission) { described_class.new(nil) } let(:mock_client) { nil } # Client should not be called it 'raises an error with a message' do - error_message = "Accessionable submission is invalid: Contact can't be blank, Sample can't be blank" - expect do - invalid_submission.submit_accession(event_user) - end.to raise_error(StandardError, error_message) + expect { invalid_submission.submit_accession(event_user) } + .to raise_error(StandardError, "Accessionable submission is invalid: Sample can't be blank") end end @@ -139,7 +127,7 @@ end describe '#compile_files' do - let(:submission) { described_class.new(contact_user, sample) } + let(:submission) { described_class.new(sample) } let(:files) { submission.compile_files } it 'returns a hash of files' do diff --git a/spec/lib/accession_spec.rb b/spec/lib/accession_spec.rb index aee9be6e03..9e6d9fe279 100644 --- a/spec/lib/accession_spec.rb +++ b/spec/lib/accession_spec.rb @@ -5,10 +5,6 @@ describe '.accession_sample' do include AccessionV1ClientHelper - before do - create(:user, api_key: configatron.accession_local_key) # create contact user - end - context 'when accessioning is disabled', :accessioning_disabled, :un_delay_jobs do let(:event_user) { create(:user) } let(:sample_metadata) { create(:sample_metadata_for_accessioning) } diff --git a/spec/models/sample_manifest/uploader_spec.rb b/spec/models/sample_manifest/uploader_spec.rb index 7f2e607cf9..3d3eb88470 100644 --- a/spec/models/sample_manifest/uploader_spec.rb +++ b/spec/models/sample_manifest/uploader_spec.rb @@ -16,7 +16,7 @@ let(:test_file_name) { 'test_file.xlsx' } let(:test_file) { Rack::Test::UploadedFile.new(Rails.root.join(test_file_name), '') } - let(:user) { create(:user, api_key: configatron.accession_local_key) } + let(:user) { create(:user) } after(:all) { SampleManifestExcel.reset! } From 94195680148dae71ee7b6aeeca4b20f51f8c4fae Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Thu, 15 Jan 2026 17:13:01 +0000 Subject: [PATCH 30/92] test: remove FakeAccessionService and references --- features/support/accession_service.rb | 108 ------------------ .../4491710_ega_integration_steps.rb | 20 ---- .../support/step_definitions/samples_steps.rb | 41 ------- 3 files changed, 169 deletions(-) delete mode 100644 features/support/accession_service.rb diff --git a/features/support/accession_service.rb b/features/support/accession_service.rb deleted file mode 100644 index f803a0fd03..0000000000 --- a/features/support/accession_service.rb +++ /dev/null @@ -1,108 +0,0 @@ -# frozen_string_literal: true - -require 'singleton' -require 'rest-client' - -class FakeAccessionService - include Singleton - - # Unfortunately Webmock doesn't handle multipart files, so we can't access - # the payload. Instead we set up our own evesdropping Rest Client class - # and use that instead. If we monkey patch the original class we evesdrop on - # everything! - class EvesdropResource < RestClient::Resource - def post(payload) - FakeAccessionService.instance.add_payload(payload) - super - end - end - - # rubocop:todo Metrics/MethodLength - def self.install_hooks(target, tags) # rubocop:todo Metrics/AbcSize - target.instance_eval do - Before(tags) do |_scenario| - # Enable accessioning - @accessioning_enabled_initially = configatron.accession_samples - configatron.accession_samples = true - - # Set up our evesdropper - AccessionService::BaseService.rest_client_class = EvesdropResource - - # We actually know what the value of these will be - # but we include the lookup here, as we're more keen - # on where they are sourced from, rather than what they are - accession_url = configatron.accession.url! - - ena_login = [configatron.accession.ena.user!, configatron.accession.ena.password!] - ega_login = [configatron.accession.ega.user!, configatron.accession.ega.password!] - - [ena_login, ega_login].each do |service_login| - stub_request(:post, accession_url) - .with(basic_auth: service_login) - .to_return do |_request| - response = FakeAccessionService.instance.next! - status = response.nil? ? 500 : 200 - { headers: { 'Content-Type' => 'text/xml' }, body: response, status: status } - end - end - end - - After(tags) do |_scenario| - FakeAccessionService.instance.clear - - # Remove the evesdropper - AccessionService::BaseService.rest_client_class = RestClient::Resource - - # Revert accessioning - configatron.accession_samples = @accession_samples_initially - end - end - end - - # rubocop:enable Metrics/MethodLength - - def bodies - @bodies ||= [] - end - - def sent - @sent ||= [] - end - - attr_reader :last_received - - def clear - @bodies = [] - @sent = [] - end - - def success(type, accession, body = '') - model = type.upcase - bodies << <<-XML - <RECEIPT success="true"> - <#{model} accession="#{accession}">#{body}</#{model}> - <SUBMISSION accession="EGA00001000240" /> - </RECEIPT> - XML - end - - def failure(message) - bodies << "<RECEIPT success=\"false\"><MESSAGES><ERROR>#{message}</ERROR></MESSAGES></RECEIPT>" - end - - def next! - @last_received = bodies.pop - end - - def service - Service - end - - def add_payload(payload) - sent.push(Hash[*payload.map { |k, v| [k, v.readlines] }.map { |k, v| [k, (v unless v.empty?)] }.flatten]) - end -end - -require 'rest_client' - -FakeAccessionService.install_hooks(self, '@accession-service') diff --git a/features/support/step_definitions/4491710_ega_integration_steps.rb b/features/support/step_definitions/4491710_ega_integration_steps.rb index 117f305d8f..cccc0d785f 100644 --- a/features/support/step_definitions/4491710_ega_integration_steps.rb +++ b/features/support/step_definitions/4491710_ega_integration_steps.rb @@ -1,25 +1,5 @@ # frozen_string_literal: true -# rubocop:todo Layout/LineLength -Given /^an accessioning webservice exists which returns a (study|sample|dac|policy) accession number "([^"]*)"$/ do |type, accession_number| - # rubocop:enable Layout/LineLength - FakeAccessionService.instance.success(type, accession_number) -end - -Given /^an accessioning webservice exists that errors with "([^"]+)"$/ do |message| - FakeAccessionService.instance.failure(message) -end - -Given /^an accessioning service exists which returns an array express accession number "([^"]+)"/ do |ae_an| - FakeAccessionService.instance.success('Study', 'EGAS00001000241', <<-XML) - <EXT_ID accession="#{ae_an}" type="ArrayExpress"/> - XML -end - -Given /^an accessioning webservice is unavailable$/ do - # Do nothing, just don't tag the scenario! -end - Given /^an accession number is required for study "([^"]*)"$/ do |study_name| study = Study.find_by(name: study_name) or raise StandardError, "Cannot find study #{study_name.inspect}" study.enforce_accessioning = true diff --git a/features/support/step_definitions/samples_steps.rb b/features/support/step_definitions/samples_steps.rb index a28e1a66a3..9589e1130d 100644 --- a/features/support/step_definitions/samples_steps.rb +++ b/features/support/step_definitions/samples_steps.rb @@ -49,33 +49,6 @@ assert_equal(genome, sample.sample_metadata.reference_genome.name) end -# rubocop:todo Layout/LineLength -Then /^the XML root attribute "([^"]+)" sent to the accession service for sample "([^"]+)" should be "(.*?)"$/ do |xml_attr, sample_name, value| - # rubocop:enable Layout/LineLength - sample = Sample.find_by(name: sample_name) or - raise StandardError, "Cannot find sample with name #{sample_name.inspect}" - xml = FakeAccessionService.instance.sent.last['SAMPLE'].to_s - assert_equal(value, Nokogiri(xml).xpath("/SAMPLE_SET/SAMPLE/@#{xml_attr}").map(&:to_s)[0]) -end - -# rubocop:todo Layout/LineLength -Then /^the XML tag "([^"]+)" sent to the accession service for sample "([^"]+)" should be not present$/ do |xml_attr, sample_name| - # rubocop:enable Layout/LineLength - sample = Sample.find_by(name: sample_name) or - raise StandardError, "Cannot find sample with name #{sample_name.inspect}" - xml = FakeAccessionService.instance.sent.last['SAMPLE'].to_s - assert_equal(true, Nokogiri(xml).xpath("/SAMPLE_SET/SAMPLE/#{xml_attr}").length == 0) -end - -# rubocop:todo Layout/LineLength -Then /^the XML tag "([^"]+)" sent to the accession service for sample "([^"]+)" should be "(.*?)"$/ do |xml_attr, sample_name, value| - # rubocop:enable Layout/LineLength - sample = Sample.find_by(name: sample_name) or - raise StandardError, "Cannot find sample with name #{sample_name.inspect}" - xml = FakeAccessionService.instance.sent.last['SAMPLE'].to_s - assert_equal(value, Nokogiri(xml).xpath("/SAMPLE_SET/SAMPLE/#{xml_attr}").text) -end - Given /^the metadata attribute "(.*?)" of the sample "(.*?)" is "(.*?)"$/ do |attr_name, sample_name, value| sample = Sample.find_by(name: sample_name) or raise StandardError, "Cannot find sample with name #{sample_name.inspect}" @@ -131,20 +104,6 @@ step("I follow \"#{action_str}\"") end -# rubocop:todo Layout/LineLength -Then /^I (should|should not) have (sent|received) the attribute "([^"]*)" for the sample element (to|from) the accessioning service$/ do |state_action, type_action, attr_name, _dest| - # rubocop:enable Layout/LineLength - xml = - if type_action == 'sent' - FakeAccessionService.instance.sent.last['SAMPLE'] - else - FakeAccessionService.instance.last_received - end - assert_equal (state_action == 'should'), - Nokogiri(xml).xpath("/SAMPLE_SET/SAMPLE/@#{attr_name}").map(&:to_s).present?, - "XML was: #{xml}" -end - Given /^sample "([^"]*)" came from a sample manifest$/ do |sample_name| sample = Sample.find_by(name: sample_name) sample_manifest = FactoryBot.create(:sample_manifest, id: 1) From 430b09e152348cbb7cf3e95b3fd0c4e271fe82bf Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Fri, 16 Jan 2026 16:58:30 +0000 Subject: [PATCH 31/92] test: replace feature test with spec/models/accessionable/study_spec.rb --- .../data_release/12400603_update_accession.feature | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 features/data_release/12400603_update_accession.feature diff --git a/features/data_release/12400603_update_accession.feature b/features/data_release/12400603_update_accession.feature deleted file mode 100644 index 55ec3b0145..0000000000 --- a/features/data_release/12400603_update_accession.feature +++ /dev/null @@ -1,14 +0,0 @@ -@accession_number @accession-service -Feature: object with an accession should be modifiable - Background: - Given I am an "administrator" user logged in as "me" - - @study - Scenario: A study with already an accession number should add updated in the history - Given a study named "study" exists for accession - And the study "study" has the accession number "E-ERA-16" - Given an accessioning webservice exists which returns a study accession number "E-ERA-16" - When I update an accession number for study "study" - - When I am on the event history page for study "study" - Then I should see "Assigned study accession number" From 8a9744793b2532c272bec6acf94aad7bf4e27af2 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Fri, 16 Jan 2026 17:02:41 +0000 Subject: [PATCH 32/92] test: remove outdated array express test --- .../8487329_array_express_accession_number.feature | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 features/data_release/8487329_array_express_accession_number.feature diff --git a/features/data_release/8487329_array_express_accession_number.feature b/features/data_release/8487329_array_express_accession_number.feature deleted file mode 100644 index f502e35463..0000000000 --- a/features/data_release/8487329_array_express_accession_number.feature +++ /dev/null @@ -1,13 +0,0 @@ -@study @accession_number @array_express @accession-service -Feature: Array express accession number should be parsed and saved - Background: - Given I am an "administrator" user logged in as "me" - Scenario: The array express accession number is saved to the study - Given a study named "study" exists for array express - Given an accessioning service exists which returns an array express accession number "E-ERA-16" - When I generate an array express accession number for study "study" - And I am on the details page for study "study" - Then I should see "E-ERA-16" - - - From d94bbaf86a7939c6a09a02b80e13ce4a27e12ca8 Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal <pubudumald@gmail.com> Date: Mon, 19 Jan 2026 13:54:29 +0000 Subject: [PATCH 33/92] Refactor MessengerTest to improve stubbing and add tests for process_receptacles --- test/unit/messaging/messenger_test.rb | 47 +++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/test/unit/messaging/messenger_test.rb b/test/unit/messaging/messenger_test.rb index d9ef54396a..7423014ec8 100644 --- a/test/unit/messaging/messenger_test.rb +++ b/test/unit/messaging/messenger_test.rb @@ -2,18 +2,29 @@ require 'test_helper' +# Structs for configatron stub +AmqpStruct = Struct.new(:lims_id!, keyword_init: true) +ConfigatronStruct = Struct.new(:amqp, keyword_init: true) + class MessengerTest < ActiveSupport::TestCase context '#Messenger' do setup do @target = Batch.new - - # @target.stubs(:class).returns(Batch) @template = 'FlowcellIo' @messenger = Messenger.new(target: @target, template: @template, root: 'example') end context 'to_json' do - setup { Api::Messages::FlowcellIo.expects(:to_hash).with(@target).returns('example' => 'hash') } + setup do + Api::Messages::FlowcellIo.expects(:to_hash).with(@target).returns('example' => 'hash') + # Stub configatron for lims_id! using Struct (defined at top) + amqp = AmqpStruct.new(lims_id!: 'SQSCP') + configatron = ConfigatronStruct.new(amqp:) + @messenger.stubs(:configatron).returns(configatron) + # Stub process_receptacles to pass through message + @messenger.stubs(:process_receptacles).returns('example' => 'hash') + Rails.logger.stubs(:info) + end should 'render the json' do assert_equal '{"example":{"example":"hash"},"lims":"SQSCP"}', @messenger.to_json @@ -21,10 +32,40 @@ class MessengerTest < ActiveSupport::TestCase should 'render the json when template is historical (ends in IO)' do messenger = Messenger.new(target: @target, template: 'FlowcellIO', root: 'example') + amqp = AmqpStruct.new(lims_id!: 'SQSCP') + configatron = ConfigatronStruct.new(amqp:) + messenger.stubs(:configatron).returns(configatron) + messenger.stubs(:process_receptacles).returns('example' => 'hash') + Rails.logger.stubs(:info) assert_equal '{"example":{"example":"hash"},"lims":"SQSCP"}', messenger.to_json end end + context '#process_receptacles' do + setup do + @message = { 'foo' => 'bar' } + end + + should 'return message unchanged if not a receptacle target' do + @messenger.stubs(:receptacle_target?).returns(false) + assert_equal @message, @messenger.process_receptacles(@message.dup) + end + + should 'add labware_type if asset_type is library_plate' do + @messenger.stubs(:receptacle_target?).returns(true) + @messenger.stubs(:fetch_asset_type).returns('library_plate') + result = @messenger.process_receptacles(@message.dup) + assert_equal 'library_plate_well', result['labware_type'] + end + + should 'not add labware_type if asset_type is not library_plate' do + @messenger.stubs(:receptacle_target?).returns(true) + @messenger.stubs(:fetch_asset_type).returns('other_type') + result = @messenger.process_receptacles(@message.dup) + assert_nil result['labware_type'] + end + end + should 'provide a routing key' do assert_equal @messenger.routing_key, "message.example.#{@messenger.id}" end From 2897a8f2467697d80fc0f4427a084b6426310c38 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Wed, 21 Jan 2026 15:00:14 +0000 Subject: [PATCH 34/92] test: replace study cucumber test with feature test --- .rubocop_todo.yml | 1 + features/studies/accession_number.feature | 65 ------------- spec/features/studies/accession_study_spec.rb | 92 +++++++++++++++++++ 3 files changed, 93 insertions(+), 65 deletions(-) delete mode 100644 features/studies/accession_number.feature create mode 100644 spec/features/studies/accession_study_spec.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b078f05d49..04bd0d32f0 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -538,6 +538,7 @@ RSpec/AnyInstance: Exclude: - 'spec/controllers/samples_controller_spec.rb' - 'spec/controllers/studies_controller_spec.rb' + - 'spec/features/studies/accession_study_spec.rb' - 'spec/models/lane_spec.rb' # Offense count: 24 diff --git a/features/studies/accession_number.feature b/features/studies/accession_number.feature deleted file mode 100644 index bca7d398da..0000000000 --- a/features/studies/accession_number.feature +++ /dev/null @@ -1,65 +0,0 @@ -# rake features FEATURE=features/plain/studies/accession_number.feature -@study @accession_number @accession-service -Feature: Studies should be able to generate accession numbers - Background: - Given I am an "administrator" user logged in as "John Smith" - - Given a study named "Study for accession number testing" exists - And the title of study "Study for accession number testing" is "Testing accession numbers" - And the description of study "Study for accession number testing" is "To find out if something is broken" - And the abstract of study "Study for accession number testing" is "Ok, not ok?" - And the study "Study for accession number testing" is a "Whole Genome Sequencing" study - - Given I am on the information page for study "Study for accession number testing" - - Scenario: The study does not have an accession number but doesn't need one anyway - When I follow "Generate Accession Number" - Then I should be on the information page for study "Study for accession number testing" - And I should see "An accession number is not required for this study" - - Scenario: The study already has an accession number - Given an accessioning webservice exists which returns a study accession number "EGAN00001000234" - Given an accession number is required for study "Study for accession number testing" - And the study "Study for accession number testing" has the accession number "EGAN00001000235" - When I follow "Generate Accession Number" - Then I should see "Accession number generated: EGAN00001000234" - - Scenario Outline: The study has data missing from the required fields - Given an accession number is required for study "Study for accession number testing" - Given the <attribute> of study "Study for accession number testing" is "" - - When I follow "Generate Accession Number" - And I should see "Please fill in the required fields" - - Examples: - | attribute | - | title | - | abstract | - - Scenario: The study gets a valid accession number - Given an accessioning webservice exists which returns a study accession number "EGAN00001000234" - - Given an accession number is required for study "Study for accession number testing" - - When I follow "Generate Accession Number" - # Then I should be on the information page for study "Study for accession number testing" - And I should see "Accession number generated: EGAN00001000234" - Given I am on the information page for study "Study for accession number testing" - When I follow "Study details" - Then I should see "EGAN00001000234" - - Scenario: The accession number service gives an error - Given an accessioning webservice exists that errors with "We are experiencing problems, sorry" - - Given an accession number is required for study "Study for accession number testing" - - When I follow "Generate Accession Number" - Then I should see "We are experiencing problems, sorry" - - Scenario: There are problems contacting the accession number service - Given an accessioning webservice is unavailable - - Given an accession number is required for study "Study for accession number testing" - - When I follow "Generate Accession Number" - Then I should see "EBI may be down or invalid data submitted" diff --git a/spec/features/studies/accession_study_spec.rb b/spec/features/studies/accession_study_spec.rb new file mode 100644 index 0000000000..e8174fdbf8 --- /dev/null +++ b/spec/features/studies/accession_study_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Study accession number', :accessioning_enabled, :js, type: :feature do + include MockAccession + + let(:user) { create(:admin, first_name: 'John', last_name: 'Smith') } + let(:study) { create(:managed_study) } + + before do + login_user(user) + visit study_path(study) + end + + context 'when the study does not need an accession number' do + let(:study) { create(:study) } + + it 'shows not required message' do + click_link 'Generate Accession Number' + expect(page).to have_css('.alert', text: 'An accession number is not required for this study') + expect(page).to have_current_path(study_information_path(study)) + end + end + + context 'when the study already has an accession number' do + before do + allow_any_instance_of(RestClient::Resource).to receive(:post).and_return(successful_study_accession_response) + + study.study_metadata.update!(study_ebi_accession_number: 'EGAN00001000234') + visit study_path(study) + end + + it 'shows the generated accession number' do + click_link 'Update Study Data for Accessioning' + expect(page).to have_css('.alert', text: 'Accession number generated: EGA00002000345') + end + end + + context 'when required fields are missing' do + %w[study_study_title study_abstract].each do |attribute| + it "shows required fields message when #{attribute} is missing" do + study.study_metadata.update!(attribute => '') + visit study_path(study) + click_link 'Generate Accession Number' + expect(page).to have_css('.alert', text: 'Please fill in the required fields') + end + end + end + + context 'when the study gets a valid accession number' do + before do + allow_any_instance_of(RestClient::Resource).to receive(:post).and_return(successful_study_accession_response) + + visit study_path(study) + end + + it 'shows the generated accession number and displays it on study details' do + click_link 'Generate Accession Number' + expect(page).to have_css('.alert', text: 'Accession number generated: EGA00002000345') + visit study_path(study) + click_link 'Study details' + expect(page).to have_content('EGA00002000345') + end + end + + context 'when the accession number service gives an error' do + before do + allow_any_instance_of(RestClient::Resource).to receive(:post).and_return(failed_accession_response) + + visit study_path(study) + end + + it 'shows the error message' do + click_link 'Generate Accession Number' + expect(page).to have_css('.alert', text: 'Error 1; Error 2') + end + end + + context 'when the accession number service is unavailable' do + before do + allow_any_instance_of(RestClient::Resource).to receive(:post).and_raise(RestClient::ServiceUnavailable) + + visit study_path(study) + end + + it 'shows the unavailable message' do + click_link 'Generate Accession Number' + expect(page).to have_css('.alert', text: 'EBI may be down or invalid data submitted') + end + end +end From 2069720341dfdc371abc9d88fc3c0ee6b8d1ee8c Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Wed, 21 Jan 2026 16:07:03 +0000 Subject: [PATCH 35/92] test: replace study dac and policy cucumber test with feature test --- .rubocop_todo.yml | 1 + .../8487329_ega_dac_integration.feature | 40 -------- spec/factories/roles.rb | 5 + .../accession_study_dac_policy_spec.rb | 94 +++++++++++++++++++ spec/support/mock_accession.rb | 17 ++-- 5 files changed, 110 insertions(+), 47 deletions(-) delete mode 100644 features/data_release/8487329_ega_dac_integration.feature create mode 100644 spec/features/studies/accession_study_dac_policy_spec.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 04bd0d32f0..ab0de7ecff 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -538,6 +538,7 @@ RSpec/AnyInstance: Exclude: - 'spec/controllers/samples_controller_spec.rb' - 'spec/controllers/studies_controller_spec.rb' + - 'spec/features/studies/accession_study_dac_policy_spec.rb' - 'spec/features/studies/accession_study_spec.rb' - 'spec/models/lane_spec.rb' diff --git a/features/data_release/8487329_ega_dac_integration.feature b/features/data_release/8487329_ega_dac_integration.feature deleted file mode 100644 index 553c1cf3ce..0000000000 --- a/features/data_release/8487329_ega_dac_integration.feature +++ /dev/null @@ -1,40 +0,0 @@ -@study @accession_number @dac @policy @accession-service -Feature: Dac and Policy should be able to generate accession numbers - Background: - Given I am an "administrator" user logged in as "me" - - Scenario Outline: A managed study has a valid <object> set but no accession number for it - Given a study named "managed study" exists - Given the study "managed study" has a managed data release strategy - Given the study "managed study" has a valid <object> - Given an accessioning webservice exists which returns a <object> accession number "EGAP00001000234" - When I generate a <object> accession number for study "managed study" - And I am on the information page for study "managed study" - And I follow "Study details" - Then I should see "EGAP00001000234" - Examples: - | object | - | policy | - | dac | - - Scenario Outline: A open study has a valid <object> set but no accession number for it. Should fail. - Given a study named "open study" exists - Given the study "open study" has a open data release strategy - Given the study "open study" has a valid <object> - Given an accessioning webservice exists which returns a <object> accession number "EGAP00001000234" - When I generate a <object> accession number for study "open study" - Then I should see "No accession number was generated" - Examples: - | object | - | policy | - | dac | - - Scenario: A managed study has an invalid DAC - Given a study named "managed study" exists - Given the study "managed study" has a managed data release strategy - Given an accessioning webservice exists which returns a dac accession number "EGAP00001000234" - When I generate a dac accession number for study "managed study" - Then I should see "Data Access Contacts Empty" - And I am on the information page for study "managed study" - And I follow "Study details" - Then I should not see "EGAP00001000234" diff --git a/spec/factories/roles.rb b/spec/factories/roles.rb index 681292e83a..99ad2c54c2 100644 --- a/spec/factories/roles.rb +++ b/spec/factories/roles.rb @@ -45,4 +45,9 @@ transient { follower { build(:user) } } roles { |role| [role.association(:role, name: 'follower', users: [follower])] } end + + trait :with_data_access_contacts do + transient { data_access_contacts { build_list(:user, 1) } } + roles { |role| [role.association(:role, name: 'Data Access Contact', users: data_access_contacts)] } + end end diff --git a/spec/features/studies/accession_study_dac_policy_spec.rb b/spec/features/studies/accession_study_dac_policy_spec.rb new file mode 100644 index 0000000000..a5d8f9b3af --- /dev/null +++ b/spec/features/studies/accession_study_dac_policy_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'EGA DAC and Policy accessioning', :accessioning_enabled, :js, type: :feature do + include MockAccession + + let(:user) { create(:admin) } + let(:data_access_contacts) { create_list(:user, 1) } + + before do + login_user(user) + end + + context 'when a managed study has a valid DAC set but no accession number for it' do + let(:study) { create(:managed_study, :with_data_access_contacts, data_access_contacts:) } + + before do + allow_any_instance_of(RestClient::Resource).to receive(:post) + .and_return(successful_dac_policy_accession_response) + + visit study_path(study) + end + + it 'generates a DAC accession number and displays it on study details' do + click_link 'Generate DAC Accession Number' + visit study_information_path(study) + click_link 'Study details' + expect(page).to have_content('EGAD0001000234') + end + end + + context 'when an open study has a valid DAC set but no accession number for it' do + let(:study) { create(:open_study, :with_data_access_contacts, data_access_contacts:) } + + before do + allow_any_instance_of(RestClient::Resource).to receive(:post).and_return(failed_accession_response) + visit study_path(study) + end + + it 'does not generate a DAC accession number' do + click_link 'Generate DAC Accession Number' + expect(page).to have_content('No accession number was generated') + end + end + + context 'when a managed study has a valid Policy set but no accession number for it' do + let(:study) { create(:managed_study, :with_data_access_contacts, data_access_contacts:) } + + before do + allow_any_instance_of(RestClient::Resource).to receive(:post) + .and_return(successful_dac_policy_accession_response) + + study.study_metadata.update(ega_dac_accession_number: 'EGAD0001000234') # DAC required prior to Policy + + visit study_path(study) + end + + it 'generates a Policy accession number and displays it on study details' do + click_link 'Generate Policy Accession Number' + visit study_information_path(study) + click_link 'Study details' + expect(page).to have_content('EGAP0001000234') + end + end + + context 'when an open study has a valid Policy set but no accession number for it' do + let(:study) { create(:open_study, :with_data_access_contacts, data_access_contacts:) } + + before do + allow_any_instance_of(RestClient::Resource).to receive(:post).and_return(failed_accession_response) + + visit study_path(study) + end + + it 'does not generate a Policy accession number' do + click_link 'Generate Policy Accession Number' + expect(page).to have_content('No accession number was generated') + end + end + + context 'when a managed study has an invalid DAC' do + let(:study) { create(:managed_study) } # no data access contacts + + before do + visit study_path(study) + end + + it 'shows error and does not display accession number on study details' do + click_link 'Generate DAC Accession Number' + expect(page).to have_content('Data Access Contacts Empty') + end + end +end diff --git a/spec/support/mock_accession.rb b/spec/support/mock_accession.rb index d08eec6af3..1e08ee5eb6 100644 --- a/spec/support/mock_accession.rb +++ b/spec/support/mock_accession.rb @@ -3,15 +3,19 @@ module MockAccession Response = Struct.new(:code, :body) - # for samples - def successful_sample_accession_response - Response.new(200, '<RECEIPT success="true"><SAMPLE accession="EGA00001000240" /></RECEIPT>') - end - def successful_study_accession_response Response.new(200, '<RECEIPT success="true"><STUDY accession="EGA00002000345" /></RECEIPT>') end + def successful_dac_policy_accession_response + Response.new(200, <<~XML) + <RECEIPT success="true"> + <DAC accession="EGAD0001000234" /> + <POLICY accession="EGAP0001000234" /> + </RECEIPT> + XML + end + def failed_accession_response Response.new(200, <<~XML) <RECEIPT receiptDate="2014-12-02T16:06:20.871Z" success="false"> @@ -23,6 +27,5 @@ def failed_accession_response XML end - module_function :successful_sample_accession_response, :successful_study_accession_response, - :failed_accession_response + module_function :successful_study_accession_response, :failed_accession_response end From f4ef20cf949146d543e097832647ba29ccb09e5c Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Wed, 21 Jan 2026 16:39:16 +0000 Subject: [PATCH 36/92] style: update rubocop todo --- .rubocop_todo.yml | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ab0de7ecff..1a50be4722 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --no-exclude-limit` -# on 2026-01-14 14:56:46 UTC using RuboCop version 1.81.7. +# on 2026-01-21 16:38:56 UTC using RuboCop version 1.81.7. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -126,7 +126,7 @@ Lint/DuplicateMethods: - 'app/models/stock_stamper.rb' - 'lib/accession/tag.rb' -# Offense count: 63 +# Offense count: 62 # Configuration parameters: AllowComments, AllowEmptyLambdas. Lint/EmptyBlock: Exclude: @@ -164,7 +164,6 @@ Lint/EmptyBlock: - 'app/api/endpoints/users.rb' - 'app/api/endpoints/uuids.rb' - 'app/api/endpoints/wells.rb' - - 'app/models/accessionable/sample.rb' - 'app/models/plate.rb' - 'app/models/request/library_creation.rb' - 'app/sequencescape_excel/sequencescape_excel/list.rb' @@ -240,7 +239,7 @@ Lint/StructNewOverride: Exclude: - 'app/models/product_criteria/basic.rb' -# Offense count: 61 +# Offense count: 57 # This cop supports safe autocorrection (--autocorrect). Lint/UselessAssignment: Exclude: @@ -533,20 +532,18 @@ Performance/MethodObjectAsBlock: - 'features/support/step_definitions/transfer_steps.rb' - 'lib/tasks/report.rake' -# Offense count: 3 +# Offense count: 10 RSpec/AnyInstance: Exclude: - - 'spec/controllers/samples_controller_spec.rb' - 'spec/controllers/studies_controller_spec.rb' - 'spec/features/studies/accession_study_dac_policy_spec.rb' - 'spec/features/studies/accession_study_spec.rb' - 'spec/models/lane_spec.rb' -# Offense count: 24 +# Offense count: 22 RSpec/BeforeAfterAll: Exclude: - 'spec/features/sample_manifests/uploader_for_manifests_with_tag_sequences_spec.rb' - - 'spec/lib/accession/contact_spec.rb' - 'spec/models/sample_manifest/generator_spec.rb' - 'spec/models/sample_manifest/uploader_spec.rb' - 'spec/sample_manifest_excel/download_spec.rb' @@ -639,7 +636,7 @@ RSpec/EmptyExampleGroup: - 'spec/models/pulldown/requests_spec.rb' - 'spec/models/tag_substitutions_spec.rb' -# Offense count: 377 +# Offense count: 378 # Configuration parameters: Max, CountAsOne. RSpec/ExampleLength: Exclude: @@ -783,14 +780,13 @@ RSpec/ExampleWording: - 'spec/sequencescape_excel/validation_spec.rb' - 'spec/sequencescape_excel/worksheet_spec.rb' -# Offense count: 258 +# Offense count: 257 # Configuration parameters: AssignmentOnly. RSpec/InstanceVariable: Exclude: - 'spec/api/extraction_attributes_spec.rb' - 'spec/controllers/studies_controller_spec.rb' - 'spec/controllers/submissions_controller_spec.rb' - - 'spec/lib/accession/contact_spec.rb' - 'spec/models/orders/order_spec.rb' - 'spec/models/pulldown/requests_spec.rb' - 'spec/models/qc_report_spec.rb' @@ -893,7 +889,7 @@ RSpec/MultipleDescribes: - 'spec/lib/label_printer/asset_labels_spec.rb' - 'spec/models/qc_result/qc_result_spec.rb' -# Offense count: 932 +# Offense count: 931 # Configuration parameters: Max. RSpec/MultipleExpectations: Exclude: @@ -935,7 +931,6 @@ RSpec/MultipleExpectations: - 'spec/jobs/export_pool_xp_to_traction_job_spec.rb' - 'spec/lib/accession/accessionable_spec.rb' - 'spec/lib/accession/configuration_spec.rb' - - 'spec/lib/accession/contact_spec.rb' - 'spec/lib/accession/sample_spec.rb' - 'spec/lib/accession/service_spec.rb' - 'spec/lib/accession/submission_spec.rb' @@ -1124,7 +1119,7 @@ RSpec/MultipleExpectations: - 'spec/views/labware/show_chromium_chip_spec.rb' - 'spec/views/samples/index_html_erb_spec.rb' -# Offense count: 247 +# Offense count: 241 # Configuration parameters: EnforcedStyle, IgnoreSharedExamples. # SupportedStyles: always, named_only RSpec/NamedSubject: @@ -1142,7 +1137,6 @@ RSpec/NamedSubject: - 'spec/api/work_completion_spec.rb' - 'spec/controllers/studies/information_controller_spec.rb' - 'spec/controllers/studies_controller_spec.rb' - - 'spec/lib/accession/contact_spec.rb' - 'spec/lib/label_printer/sample_manifest_plate_double_spec.rb' - 'spec/models/aliquot_spec.rb' - 'spec/models/api/library_tube_io_spec.rb' @@ -1881,7 +1875,7 @@ Style/Not: - 'app/views/batches/show.xml.builder' - 'features/support/step_definitions/api_steps.rb' -# Offense count: 33 +# Offense count: 31 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns. # SupportedStyles: predicate, comparison @@ -1899,7 +1893,6 @@ Style/NumericPredicate: - 'app/models/study.rb' - 'app/models/tag_layout/walk_wells_by_pools.rb' - 'app/models/well_attribute.rb' - - 'features/support/step_definitions/samples_steps.rb' - 'lib/deployed.rb' - 'lib/submission_serializer.rb' - 'test/shoulda_macros/sanger_macros/resource_test.rb' @@ -2018,14 +2011,13 @@ Style/Proc: - 'app/models/lib_pool_norm_tube_generator.rb' - 'app/models/qcable/statemachine.rb' -# Offense count: 4 +# Offense count: 3 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Methods. Style/RedundantArgument: Exclude: - 'app/models/plate/fluidigm_behaviour.rb' - 'app/models/study_report/study_details.rb' - - 'features/support/step_definitions/samples_steps.rb' - 'spec/features/shared_examples/cherrypicking.rb' # Offense count: 7 @@ -2121,11 +2113,10 @@ Style/SymbolProc: - 'app/models/tasks/plate_transfer_handler.rb' - 'db/seeds/0001_workflows.rb' -# Offense count: 9 +# Offense count: 7 # This cop supports unsafe autocorrection (--autocorrect-all). Style/ZeroLengthPredicate: Exclude: - 'app/models/bulk_submission.rb' - 'app/models/sequencing_pipeline.rb' - - 'features/support/step_definitions/samples_steps.rb' - 'test/shoulda_macros/sanger_macros/resource_test.rb' From 3b72018964e228ce28e1e6ffad1c6632b87a45b2 Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal <pubudumald@gmail.com> Date: Mon, 26 Jan 2026 14:36:39 +0000 Subject: [PATCH 37/92] Fix indentation for update_subject_type_for_library_receptacles! call in base.rb --- app/sample_manifest_excel/sample_manifest_excel/upload/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb b/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb index a014be7a12..205b338988 100644 --- a/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb +++ b/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb @@ -202,7 +202,7 @@ def stock_receptacles_to_be_registered @stock_receptacles_to_be_registered ||= rows.select(&:sample_created?) .map(&:asset) .each do |asset| - update_subject_type_for_library_receptacles!(asset) + update_subject_type_for_library_receptacles!(asset) end end end From 7bf0bd8a7bf68615c1725ba8921cfdca4c926cb6 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Wed, 28 Jan 2026 13:50:39 +0000 Subject: [PATCH 38/92] doc: remove some known removed components --- docs/accessioning/accessioning.mermaid | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index fc3bde2865..5ee1447851 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -61,7 +61,6 @@ flowchart LR %% CT_SS_Studies(fa:fa-arrows-spin Studies Controller) %% Functions - FN_SS_AccessionService_submit_sample_for_user(fa:fa-caret-right submit_sample_for_user) FN_SS_AccessionService_submit_study_for_user(fa:fa-caret-right submit_study_for_user) FN_SS_AccessionService_submit(fa:fa-caret-right submit) FN_SS_Sample_accession(fa:fa-caret-right accession) @@ -134,7 +133,6 @@ flowchart LR CP_SS_AccessionRequest end subgraph MD_SS_AccessionService[fa:fa-square-caret-down AccessionService Model - older] - FN_SS_AccessionService_submit_sample_for_user FN_SS_AccessionService_submit_study_for_user FN_SS_AccessionService_submit end @@ -172,15 +170,12 @@ flowchart LR %% Sample generate accession number User_SSR --> UI_SS_Sample_GAN User_Neil --> UI_SS_Sample_GAN - FN_SS_Samples_accession --> | 1. |CF_SS_accession_samples + %% FN_SS_Samples_accession --> | 1. |CF_SS_accession_samples UI_SS_Sample_GAN --> FN_SS_Samples_accession FN_SS_Samples_accession --> | 2. | FN_SS_Sample_validate_ena_required_fields - FN_SS_Samples_accession ==> | 3. Validation Passed | FN_SS_AccessionService_submit_sample_for_user - FN_SS_AccessionService_submit_sample_for_user ==> FN_SS_AccessionService_submit FN_SS_AccessionService_submit ==> External_EBI FN_SS_Sample_validate_ena_required_fields -..-> | Validation Failed | FN_SS_Samples_accession External_EBI -..-> | FROM AccessionService.submit | FN_SS_AccessionService_submit - FN_SS_AccessionService_submit -..-> | FROM submit_sample_for_user | FN_SS_Samples_accession FN_SS_Samples_accession -..-> | flash message | User_SS_Users %% Study accession all samples @@ -192,7 +187,7 @@ flowchart LR FN_SS_Studies_accession_all_samples -..-> | flash message | User_SS_Users FN_SS_Study_accession_all_samples --> FN_SS_Sample_accession FN_SS_Sample_accession -..-> | FROM accession_all_samples, error messages | FN_SS_Study_accession_all_samples - FN_SS_Sample_accession --> | 1. | CF_SS_accession_samples + %% FN_SS_Sample_accession --> | 1. | CF_SS_accession_samples FN_SS_Sample_accession --> | 2. | FN_SS_Sample_validate_accessionable FN_SS_Sample_accession ==> | 3. > NO FEEDBACK > | CP_SS_Delayed_Job_Accessioning CP_SS_Delayed_Job_Accessioning -..-> | exception email | User_Developers @@ -207,7 +202,7 @@ flowchart LR FN_SS_Studies_accession --> | 2. | FN_SS_AccessionService_submit_study_for_user FN_SS_Studies_accession -..-> | 3. IF success -- flash message | User_SS_Users FN_SS_Studies_accession --> | IF error | FN_SS_Studies_rescue_accession_errors - FN_SS_AccessionService_submit_study_for_user --> CF_SS_accession_samples + %% FN_SS_AccessionService_submit_study_for_user --> CF_SS_accession_samples FN_SS_AccessionService_submit_study_for_user ==> FN_SS_AccessionService_submit FN_SS_AccessionService_submit -..-> | FROM submit_study_for_user | FN_SS_Studies_accession FN_SS_Studies_rescue_accession_errors -..-> | flash message | User_SS_Users From 45b917c38f0f6bbd19c20fd5b8b417cefb681238 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Wed, 28 Jan 2026 16:22:18 +0000 Subject: [PATCH 39/92] doc: update generate accession number for a single sample --- docs/accessioning/accessioning.mermaid | 54 ++++++++++++++++++-------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index 5ee1447851..7244a1fb43 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -16,6 +16,7 @@ flowchart LR L_Interface(fa:fa-computer-mouse User Interface) L_Model(fa:fa-square-caret-down Model) L_Controller(fa:fa-arrows-spin Controller) + L_View(fa:fa-eye View) L_Function(fa:fa-caret-right Function) L_Config(fa:fa-screwdriver-wrench Configuration) L_Resource(fa:fa-file Resource) @@ -23,7 +24,7 @@ flowchart LR L_Async(fa:fa-clock Asynchronous Process) L_User ~~~ L_External ~~~ L_API ~~~ L_Interface ~~~ L_Resource ~~~ L_Async - L_Config ~~~ L_Controller ~~~ L_Model ~~~ L_Function ~~~ L_Library + L_Config ~~~ L_Controller ~~~ L_Model ~~~ L_View ~~~ L_Function ~~~ L_Library InvisibleNodeA[ ] -..-> | Data flow | InvisibleNodeB[ ] -- Function calls --> InvisibleNodeC[ ] == Accessioning function calls ==> InvisibleNodeD[ ] end @@ -34,6 +35,7 @@ flowchart LR %% Nodes %% Users User_SeqOps(fa:fa-user SeqOps) + User_Study_Owners(fa:fa-user Study Owners) User_Neil(fa:fa-user Neil) User_SSR(fa:fa-user SSRs) User_LB_Users(fa:fa-user LB Users) @@ -55,11 +57,15 @@ flowchart LR %% MD_SS_Sample(fa:fa-square-caret-down Sample) %% MD_SS_Study(fa:fa-square-caret-down Study) MD_SS_SampleManifest_Uploader(fa:fa-square-caret-down SampleManifest::Uploader) + MD_SS_AccessionStatuses(fa:fa-square-caret-down AccessionStatuses) %% Controllers %% CT_SS_Samples(fa:fa-arrows-spin Samples Controller) %% CT_SS_Studies(fa:fa-arrows-spin Studies Controller) + %% Views + VW_SS_Sample(fa:fa-eye Sample View) + %% Functions FN_SS_AccessionService_submit_study_for_user(fa:fa-caret-right submit_study_for_user) FN_SS_AccessionService_submit(fa:fa-caret-right submit) @@ -72,13 +78,17 @@ flowchart LR FN_SS_Studies_accession(fa:fa-caret-right accession) FN_SS_Studies_validate_ena_required_fields(fa:fa-caret-right validate_ena_required_fields!) FN_SS_Studies_rescue_accession_errors(fa:fa-caret-right rescue_accession_errors) + FN_SS_Accession_accession_sample(fa:fa-caret-right accession_sample) + FN_SS_Accession_Sample_validate(fa:fa-caret-right Accession::Sample#validate) + + %% Libraries + LB_SS_AccessioningV1Client(fa:fa-book AccessioningV1Client) %% Other Components API_SS_OrderResource(fa:fa-arrow-right-to-bracket Order Resource) - CP_SS_Delayed_Job_Accessioning(fa:fa-clock DelayedJob::SampleAccessioningJob) CP_SS_AccessionSubmission(fa:fa-book Accession::Submission) - CP_SS_AccessionRequest(fa:fa-book Accession::Request) CP_SS_SampleManifestExcel_Upload(SampleManifestExcel::Upload) + DJ_SS_SampleAccessioningJob(fa:fa-clock SampleAccessioningJob) %% Config CF_SS_accession_samples(fa:fa-screwdriver-wrench accession_samples) @@ -90,6 +100,7 @@ flowchart LR %% Groupings of nodes subgraph Providers User_SeqOps + User_Study_Owners User_Neil User_SSR end @@ -104,7 +115,12 @@ flowchart LR MD_SS_Submission_AccessionBehaviour CF_SS_accession_samples CF_SS_disable_accession_check - CP_SS_Delayed_Job_Accessioning + + DJ_SS_SampleAccessioningJob + LB_SS_AccessioningV1Client + MD_SS_AccessionStatuses + + VW_SS_Sample subgraph Samples UI_SS_Sample_GAN @@ -128,11 +144,12 @@ flowchart LR subgraph CT_SS_Samples[fa:fa-arrows-spin Samples Controller] FN_SS_Samples_accession end - subgraph LB_SS_Accession[fa:fa-book Accession - newer] + subgraph LB_SS_Accession[fa:fa-book Accession - samples only] + FN_SS_Accession_accession_sample CP_SS_AccessionSubmission - CP_SS_AccessionRequest + FN_SS_Accession_Sample_validate end - subgraph MD_SS_AccessionService[fa:fa-square-caret-down AccessionService Model - older] + subgraph MD_SS_AccessionService[fa:fa-square-caret-down AccessionService Model - studies only] FN_SS_AccessionService_submit_study_for_user FN_SS_AccessionService_submit end @@ -168,18 +185,26 @@ flowchart LR MD_SS_SampleManifest_Uploader --> CP_SS_SampleManifestExcel_Upload ---> FN_SS_Sample_accession %% Sample generate accession number - User_SSR --> UI_SS_Sample_GAN User_Neil --> UI_SS_Sample_GAN + User_SSR --> UI_SS_Sample_GAN %% FN_SS_Samples_accession --> | 1. |CF_SS_accession_samples UI_SS_Sample_GAN --> FN_SS_Samples_accession - FN_SS_Samples_accession --> | 2. | FN_SS_Sample_validate_ena_required_fields + FN_SS_Samples_accession --> | 2. | FN_SS_Sample_validate_ena_required_fields + FN_SS_Sample_accession ==> FN_SS_Accession_accession_sample + FN_SS_Accession_accession_sample ==> DJ_SS_SampleAccessioningJob + DJ_SS_SampleAccessioningJob --> | 1. | FN_SS_Accession_Sample_validate + DJ_SS_SampleAccessioningJob ==> | 2. | CP_SS_AccessionSubmission + CP_SS_AccessionSubmission ==> LB_SS_AccessioningV1Client + LB_SS_AccessioningV1Client ==> External_EBI FN_SS_AccessionService_submit ==> External_EBI + External_EBI ==> MD_SS_AccessionStatuses + MD_SS_AccessionStatuses ==> VW_SS_Sample ==> User_SS_Users FN_SS_Sample_validate_ena_required_fields -..-> | Validation Failed | FN_SS_Samples_accession External_EBI -..-> | FROM AccessionService.submit | FN_SS_AccessionService_submit FN_SS_Samples_accession -..-> | flash message | User_SS_Users %% Study accession all samples - User_SSR --> UI_SS_Study_AAS + User_Study_Owners --> UI_SS_Study_AAS User_Neil --> UI_SS_Study_AAS UI_SS_Study_AAS --> FN_SS_Studies_accession_all_samples FN_SS_Studies_accession_all_samples --> FN_SS_Study_accession_all_samples @@ -189,14 +214,9 @@ flowchart LR FN_SS_Sample_accession -..-> | FROM accession_all_samples, error messages | FN_SS_Study_accession_all_samples %% FN_SS_Sample_accession --> | 1. | CF_SS_accession_samples FN_SS_Sample_accession --> | 2. | FN_SS_Sample_validate_accessionable - FN_SS_Sample_accession ==> | 3. > NO FEEDBACK > | CP_SS_Delayed_Job_Accessioning - CP_SS_Delayed_Job_Accessioning -..-> | exception email | User_Developers - CP_SS_Delayed_Job_Accessioning ==> CP_SS_AccessionSubmission ==> CP_SS_AccessionRequest ==> External_EBI - %% External_EBI .-> | FROM Accession::Request | CP_SS_Delayed_Job_Accessioning ...struggling to reverse the arrow here... - CP_SS_Delayed_Job_Accessioning -.- | <-- FROM Accession::Request | External_EBI %% Study generate accession number - User_Neil --> UI_SS_Study_GAN + User_Study_Owners --> UI_SS_Study_GAN UI_SS_Study_GAN --> FN_SS_Studies_accession FN_SS_Studies_accession --> | 1. | FN_SS_Studies_validate_ena_required_fields FN_SS_Studies_accession --> | 2. | FN_SS_AccessionService_submit_study_for_user @@ -240,5 +260,5 @@ flowchart LR class SS_API,DelayedJob,Samples,Studies,Submissions SequencescapeSection; class L_Model,MD_SS_Study,MD_SS_AccessionService,MD_SS_Sample,MD_SS_SampleManifest_Uploader,MD_SS_Order,MD_SS_Submission_AccessionBehaviour railsModel; class L_Controller,CT_SS_Samples,CT_SS_Studies railsController; - class L_User,User_SeqOps,User_Neil,User_SSR,User_LB_Users,User_SS_Users,User_Developers users; + class L_User,User_SeqOps,User_Study_Owners,User_Neil,User_SSR,User_LB_Users,User_SS_Users,User_Developers users; class L_Interface,UI_LB_Charge_and_Pass,UI_SS_Sample_GAN,UI_SS_Study_GAN,UI_SS_Study_AAS,UI_SS_Manifest_Upload userInterface; From 46c6e8d0cb43589b8d5024616d1264bc5e94c7c8 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Wed, 28 Jan 2026 17:03:16 +0000 Subject: [PATCH 40/92] doc: update sample manifest and accession all samples --- docs/accessioning/accessioning.mermaid | 95 +++++++++++++++----------- 1 file changed, 56 insertions(+), 39 deletions(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index 7244a1fb43..b7452f7396 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -11,7 +11,7 @@ flowchart LR subgraph Legend [Legend] direction TB L_User(fa:fa-user User) - L_External(fa:fa-globe External System) + L_External(fa:fa-arrow-up External API) L_API(fa:fa-arrow-right-to-bracket API) L_Interface(fa:fa-computer-mouse User Interface) L_Model(fa:fa-square-caret-down Model) @@ -41,11 +41,15 @@ flowchart LR User_LB_Users(fa:fa-user LB Users) User_Developers(fa:fa-user Developers) User_SS_Users(fa:fa-user SS Users) - External_EBI(fa:fa-globe EBI) + + %% External Systems + External_EBI_Studies(fa:fa-arrow-up EBI Studies) + External_EBI_Samples(fa:fa-arrow-up EBI Samples) %% User Interfaces UI_LB_Charge_and_Pass(fa:fa-computer-mouse Charge and Pass) UI_SS_Sample_GAN(fa:fa-computer-mouse Generate Accession Number) + UI_SS_Sample_SAA(fa:fa-computer-mouse Save and Accession) UI_SS_Study_GAN(fa:fa-computer-mouse Generate Accession Number) UI_SS_Study_AAS(fa:fa-computer-mouse Accession all Samples) UI_SS_Manifest_Upload(fa:fa-computer-mouse Manifest Upload) @@ -65,21 +69,26 @@ flowchart LR %% Views VW_SS_Sample(fa:fa-eye Sample View) + VW_SS_Study(fa:fa-eye Study View) %% Functions + FN_SS_SampleManifestExcel_Upload_Base_trigger_accessioning(fa:fa-caret-right SampleManifestExcel::Upload::Base#trigger_accessioning) + FN_SS_AccessionService_select_for_study(fa:fa-caret-right select_for_study) FN_SS_AccessionService_submit_study_for_user(fa:fa-caret-right submit_study_for_user) FN_SS_AccessionService_submit(fa:fa-caret-right submit) + FN_SS_Accessionable_Study_update_accession_number(fa:fa-caret-right Accessionable::Study#update_accession_number!) FN_SS_Sample_accession(fa:fa-caret-right accession) + FN_SS_Sample_accession_and_handle_validation_errors(fa:fa-caret-right accession_and_handle_validation_errors) FN_SS_Sample_validate_accessionable(fa:fa-caret-right validate_accessionable!) - FN_SS_Sample_validate_ena_required_fields(fa:fa-caret-right validate_ena_required_fields!) FN_SS_Samples_accession(fa:fa-caret-right accession) FN_SS_Study_accession_all_samples(fa:fa-caret-right accession_all_samples) FN_SS_Studies_accession_all_samples(fa:fa-caret-right accession_all_samples) FN_SS_Studies_accession(fa:fa-caret-right accession) - FN_SS_Studies_validate_ena_required_fields(fa:fa-caret-right validate_ena_required_fields!) FN_SS_Studies_rescue_accession_errors(fa:fa-caret-right rescue_accession_errors) FN_SS_Accession_accession_sample(fa:fa-caret-right accession_sample) FN_SS_Accession_Sample_validate(fa:fa-caret-right Accession::Sample#validate) + FN_SS_Accession_Sample_update_accession_number(fa:fa-caret-right Accession::Sample#update_accession_number) + FN_SS_Study_validate_study_for_accessioning(fa:fa-caret-right validate_study_for_accessioning!) %% Libraries LB_SS_AccessioningV1Client(fa:fa-book AccessioningV1Client) @@ -87,7 +96,6 @@ flowchart LR %% Other Components API_SS_OrderResource(fa:fa-arrow-right-to-bracket Order Resource) CP_SS_AccessionSubmission(fa:fa-book Accession::Submission) - CP_SS_SampleManifestExcel_Upload(SampleManifestExcel::Upload) DJ_SS_SampleAccessioningJob(fa:fa-clock SampleAccessioningJob) %% Config @@ -110,7 +118,7 @@ flowchart LR subgraph Application_Sequencescape[Sequencescape Application] UI_SS_Manifest_Upload MD_SS_SampleManifest_Uploader - CP_SS_SampleManifestExcel_Upload + FN_SS_SampleManifestExcel_Upload_Base_trigger_accessioning MD_SS_Order MD_SS_Submission_AccessionBehaviour CF_SS_accession_samples @@ -120,9 +128,13 @@ flowchart LR LB_SS_AccessioningV1Client MD_SS_AccessionStatuses + FN_SS_Accessionable_Study_update_accession_number + VW_SS_Sample + VW_SS_Study subgraph Samples + UI_SS_Sample_SAA UI_SS_Sample_GAN end subgraph Studies @@ -135,11 +147,11 @@ flowchart LR subgraph CT_SS_Studies[fa:fa-arrows-spin Studies Controller] FN_SS_Studies_accession_all_samples FN_SS_Studies_accession - FN_SS_Studies_validate_ena_required_fields FN_SS_Studies_rescue_accession_errors end subgraph MD_SS_Study[fa:fa-square-caret-down Study Model] FN_SS_Study_accession_all_samples + FN_SS_Study_validate_study_for_accessioning end subgraph CT_SS_Samples[fa:fa-arrows-spin Samples Controller] FN_SS_Samples_accession @@ -148,17 +160,23 @@ flowchart LR FN_SS_Accession_accession_sample CP_SS_AccessionSubmission FN_SS_Accession_Sample_validate + FN_SS_Accession_Sample_update_accession_number end subgraph MD_SS_AccessionService[fa:fa-square-caret-down AccessionService Model - studies only] + FN_SS_AccessionService_select_for_study FN_SS_AccessionService_submit_study_for_user FN_SS_AccessionService_submit end subgraph MD_SS_Sample[fa:fa-square-caret-down Sample Model] FN_SS_Sample_accession + FN_SS_Sample_accession_and_handle_validation_errors FN_SS_Sample_validate_accessionable - FN_SS_Sample_validate_ena_required_fields end end + subgraph External_EBI[fa:fa-globe EBI ENA Webin REST V1] + External_EBI_Studies + External_EBI_Samples + end subgraph Consumers User_LB_Users User_SS_Users @@ -182,52 +200,51 @@ flowchart LR %% Manifest upload User_SSR --> RES_Manifest -..-> UI_SS_Manifest_Upload --> MD_SS_SampleManifest_Uploader - MD_SS_SampleManifest_Uploader --> CP_SS_SampleManifestExcel_Upload ---> FN_SS_Sample_accession + MD_SS_SampleManifest_Uploader --> FN_SS_SampleManifestExcel_Upload_Base_trigger_accessioning ---> FN_SS_Accession_accession_sample %% Sample generate accession number - User_Neil --> UI_SS_Sample_GAN User_SSR --> UI_SS_Sample_GAN - %% FN_SS_Samples_accession --> | 1. |CF_SS_accession_samples + User_Neil --> UI_SS_Sample_GAN UI_SS_Sample_GAN --> FN_SS_Samples_accession - FN_SS_Samples_accession --> | 2. | FN_SS_Sample_validate_ena_required_fields - FN_SS_Sample_accession ==> FN_SS_Accession_accession_sample + FN_SS_Samples_accession <--> | 1. | FN_SS_Accession_accession_sample FN_SS_Accession_accession_sample ==> DJ_SS_SampleAccessioningJob - DJ_SS_SampleAccessioningJob --> | 1. | FN_SS_Accession_Sample_validate + DJ_SS_SampleAccessioningJob <--> | 1. | FN_SS_Accession_Sample_validate DJ_SS_SampleAccessioningJob ==> | 2. | CP_SS_AccessionSubmission CP_SS_AccessionSubmission ==> LB_SS_AccessioningV1Client - LB_SS_AccessioningV1Client ==> External_EBI - FN_SS_AccessionService_submit ==> External_EBI - External_EBI ==> MD_SS_AccessionStatuses - MD_SS_AccessionStatuses ==> VW_SS_Sample ==> User_SS_Users - FN_SS_Sample_validate_ena_required_fields -..-> | Validation Failed | FN_SS_Samples_accession - External_EBI -..-> | FROM AccessionService.submit | FN_SS_AccessionService_submit - FN_SS_Samples_accession -..-> | flash message | User_SS_Users + LB_SS_AccessioningV1Client ==> External_EBI_Samples + External_EBI_Samples ==> | Success: accession number | FN_SS_Accession_Sample_update_accession_number + External_EBI_Samples ==> | Failure: reason | MD_SS_AccessionStatuses + FN_SS_Accession_Sample_update_accession_number -.- VW_SS_Sample + MD_SS_AccessionStatuses -.- VW_SS_Sample + FN_SS_Samples_accession --> | 2. | VW_SS_Sample + VW_SS_Sample --> User_SS_Users + + %% Sample save and accession + User_SSR --> UI_SS_Sample_SAA + UI_SS_Sample_SAA --> FN_SS_Sample_accession_and_handle_validation_errors + FN_SS_Sample_accession_and_handle_validation_errors --> FN_SS_Accession_accession_sample %% Study accession all samples - User_Study_Owners --> UI_SS_Study_AAS User_Neil --> UI_SS_Study_AAS + User_Study_Owners --> UI_SS_Study_AAS UI_SS_Study_AAS --> FN_SS_Studies_accession_all_samples - FN_SS_Studies_accession_all_samples --> FN_SS_Study_accession_all_samples - FN_SS_Study_accession_all_samples -..-> | error messages | FN_SS_Studies_accession_all_samples - FN_SS_Studies_accession_all_samples -..-> | flash message | User_SS_Users - FN_SS_Study_accession_all_samples --> FN_SS_Sample_accession - FN_SS_Sample_accession -..-> | FROM accession_all_samples, error messages | FN_SS_Study_accession_all_samples - %% FN_SS_Sample_accession --> | 1. | CF_SS_accession_samples - FN_SS_Sample_accession --> | 2. | FN_SS_Sample_validate_accessionable + FN_SS_Studies_accession_all_samples <--> | 1. | FN_SS_Study_accession_all_samples + FN_SS_Study_accession_all_samples --> FN_SS_Accession_accession_sample + FN_SS_Studies_accession_all_samples --> | 2. | VW_SS_Study %% Study generate accession number User_Study_Owners --> UI_SS_Study_GAN UI_SS_Study_GAN --> FN_SS_Studies_accession - FN_SS_Studies_accession --> | 1. | FN_SS_Studies_validate_ena_required_fields - FN_SS_Studies_accession --> | 2. | FN_SS_AccessionService_submit_study_for_user - FN_SS_Studies_accession -..-> | 3. IF success -- flash message | User_SS_Users - FN_SS_Studies_accession --> | IF error | FN_SS_Studies_rescue_accession_errors - %% FN_SS_AccessionService_submit_study_for_user --> CF_SS_accession_samples + FN_SS_Studies_accession <--> | 1. | FN_SS_Study_validate_study_for_accessioning + FN_SS_Studies_accession <--> | 2. | FN_SS_AccessionService_select_for_study + FN_SS_Studies_accession <--> | 3. | FN_SS_AccessionService_submit_study_for_user + FN_SS_Studies_accession --> | 4. IF success -- flash message | VW_SS_Study + FN_SS_Studies_accession --> | 4. IF error | FN_SS_Studies_rescue_accession_errors FN_SS_AccessionService_submit_study_for_user ==> FN_SS_AccessionService_submit - FN_SS_AccessionService_submit -..-> | FROM submit_study_for_user | FN_SS_Studies_accession - FN_SS_Studies_rescue_accession_errors -..-> | flash message | User_SS_Users - - User_Developers -.-> | slack / email | User_SS_Users + FN_SS_AccessionService_submit ==> External_EBI_Studies + External_EBI_Studies ==> FN_SS_Accessionable_Study_update_accession_number + FN_SS_Studies_rescue_accession_errors --> | flash message | VW_SS_Study + VW_SS_Study --> User_SS_Users %% Subgraph styling @@ -261,4 +278,4 @@ flowchart LR class L_Model,MD_SS_Study,MD_SS_AccessionService,MD_SS_Sample,MD_SS_SampleManifest_Uploader,MD_SS_Order,MD_SS_Submission_AccessionBehaviour railsModel; class L_Controller,CT_SS_Samples,CT_SS_Studies railsController; class L_User,User_SeqOps,User_Study_Owners,User_Neil,User_SSR,User_LB_Users,User_SS_Users,User_Developers users; - class L_Interface,UI_LB_Charge_and_Pass,UI_SS_Sample_GAN,UI_SS_Study_GAN,UI_SS_Study_AAS,UI_SS_Manifest_Upload userInterface; + class L_Interface,UI_LB_Charge_and_Pass,UI_SS_Sample_GAN,UI_SS_Sample_SAA,UI_SS_Study_GAN,UI_SS_Study_AAS,UI_SS_Manifest_Upload userInterface; From f13e13c5756ba8d857e02de56fa66ae39806e483 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Thu, 29 Jan 2026 09:14:59 +0000 Subject: [PATCH 41/92] doc: more cleanup --- docs/accessioning/accessioning.mermaid | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index b7452f7396..fe077aaa95 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -77,17 +77,15 @@ flowchart LR FN_SS_AccessionService_submit_study_for_user(fa:fa-caret-right submit_study_for_user) FN_SS_AccessionService_submit(fa:fa-caret-right submit) FN_SS_Accessionable_Study_update_accession_number(fa:fa-caret-right Accessionable::Study#update_accession_number!) - FN_SS_Sample_accession(fa:fa-caret-right accession) FN_SS_Sample_accession_and_handle_validation_errors(fa:fa-caret-right accession_and_handle_validation_errors) - FN_SS_Sample_validate_accessionable(fa:fa-caret-right validate_accessionable!) FN_SS_Samples_accession(fa:fa-caret-right accession) FN_SS_Study_accession_all_samples(fa:fa-caret-right accession_all_samples) FN_SS_Studies_accession_all_samples(fa:fa-caret-right accession_all_samples) FN_SS_Studies_accession(fa:fa-caret-right accession) FN_SS_Studies_rescue_accession_errors(fa:fa-caret-right rescue_accession_errors) FN_SS_Accession_accession_sample(fa:fa-caret-right accession_sample) - FN_SS_Accession_Sample_validate(fa:fa-caret-right Accession::Sample#validate) - FN_SS_Accession_Sample_update_accession_number(fa:fa-caret-right Accession::Sample#update_accession_number) + FN_SS_Accession_Sample_validate(fa:fa-caret-right validate) + FN_SS_Accession_Sample_update_accession_number(fa:fa-caret-right update_accession_number) FN_SS_Study_validate_study_for_accessioning(fa:fa-caret-right validate_study_for_accessioning!) %% Libraries @@ -141,6 +139,9 @@ flowchart LR UI_SS_Study_GAN UI_SS_Study_AAS end + subgraph Sample Manifests + UI_SS_Manifest_Upload + end subgraph SS_API["SS API"] API_SS_OrderResource end @@ -159,8 +160,10 @@ flowchart LR subgraph LB_SS_Accession[fa:fa-book Accession - samples only] FN_SS_Accession_accession_sample CP_SS_AccessionSubmission - FN_SS_Accession_Sample_validate - FN_SS_Accession_Sample_update_accession_number + subgraph MD_SS_Accession_Sample[fa:fa-square-caret-down Accession::Sample Model] + FN_SS_Accession_Sample_validate + FN_SS_Accession_Sample_update_accession_number + end end subgraph MD_SS_AccessionService[fa:fa-square-caret-down AccessionService Model - studies only] FN_SS_AccessionService_select_for_study @@ -168,9 +171,7 @@ flowchart LR FN_SS_AccessionService_submit end subgraph MD_SS_Sample[fa:fa-square-caret-down Sample Model] - FN_SS_Sample_accession FN_SS_Sample_accession_and_handle_validation_errors - FN_SS_Sample_validate_accessionable end end subgraph External_EBI[fa:fa-globe EBI ENA Webin REST V1] @@ -188,8 +189,8 @@ flowchart LR Providers ~~~ Application_Limber ~~~ Consumers Providers ~~~ Application_Sequencescape ~~~ Consumers - CT_SS_Studies ~~~ MD_SS_Study ~~~ CT_SS_Samples ~~~ MD_SS_Sample - FN_SS_Samples_accession ~~~ FN_SS_AccessionService_submit_study_for_user + External_EBI_Studies ~~~ External_EBI_Samples + %% External_EBI_Samples ~~~ External_EBI_Studies %% Limber-related User_SeqOps --> UI_LB_Charge_and_Pass -...-> User_LB_Users From ffe65b202782fdeb248807a324f84f9ccae4d8e6 Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal <pubudumald@gmail.com> Date: Mon, 2 Feb 2026 09:24:05 +0000 Subject: [PATCH 42/92] Remove unnecessary files --- app/models/library_plate_well.rb | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 app/models/library_plate_well.rb diff --git a/app/models/library_plate_well.rb b/app/models/library_plate_well.rb deleted file mode 100644 index f466d8eaa1..0000000000 --- a/app/models/library_plate_well.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true -class LibraryPlateWell < Well - def subject_type - 'library_plate_well' - end -end From 91641accc8e8ed555d9bb550d77a78dda96d34d8 Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal <pubudumald@gmail.com> Date: Mon, 2 Feb 2026 13:01:44 +0000 Subject: [PATCH 43/92] refactor: simplify stock resource registration and update subject types --- app/models/library_tube.rb | 4 -- app/models/messenger.rb | 36 +------------- app/models/multiplexed_library_tube.rb | 4 -- app/models/sample_manifest/core_behaviour.rb | 2 +- .../library_plate_behaviour.rb | 4 -- .../sample_manifest/library_tube_behaviour.rb | 4 -- .../multiplexed_library_behaviour.rb | 4 -- app/models/sample_manifest/plate_behaviour.rb | 9 ---- .../subscriber/queue_broadcast_consumer.rb | 3 -- app/models/well.rb | 4 +- .../sample_manifest_excel/upload/base.rb | 14 +----- spec/models/broadcast_event/lab_event_spec.rb | 4 +- test/unit/messaging/messenger_test.rb | 47 ++----------------- 13 files changed, 10 insertions(+), 129 deletions(-) diff --git a/app/models/library_tube.rb b/app/models/library_tube.rb index e3424f05ae..55570e19f3 100644 --- a/app/models/library_tube.rb +++ b/app/models/library_tube.rb @@ -13,10 +13,6 @@ def self.stock_asset_purpose Tube::Purpose.stock_library_tube end - def subject_type - 'library_tube' - end - def library_information # rubocop:todo Metrics/AbcSize tag = aliquots.first.tag tag2 = aliquots.first.tag2 diff --git a/app/models/messenger.rb b/app/models/messenger.rb index d8ee8b6d3b..f12e3bf46b 100644 --- a/app/models/messenger.rb +++ b/app/models/messenger.rb @@ -15,20 +15,7 @@ def routing_key end def as_json(_options = {}) - message = render_class.to_hash(target) - Rails.logger.info("Publishing message: #{message}") - { root => process_receptacles(message), 'lims' => configatron.amqp.lims_id! } - end - - # Processes the message for receptacle targets, setting labware type if applicable. - # @param message [Hash] The message to process. - # @return [Hash] The processed message. - def process_receptacles(message) - return message unless receptacle_target? - - asset_type = fetch_asset_type - message['labware_type'] = 'library_plate_well' if library_plate?(asset_type) - message + { root => render_class.to_hash(target), 'lims' => configatron.amqp.lims_id! } end def template @@ -41,25 +28,4 @@ def template def resend Warren.handler << Warren::Message::Short.new(self) end - - private - - # Checks if the target type is 'Receptacle'. - # @return [Boolean] True if target type is 'Receptacle', false otherwise. - def receptacle_target? - target_type == 'Receptacle' - end - - # Fetches the asset type for the current target. - # @return [String, nil] The asset type or nil if not found. - def fetch_asset_type - SampleManifestAsset.find_by(asset_id: target_id)&.sample_manifest&.asset_type - end - - # Determines if the asset type is a library plate. - # @param asset_type [String, nil] The asset type to check. - # @return [Boolean] True if asset type is 'library_plate', false otherwise. - def library_plate?(asset_type) - asset_type.present? && asset_type == 'library_plate' - end end diff --git a/app/models/multiplexed_library_tube.rb b/app/models/multiplexed_library_tube.rb index e0f5fe7d19..9d5453273b 100644 --- a/app/models/multiplexed_library_tube.rb +++ b/app/models/multiplexed_library_tube.rb @@ -17,10 +17,6 @@ def asset_type_for_request_types LibraryTube end - def subject_type - 'multiplexed_library_tube' - end - def team creation_requests.first&.product_line end diff --git a/app/models/sample_manifest/core_behaviour.rb b/app/models/sample_manifest/core_behaviour.rb index af93f5da45..deb8950cc5 100644 --- a/app/models/sample_manifest/core_behaviour.rb +++ b/app/models/sample_manifest/core_behaviour.rb @@ -74,7 +74,7 @@ def generate_sample_and_aliquot(sanger_sample_id, receptacle) end def stocks? - false + true end end diff --git a/app/models/sample_manifest/library_plate_behaviour.rb b/app/models/sample_manifest/library_plate_behaviour.rb index 29697aebbe..3b7ab0ef6b 100644 --- a/app/models/sample_manifest/library_plate_behaviour.rb +++ b/app/models/sample_manifest/library_plate_behaviour.rb @@ -5,9 +5,5 @@ module SampleManifest::LibraryPlateBehaviour # it sets library_id on aliquots in wells and doesn't generate stock assets. class Core < SampleManifest::PlateBehaviour::Base include SampleManifest::CoreBehaviour::LibraryAssets - - def stocks? - true - end end end diff --git a/app/models/sample_manifest/library_tube_behaviour.rb b/app/models/sample_manifest/library_tube_behaviour.rb index 57687d00a5..1c1d5e30e8 100644 --- a/app/models/sample_manifest/library_tube_behaviour.rb +++ b/app/models/sample_manifest/library_tube_behaviour.rb @@ -53,9 +53,5 @@ def default_purpose def included_resources [{ sample: :sample_metadata, asset: %i[barcodes aliquots] }] end - - def stocks? - true - end end end diff --git a/app/models/sample_manifest/multiplexed_library_behaviour.rb b/app/models/sample_manifest/multiplexed_library_behaviour.rb index 9f7d8fcb98..582ae5ee87 100644 --- a/app/models/sample_manifest/multiplexed_library_behaviour.rb +++ b/app/models/sample_manifest/multiplexed_library_behaviour.rb @@ -76,9 +76,5 @@ def printables def included_resources [{ sample: :sample_metadata, asset: [:barcodes, :aliquots, { requests: :target_asset }] }] end - - def stocks? - true - end end end diff --git a/app/models/sample_manifest/plate_behaviour.rb b/app/models/sample_manifest/plate_behaviour.rb index bad48f1a40..70e2eaea54 100644 --- a/app/models/sample_manifest/plate_behaviour.rb +++ b/app/models/sample_manifest/plate_behaviour.rb @@ -11,15 +11,6 @@ def initialize(manifest) @plates = [] end - # Generates plates and associated data for the sample manifest. - # - # Steps: - # 1. Generates new plates for the given purpose. - # 2. Inserts Sanger sample IDs for the plates. - # 3. Builds well data mapping plates, maps, and sample IDs. - # 4. Enqueues asynchronous jobs to build wells for each plate. - # 5. Constructs an array of details for each well. - # 6. Updates the manifest with the barcodes of the generated plates. def generate @plates = generate_plates(purpose) diff --git a/app/models/warren/subscriber/queue_broadcast_consumer.rb b/app/models/warren/subscriber/queue_broadcast_consumer.rb index d403f9f4fe..1ef7b59e73 100644 --- a/app/models/warren/subscriber/queue_broadcast_consumer.rb +++ b/app/models/warren/subscriber/queue_broadcast_consumer.rb @@ -24,9 +24,6 @@ class Warren::Subscriber::QueueBroadcastConsumer < Warren::Subscriber::Base # Handles message processing. Messages are acknowledged automatically # on return from the method assuming they haven't been handled already. # In the event of an uncaught exception, the message will be dead-lettered. - # - # For example, if the message is `["Well", 1]`, this method will find the Well - # with ID 1 and call `broadcast` on it. def process klass = json.first.constantize klass.find(json.last).broadcast diff --git a/app/models/well.rb b/app/models/well.rb index 78893303a8..2607705fa7 100644 --- a/app/models/well.rb +++ b/app/models/well.rb @@ -208,11 +208,9 @@ def stock_wells_for_downstream_wells end def subject_type - @subject_type ||= 'well' + 'well' end - attr_writer :subject_type - def outer_request(submission_id) outer_requests.order(id: :desc).find_by(submission_id:) end diff --git a/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb b/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb index 205b338988..9529168524 100644 --- a/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb +++ b/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb @@ -113,7 +113,7 @@ def trigger_accessioning(event_user) "Skipping accessioning of samples in manifest #{sample_manifest.id}." end - # If samples have been created, register a stock_resource record in the MLWH + # If samples have been created, and it's not a library plate/tube, register a stock_resource record in the MLWH def register_stock_resources stock_receptacles_to_be_registered.each(&:register_stock!) end @@ -190,20 +190,10 @@ def changed_labware @changed_labware ||= rows.select(&:changed?).reduce(Set.new) { |set, row| set << row.labware } end - def update_subject_type_for_library_receptacles!(asset) - return unless sample_manifest.core_behaviour.to_s.include?('LibraryPlateBehaviour') - - asset.subject_type = 'library_plate_well' - end - def stock_receptacles_to_be_registered return [] unless sample_manifest.core_behaviour.stocks? - @stock_receptacles_to_be_registered ||= rows.select(&:sample_created?) - .map(&:asset) - .each do |asset| - update_subject_type_for_library_receptacles!(asset) - end + @stock_receptacles_to_be_registered ||= rows.select(&:sample_created?).map(&:asset) end end end diff --git a/spec/models/broadcast_event/lab_event_spec.rb b/spec/models/broadcast_event/lab_event_spec.rb index a35d4ed8c0..1f06d87ce6 100644 --- a/spec/models/broadcast_event/lab_event_spec.rb +++ b/spec/models/broadcast_event/lab_event_spec.rb @@ -70,7 +70,7 @@ }, { 'role_type' => 'sequencing_source_labware', - 'subject_type' => 'library_tube', + 'subject_type' => 'tube', 'uuid' => stock_asset.uuid, 'friendly_name' => stock_asset.human_barcode } @@ -100,7 +100,7 @@ }, { 'role_type' => 'sequencing_source_labware', - 'subject_type' => 'library_tube', + 'subject_type' => 'tube', 'uuid' => stock_asset.uuid, 'friendly_name' => stock_asset.human_barcode } diff --git a/test/unit/messaging/messenger_test.rb b/test/unit/messaging/messenger_test.rb index 7423014ec8..d9ef54396a 100644 --- a/test/unit/messaging/messenger_test.rb +++ b/test/unit/messaging/messenger_test.rb @@ -2,29 +2,18 @@ require 'test_helper' -# Structs for configatron stub -AmqpStruct = Struct.new(:lims_id!, keyword_init: true) -ConfigatronStruct = Struct.new(:amqp, keyword_init: true) - class MessengerTest < ActiveSupport::TestCase context '#Messenger' do setup do @target = Batch.new + + # @target.stubs(:class).returns(Batch) @template = 'FlowcellIo' @messenger = Messenger.new(target: @target, template: @template, root: 'example') end context 'to_json' do - setup do - Api::Messages::FlowcellIo.expects(:to_hash).with(@target).returns('example' => 'hash') - # Stub configatron for lims_id! using Struct (defined at top) - amqp = AmqpStruct.new(lims_id!: 'SQSCP') - configatron = ConfigatronStruct.new(amqp:) - @messenger.stubs(:configatron).returns(configatron) - # Stub process_receptacles to pass through message - @messenger.stubs(:process_receptacles).returns('example' => 'hash') - Rails.logger.stubs(:info) - end + setup { Api::Messages::FlowcellIo.expects(:to_hash).with(@target).returns('example' => 'hash') } should 'render the json' do assert_equal '{"example":{"example":"hash"},"lims":"SQSCP"}', @messenger.to_json @@ -32,40 +21,10 @@ class MessengerTest < ActiveSupport::TestCase should 'render the json when template is historical (ends in IO)' do messenger = Messenger.new(target: @target, template: 'FlowcellIO', root: 'example') - amqp = AmqpStruct.new(lims_id!: 'SQSCP') - configatron = ConfigatronStruct.new(amqp:) - messenger.stubs(:configatron).returns(configatron) - messenger.stubs(:process_receptacles).returns('example' => 'hash') - Rails.logger.stubs(:info) assert_equal '{"example":{"example":"hash"},"lims":"SQSCP"}', messenger.to_json end end - context '#process_receptacles' do - setup do - @message = { 'foo' => 'bar' } - end - - should 'return message unchanged if not a receptacle target' do - @messenger.stubs(:receptacle_target?).returns(false) - assert_equal @message, @messenger.process_receptacles(@message.dup) - end - - should 'add labware_type if asset_type is library_plate' do - @messenger.stubs(:receptacle_target?).returns(true) - @messenger.stubs(:fetch_asset_type).returns('library_plate') - result = @messenger.process_receptacles(@message.dup) - assert_equal 'library_plate_well', result['labware_type'] - end - - should 'not add labware_type if asset_type is not library_plate' do - @messenger.stubs(:receptacle_target?).returns(true) - @messenger.stubs(:fetch_asset_type).returns('other_type') - result = @messenger.process_receptacles(@message.dup) - assert_nil result['labware_type'] - end - end - should 'provide a routing key' do assert_equal @messenger.routing_key, "message.example.#{@messenger.id}" end From 414d4883608c8e4ad8c4241f7fca1b351e18b1cd Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal <pubudumald@gmail.com> Date: Mon, 2 Feb 2026 16:15:49 +0000 Subject: [PATCH 44/92] refactor: update stock resource registration logic and improve uploader specs --- app/sample_manifest_excel/sample_manifest_excel/upload/base.rb | 2 +- spec/models/sample_manifest/uploader_spec.rb | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb b/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb index 9529168524..09571f2a7b 100644 --- a/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb +++ b/app/sample_manifest_excel/sample_manifest_excel/upload/base.rb @@ -113,7 +113,7 @@ def trigger_accessioning(event_user) "Skipping accessioning of samples in manifest #{sample_manifest.id}." end - # If samples have been created, and it's not a library plate/tube, register a stock_resource record in the MLWH + # If samples have been created, register a stock_resource record in the MLWH def register_stock_resources stock_receptacles_to_be_registered.each(&:register_stock!) end diff --git a/spec/models/sample_manifest/uploader_spec.rb b/spec/models/sample_manifest/uploader_spec.rb index 7f2e607cf9..65cced35cb 100644 --- a/spec/models/sample_manifest/uploader_spec.rb +++ b/spec/models/sample_manifest/uploader_spec.rb @@ -109,7 +109,7 @@ ) download.save(test_file_name) uploader = described_class.new(test_file, SampleManifestExcel.configuration, user, false) - uploader.run! + expect { uploader.run! }.to change(Messenger, :count).by(6) expect(uploader).to be_processed expect(BroadcastEvent.count).to eq broadcast_events_count + 1 expect(uploader.upload.sample_manifest).to be_completed @@ -141,7 +141,6 @@ ) download.save(test_file_name) uploader = described_class.new(test_file, SampleManifestExcel.configuration, user, false) - uploader.run! expect(uploader).to be_processed expect(BroadcastEvent.count).to eq broadcast_events_count + 1 expect(uploader.upload.sample_manifest).to be_completed From 32285de424aef2c8bad0d06985f1bd0d9b15fa93 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Wed, 4 Feb 2026 12:28:32 +0000 Subject: [PATCH 45/92] fix: remove obsolete redirect to the accession-statuses tab --- app/controllers/samples_controller.rb | 2 +- app/controllers/studies_controller.rb | 2 +- spec/controllers/studies_controller_spec.rb | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/controllers/samples_controller.rb b/app/controllers/samples_controller.rb index 3316865846..d2bb705bdf 100644 --- a/app/controllers/samples_controller.rb +++ b/app/controllers/samples_controller.rb @@ -189,7 +189,7 @@ def accession # rubocop:disable Metrics/AbcSize,Metrics/MethodLength flash[:error] = "Accessioning failed with a network error: #{e.message}" ensure # Redirect back to where we came from if not already redirected - redirect_back_with_anchor_or_to(sample_path(@sample), anchor: 'accession-statuses') unless performed? + redirect_back_with_anchor_or_to(sample_path(@sample)) unless performed? end private diff --git a/app/controllers/studies_controller.rb b/app/controllers/studies_controller.rb index 7598cc2899..e1a7263cc9 100644 --- a/app/controllers/studies_controller.rb +++ b/app/controllers/studies_controller.rb @@ -269,7 +269,7 @@ def accession_all_samples # rubocop:disable Metrics/AbcSize,Metrics/MethodLength flash[:notice] = 'All of the samples in this study have been sent for accessioning. ' \ 'Please check back in 5 minutes to confirm that accessioning was successful.' end - redirect_to(study_path(@study, anchor: 'accession-statuses')) + redirect_to(study_path(@study)) end def dac_accession diff --git a/spec/controllers/studies_controller_spec.rb b/spec/controllers/studies_controller_spec.rb index 16c470b865..291aed2aee 100644 --- a/spec/controllers/studies_controller_spec.rb +++ b/spec/controllers/studies_controller_spec.rb @@ -191,8 +191,8 @@ end context 'when the accessioning succeeds' do - it 'redirects to the accession-statuses tab of the study page' do - expect(subject).to redirect_to(study_path(study, anchor: 'accession-statuses')) + it 'redirects to the study page' do + expect(subject).to redirect_to(study_path(study)) end it 'does not set a flash error message' do @@ -220,8 +220,8 @@ let(:samples) { create_list(:sample, number_of_samples) } let(:study) { create(:managed_study, accession_number: 'EGA123', samples: samples) } - it 'redirects to the accession-statuses tab of the study page' do - expect(subject).to redirect_to(study_path(study, anchor: 'accession-statuses')) + it 'redirects to the study page' do + expect(subject).to redirect_to(study_path(study)) end it 'does not set a flash notice message' do @@ -285,8 +285,8 @@ let(:samples) { create_list(:sample_for_accessioning_with_open_study, number_of_samples) } let(:study) { create(:managed_study, accession_number: 'EGA123', samples: samples) } - it 'redirects to the accession-statuses tab of the study page' do - expect(subject).to redirect_to(study_path(study, anchor: 'accession-statuses')) + it 'redirects to the study page' do + expect(subject).to redirect_to(study_path(study)) end it 'sets a flash notice message' do From 404f06e81842684f0451d1a7067f3daee0796597 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Wed, 4 Feb 2026 16:37:37 +0000 Subject: [PATCH 46/92] refactor: remove unused AccessionService::UnsuitableService --- .../accession_service/unsuitable_service.rb | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 app/models/accession_service/unsuitable_service.rb diff --git a/app/models/accession_service/unsuitable_service.rb b/app/models/accession_service/unsuitable_service.rb deleted file mode 100644 index a07cf238c0..0000000000 --- a/app/models/accession_service/unsuitable_service.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true -# Used for samples/studies which are neither open or managed. -class AccessionService::UnsuitableService < AccessionService::BaseService - self.no_study_accession_needed = true - - def initialize(studies) - @study_ids = studies.map(&:id) - end - - def provider - :unsuitable - end - - def submit(_user, *_accessionables) - raise AccessionService::NumberNotGenerated, - I18n.t(:no_suitable_study, scope: 'accession_service.unsuitable', study_ids: @study_ids.to_sentence) - end - - def submit_study_for_user(_study, _user) - raise StandardError, - # rubocop:todo Layout/LineLength - 'UnsuitableAccessionService should only be used for samples. This is a problem with Sequencescape and should be reported.' - # rubocop:enable Layout/LineLength - end - - def submit_dac_for_user(_study, _user) - raise StandardError, - # rubocop:todo Layout/LineLength - 'UnsuitableAccessionService should only be used for samples. This is a problem with Sequencescape and should be reported.' - # rubocop:enable Layout/LineLength - end -end From 6a22cc6aeb2d80191ef87e157c0dc51a54843d6c Mon Sep 17 00:00:00 2001 From: Dasun Pubudumal <pubudumald@gmail.com> Date: Fri, 6 Feb 2026 10:01:20 +0000 Subject: [PATCH 47/92] test: invoke uploader run method in uploader_spec --- spec/models/sample_manifest/uploader_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/models/sample_manifest/uploader_spec.rb b/spec/models/sample_manifest/uploader_spec.rb index 65cced35cb..fee55ee250 100644 --- a/spec/models/sample_manifest/uploader_spec.rb +++ b/spec/models/sample_manifest/uploader_spec.rb @@ -141,6 +141,7 @@ ) download.save(test_file_name) uploader = described_class.new(test_file, SampleManifestExcel.configuration, user, false) + uploader.run! expect(uploader).to be_processed expect(BroadcastEvent.count).to eq broadcast_events_count + 1 expect(uploader.upload.sample_manifest).to be_completed From c0ecbc342500376adf97b6a8df6e97155af176ec Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Fri, 6 Feb 2026 10:43:35 +0000 Subject: [PATCH 48/92] style: lint --- .../studies/accession_study_dac_policy_spec.rb | 11 +++++++---- spec/features/studies/accession_study_spec.rb | 11 +++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/spec/features/studies/accession_study_dac_policy_spec.rb b/spec/features/studies/accession_study_dac_policy_spec.rb index a5d8f9b3af..9bdff44778 100644 --- a/spec/features/studies/accession_study_dac_policy_spec.rb +++ b/spec/features/studies/accession_study_dac_policy_spec.rb @@ -16,7 +16,7 @@ let(:study) { create(:managed_study, :with_data_access_contacts, data_access_contacts:) } before do - allow_any_instance_of(RestClient::Resource).to receive(:post) + allow_any_instance_of(RestClient::Resource).to receive(:post) # rubocop:disable RSpec/AnyInstance .and_return(successful_dac_policy_accession_response) visit study_path(study) @@ -34,7 +34,9 @@ let(:study) { create(:open_study, :with_data_access_contacts, data_access_contacts:) } before do - allow_any_instance_of(RestClient::Resource).to receive(:post).and_return(failed_accession_response) + allow_any_instance_of(RestClient::Resource).to receive(:post) # rubocop:disable RSpec/AnyInstance + .and_return(failed_accession_response) + visit study_path(study) end @@ -48,7 +50,7 @@ let(:study) { create(:managed_study, :with_data_access_contacts, data_access_contacts:) } before do - allow_any_instance_of(RestClient::Resource).to receive(:post) + allow_any_instance_of(RestClient::Resource).to receive(:post) # rubocop:disable RSpec/AnyInstance .and_return(successful_dac_policy_accession_response) study.study_metadata.update(ega_dac_accession_number: 'EGAD0001000234') # DAC required prior to Policy @@ -68,7 +70,8 @@ let(:study) { create(:open_study, :with_data_access_contacts, data_access_contacts:) } before do - allow_any_instance_of(RestClient::Resource).to receive(:post).and_return(failed_accession_response) + allow_any_instance_of(RestClient::Resource).to receive(:post) # rubocop:disable RSpec/AnyInstance + .and_return(failed_accession_response) visit study_path(study) end diff --git a/spec/features/studies/accession_study_spec.rb b/spec/features/studies/accession_study_spec.rb index e8174fdbf8..eb36b8b768 100644 --- a/spec/features/studies/accession_study_spec.rb +++ b/spec/features/studies/accession_study_spec.rb @@ -25,7 +25,7 @@ context 'when the study already has an accession number' do before do - allow_any_instance_of(RestClient::Resource).to receive(:post).and_return(successful_study_accession_response) + allow_any_instance_of(RestClient::Resource).to receive(:post).and_return(successful_study_accession_response) # rubocop:disable RSpec/AnyInstance study.study_metadata.update!(study_ebi_accession_number: 'EGAN00001000234') visit study_path(study) @@ -50,7 +50,8 @@ context 'when the study gets a valid accession number' do before do - allow_any_instance_of(RestClient::Resource).to receive(:post).and_return(successful_study_accession_response) + allow_any_instance_of(RestClient::Resource).to receive(:post) # rubocop:disable RSpec/AnyInstance + .and_return(successful_study_accession_response) visit study_path(study) end @@ -66,7 +67,8 @@ context 'when the accession number service gives an error' do before do - allow_any_instance_of(RestClient::Resource).to receive(:post).and_return(failed_accession_response) + allow_any_instance_of(RestClient::Resource).to receive(:post) # rubocop:disable RSpec/AnyInstance + .and_return(failed_accession_response) visit study_path(study) end @@ -79,7 +81,8 @@ context 'when the accession number service is unavailable' do before do - allow_any_instance_of(RestClient::Resource).to receive(:post).and_raise(RestClient::ServiceUnavailable) + allow_any_instance_of(RestClient::Resource).to receive(:post) # rubocop:disable RSpec/AnyInstance + .and_raise(RestClient::ServiceUnavailable) visit study_path(study) end From 90e3a01659fb157499a1d8dca034ef24826099f9 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Fri, 6 Feb 2026 10:46:10 +0000 Subject: [PATCH 49/92] style: update rubocop todo --- .rubocop_todo.yml | 40 +++++++++++++++------------------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index c575917b4e..d48cea711e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --no-exclude-limit` -# on 2026-02-04 11:03:49 UTC using RuboCop version 1.84.1. +# on 2026-02-06 10:45:50 UTC using RuboCop version 1.84.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -126,7 +126,7 @@ Lint/DuplicateMethods: - 'app/models/stock_stamper.rb' - 'lib/accession/tag.rb' -# Offense count: 63 +# Offense count: 62 # Configuration parameters: AllowComments, AllowEmptyLambdas. Lint/EmptyBlock: Exclude: @@ -164,7 +164,6 @@ Lint/EmptyBlock: - 'app/api/endpoints/users.rb' - 'app/api/endpoints/uuids.rb' - 'app/api/endpoints/wells.rb' - - 'app/models/accessionable/sample.rb' - 'app/models/plate.rb' - 'app/models/request/library_creation.rb' - 'app/sequencescape_excel/sequencescape_excel/list.rb' @@ -194,12 +193,11 @@ Lint/IneffectiveAccessModifier: - 'app/helpers/plates_helper.rb' - 'lib/authenticated_system.rb' -# Offense count: 14 +# Offense count: 13 # Configuration parameters: AllowedParentClasses. Lint/MissingSuper: Exclude: - 'app/models/accession_service/no_service.rb' - - 'app/models/accession_service/unsuitable_service.rb' - 'app/models/delegate_validation.rb' - 'app/models/request/change_decision.rb' - 'app/models/sample_manifest/library_tube_behaviour.rb' @@ -240,7 +238,7 @@ Lint/StructNewOverride: Exclude: - 'app/models/product_criteria/basic.rb' -# Offense count: 61 +# Offense count: 57 # This cop supports safe autocorrection (--autocorrect). Lint/UselessAssignment: Exclude: @@ -449,7 +447,7 @@ Naming/PredicatePrefix: - 'lib/has_behaviour.rb' - 'lib/manifest_util.rb' -# Offense count: 246 +# Offense count: 242 # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns. # SupportedStyles: snake_case, normalcase, non_integer # AllowedIdentifiers: TLS1_1, TLS1_2, capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64 @@ -535,18 +533,16 @@ Performance/MethodObjectAsBlock: - 'features/support/step_definitions/transfer_steps.rb' - 'lib/tasks/report.rake' -# Offense count: 3 +# Offense count: 2 RSpec/AnyInstance: Exclude: - - 'spec/controllers/samples_controller_spec.rb' - 'spec/controllers/studies_controller_spec.rb' - 'spec/models/lane_spec.rb' -# Offense count: 24 +# Offense count: 22 RSpec/BeforeAfterAll: Exclude: - 'spec/features/sample_manifests/uploader_for_manifests_with_tag_sequences_spec.rb' - - 'spec/lib/accession/contact_spec.rb' - 'spec/models/sample_manifest/generator_spec.rb' - 'spec/models/sample_manifest/uploader_spec.rb' - 'spec/sample_manifest_excel/download_spec.rb' @@ -783,14 +779,13 @@ RSpec/ExampleWording: - 'spec/sequencescape_excel/validation_spec.rb' - 'spec/sequencescape_excel/worksheet_spec.rb' -# Offense count: 258 +# Offense count: 257 # Configuration parameters: AssignmentOnly. RSpec/InstanceVariable: Exclude: - 'spec/api/extraction_attributes_spec.rb' - 'spec/controllers/studies_controller_spec.rb' - 'spec/controllers/submissions_controller_spec.rb' - - 'spec/lib/accession/contact_spec.rb' - 'spec/models/orders/order_spec.rb' - 'spec/models/pulldown/requests_spec.rb' - 'spec/models/qc_report_spec.rb' @@ -832,7 +827,7 @@ RSpec/LeakyLocalVariable: - 'spec/models/well_spec.rb' - 'spec/shared_contexts/it_requires_login.rb' -# Offense count: 43 +# Offense count: 38 RSpec/LetSetup: Exclude: - 'spec/controllers/searches_controller_spec.rb' @@ -893,7 +888,7 @@ RSpec/MultipleDescribes: - 'spec/lib/label_printer/asset_labels_spec.rb' - 'spec/models/qc_result/qc_result_spec.rb' -# Offense count: 929 +# Offense count: 925 # Configuration parameters: Max. RSpec/MultipleExpectations: Exclude: @@ -935,7 +930,6 @@ RSpec/MultipleExpectations: - 'spec/jobs/export_pool_xp_to_traction_job_spec.rb' - 'spec/lib/accession/accessionable_spec.rb' - 'spec/lib/accession/configuration_spec.rb' - - 'spec/lib/accession/contact_spec.rb' - 'spec/lib/accession/sample_spec.rb' - 'spec/lib/accession/service_spec.rb' - 'spec/lib/accession/submission_spec.rb' @@ -1123,7 +1117,7 @@ RSpec/MultipleExpectations: - 'spec/views/labware/show_chromium_chip_spec.rb' - 'spec/views/samples/index_html_erb_spec.rb' -# Offense count: 247 +# Offense count: 242 # Configuration parameters: EnforcedStyle, IgnoreSharedExamples. # SupportedStyles: always, named_only RSpec/NamedSubject: @@ -1141,7 +1135,6 @@ RSpec/NamedSubject: - 'spec/api/work_completion_spec.rb' - 'spec/controllers/studies/information_controller_spec.rb' - 'spec/controllers/studies_controller_spec.rb' - - 'spec/lib/accession/contact_spec.rb' - 'spec/lib/label_printer/sample_manifest_plate_double_spec.rb' - 'spec/models/aliquot_spec.rb' - 'spec/models/api/library_tube_io_spec.rb' @@ -1714,7 +1707,7 @@ Style/InverseMethods: - 'app/models/batch.rb' - 'app/models/extended_validator/species_validator.rb' -# Offense count: 86 +# Offense count: 85 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: line_count_dependent, lambda, literal @@ -1864,7 +1857,7 @@ Style/Not: - 'app/views/batches/show.xml.builder' - 'features/support/step_definitions/api_steps.rb' -# Offense count: 33 +# Offense count: 31 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns. # SupportedStyles: predicate, comparison @@ -1882,7 +1875,6 @@ Style/NumericPredicate: - 'app/models/study.rb' - 'app/models/tag_layout/walk_wells_by_pools.rb' - 'app/models/well_attribute.rb' - - 'features/support/step_definitions/samples_steps.rb' - 'lib/deployed.rb' - 'lib/submission_serializer.rb' - 'test/shoulda_macros/sanger_macros/resource_test.rb' @@ -2001,14 +1993,13 @@ Style/Proc: - 'app/models/lib_pool_norm_tube_generator.rb' - 'app/models/qcable/statemachine.rb' -# Offense count: 4 +# Offense count: 3 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Methods. Style/RedundantArgument: Exclude: - 'app/models/plate/fluidigm_behaviour.rb' - 'app/models/study_report/study_details.rb' - - 'features/support/step_definitions/samples_steps.rb' - 'spec/features/shared_examples/cherrypicking.rb' # Offense count: 7 @@ -2104,11 +2095,10 @@ Style/SymbolProc: - 'app/models/tasks/plate_transfer_handler.rb' - 'db/seeds/0001_workflows.rb' -# Offense count: 9 +# Offense count: 7 # This cop supports unsafe autocorrection (--autocorrect-all). Style/ZeroLengthPredicate: Exclude: - 'app/models/bulk_submission.rb' - 'app/models/sequencing_pipeline.rb' - - 'features/support/step_definitions/samples_steps.rb' - 'test/shoulda_macros/sanger_macros/resource_test.rb' From 70083d64b8b54f264fcbd64cf325093a740d7ac0 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Fri, 6 Feb 2026 14:09:28 +0000 Subject: [PATCH 50/92] docs: update labels --- docs/accessioning/accessioning.mermaid | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index fe077aaa95..7b692f8f88 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -36,15 +36,15 @@ flowchart LR %% Users User_SeqOps(fa:fa-user SeqOps) User_Study_Owners(fa:fa-user Study Owners) - User_Neil(fa:fa-user Neil) + User_Neil(fa:fa-user PSD Support) User_SSR(fa:fa-user SSRs) User_LB_Users(fa:fa-user LB Users) User_Developers(fa:fa-user Developers) User_SS_Users(fa:fa-user SS Users) %% External Systems - External_EBI_Studies(fa:fa-arrow-up EBI Studies) - External_EBI_Samples(fa:fa-arrow-up EBI Samples) + External_EBI_Studies(fa:fa-arrow-up ENA/EGA Studies) + External_EBI_Samples(fa:fa-arrow-up ENA/EGA Samples) %% User Interfaces UI_LB_Charge_and_Pass(fa:fa-computer-mouse Charge and Pass) From 08022ee02357f289f01959b47c88e9fed6320940 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Fri, 6 Feb 2026 14:13:20 +0000 Subject: [PATCH 51/92] style: update rubocop todo --- .rubocop_todo.yml | 73 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d48cea711e..88182357af 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --no-exclude-limit` -# on 2026-02-06 10:45:50 UTC using RuboCop version 1.84.1. +# on 2026-02-06 14:12:50 UTC using RuboCop version 1.84.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -352,6 +352,51 @@ Metrics/PerceivedComplexity: Exclude: - 'app/models/accession_service/base_service.rb' +# Offense count: 14 +# This cop supports unsafe autocorrection (--autocorrect-all). +Minitest/AssertTruthy: + Exclude: + - 'test/controllers/admin_programs_controller_test.rb' + - 'test/controllers/plates_controller_test.rb' + - 'test/unit/batch_test.rb' + - 'test/unit/data_release_test.rb' + - 'test/unit/flexible_submission_test.rb' + - 'test/unit/import_fluidigm_data_test.rb' + - 'test/unit/parsers/plate_reader_parser_test.rb' + - 'test/unit/qc_report_file_test.rb' + - 'test/unit/user_test.rb' + +# Offense count: 2 +Minitest/AssertWithExpectedArgument: + Exclude: + - 'test/unit/batch_test.rb' + +# Offense count: 19 +# This cop supports unsafe autocorrection (--autocorrect-all). +Minitest/RefuteFalse: + Exclude: + - 'test/controllers/admin_programs_controller_test.rb' + - 'test/controllers/plates_controller_test.rb' + - 'test/unit/asset_group_test.rb' + - 'test/unit/barcode_test.rb' + - 'test/unit/batch_test.rb' + - 'test/unit/import_fluidigm_data_test.rb' + - 'test/unit/project_test.rb' + - 'test/unit/qc_metric_test.rb' + - 'test/unit/qc_report_file_test.rb' + - 'test/unit/user_test.rb' + +# Offense count: 2 +Minitest/TestFileName: + Exclude: + - 'test/controllers/api/submissions_controller.rb' + - 'test/unit/illumina_b/request_validation.rb' + +# Offense count: 2 +Minitest/UselessAssertion: + Exclude: + - 'test/shoulda_macros/sanger_macros.rb' + # Offense count: 13 Naming/AccessorMethodName: Exclude: @@ -554,7 +599,7 @@ RSpec/BeforeAfterAll: - 'spec/sample_manifest_excel/worksheet_spec.rb' - 'spec/sequencescape_excel/worksheet_spec.rb' -# Offense count: 341 +# Offense count: 342 # Configuration parameters: Prefixes, AllowedPatterns. # Prefixes: when, with, without RSpec/ContextWording: @@ -827,7 +872,7 @@ RSpec/LeakyLocalVariable: - 'spec/models/well_spec.rb' - 'spec/shared_contexts/it_requires_login.rb' -# Offense count: 38 +# Offense count: 39 RSpec/LetSetup: Exclude: - 'spec/controllers/searches_controller_spec.rb' @@ -1117,7 +1162,7 @@ RSpec/MultipleExpectations: - 'spec/views/labware/show_chromium_chip_spec.rb' - 'spec/views/samples/index_html_erb_spec.rb' -# Offense count: 242 +# Offense count: 246 # Configuration parameters: EnforcedStyle, IgnoreSharedExamples. # SupportedStyles: always, named_only RSpec/NamedSubject: @@ -1484,12 +1529,11 @@ Security/Open: Exclude: - 'app/api/core/io/json/stream.rb' -# Offense count: 2 +# Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). Style/ArrayIntersect: Exclude: - 'app/models/bulk_submission.rb' - - 'test/unit/flexible_submission_test.rb' # Offense count: 14 Style/ClassVars: @@ -1575,7 +1619,7 @@ Style/ExplicitBlockArgument: - 'app/models/submission/flexible_request_graph.rb' - 'features/support/step_definitions/web_steps.rb' -# Offense count: 7 +# Offense count: 5 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: format, sprintf, percent @@ -1585,7 +1629,6 @@ Style/FormatString: - 'app/models/map.rb' - 'features/support/step_definitions/4560014_refactoring_properties_descriptors_etc_to_table_columns_steps.rb' - 'features/support/step_definitions/samples_steps.rb' - - 'test/unit/fluidigm_plate_test.rb' # Offense count: 5 # This cop supports safe autocorrection (--autocorrect). @@ -1833,14 +1876,6 @@ Style/NestedParenthesizedCalls: - 'app/controllers/uuids_controller.rb' - 'app/models/parsers/bioanalysis_csv_parser.rb' -# Offense count: 2 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: IncludeSemanticChanges. -Style/NonNilCheck: - Exclude: - - 'test/unit/product_criteria_test.rb' - - 'test/unit/product_test.rb' - # Offense count: 18 # This cop supports safe autocorrection (--autocorrect). Style/Not: @@ -1857,7 +1892,7 @@ Style/Not: - 'app/views/batches/show.xml.builder' - 'features/support/step_definitions/api_steps.rb' -# Offense count: 31 +# Offense count: 30 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns. # SupportedStyles: predicate, comparison @@ -1877,7 +1912,6 @@ Style/NumericPredicate: - 'app/models/well_attribute.rb' - 'lib/deployed.rb' - 'lib/submission_serializer.rb' - - 'test/shoulda_macros/sanger_macros/resource_test.rb' # Offense count: 18 # Configuration parameters: AllowedMethods. @@ -2095,10 +2129,9 @@ Style/SymbolProc: - 'app/models/tasks/plate_transfer_handler.rb' - 'db/seeds/0001_workflows.rb' -# Offense count: 7 +# Offense count: 6 # This cop supports unsafe autocorrection (--autocorrect-all). Style/ZeroLengthPredicate: Exclude: - 'app/models/bulk_submission.rb' - 'app/models/sequencing_pipeline.rb' - - 'test/shoulda_macros/sanger_macros/resource_test.rb' From 5c2d721d0b3e68c0fd92a577e5a3fae3ca33d5e9 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Fri, 6 Feb 2026 15:48:34 +0000 Subject: [PATCH 52/92] docs: clarify links --- docs/accessioning/accessioning.mermaid | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index 7b692f8f88..6ab1de4011 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -200,7 +200,7 @@ flowchart LR MD_SS_Submission_AccessionBehaviour -..-> | exception email | User_Developers %% Manifest upload - User_SSR --> RES_Manifest -..-> UI_SS_Manifest_Upload --> MD_SS_SampleManifest_Uploader + User_SSR --> RES_Manifest --> UI_SS_Manifest_Upload --> MD_SS_SampleManifest_Uploader MD_SS_SampleManifest_Uploader --> FN_SS_SampleManifestExcel_Upload_Base_trigger_accessioning ---> FN_SS_Accession_accession_sample %% Sample generate accession number @@ -244,6 +244,7 @@ flowchart LR FN_SS_AccessionService_submit_study_for_user ==> FN_SS_AccessionService_submit FN_SS_AccessionService_submit ==> External_EBI_Studies External_EBI_Studies ==> FN_SS_Accessionable_Study_update_accession_number + FN_SS_Accessionable_Study_update_accession_number --> MD_SS_Study FN_SS_Studies_rescue_accession_errors --> | flash message | VW_SS_Study VW_SS_Study --> User_SS_Users From aca7e1e18750f863378561c8676654c5df6f9618 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Fri, 6 Feb 2026 15:48:45 +0000 Subject: [PATCH 53/92] docs: reorder arrows --- docs/accessioning/accessioning.mermaid | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index 6ab1de4011..6384bfe342 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -187,7 +187,7 @@ flowchart LR %% Edge connections between nodes Providers ~~~ Application_Limber ~~~ Consumers - Providers ~~~ Application_Sequencescape ~~~ Consumers + Providers ~~~ RES_Manifest ~~~ Application_Sequencescape ~~~ Consumers External_EBI_Studies ~~~ External_EBI_Samples %% External_EBI_Samples ~~~ External_EBI_Studies @@ -199,6 +199,11 @@ flowchart LR MD_SS_Submission_AccessionBehaviour --> CF_SS_disable_accession_check MD_SS_Submission_AccessionBehaviour -..-> | exception email | User_Developers + %% Sample save and accession + User_SSR --> UI_SS_Sample_SAA + UI_SS_Sample_SAA --> FN_SS_Sample_accession_and_handle_validation_errors + FN_SS_Sample_accession_and_handle_validation_errors --> FN_SS_Accession_accession_sample + %% Manifest upload User_SSR --> RES_Manifest --> UI_SS_Manifest_Upload --> MD_SS_SampleManifest_Uploader MD_SS_SampleManifest_Uploader --> FN_SS_SampleManifestExcel_Upload_Base_trigger_accessioning ---> FN_SS_Accession_accession_sample @@ -219,12 +224,7 @@ flowchart LR MD_SS_AccessionStatuses -.- VW_SS_Sample FN_SS_Samples_accession --> | 2. | VW_SS_Sample VW_SS_Sample --> User_SS_Users - - %% Sample save and accession - User_SSR --> UI_SS_Sample_SAA - UI_SS_Sample_SAA --> FN_SS_Sample_accession_and_handle_validation_errors - FN_SS_Sample_accession_and_handle_validation_errors --> FN_SS_Accession_accession_sample - + %% Study accession all samples User_Neil --> UI_SS_Study_AAS User_Study_Owners --> UI_SS_Study_AAS From 033ea57e4f5a253f7f9e05bea4ed73a8bbcfa25c Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Fri, 6 Feb 2026 16:21:23 +0000 Subject: [PATCH 54/92] docs: add accessioning_enabled? flag --- docs/accessioning/accessioning.mermaid | 48 ++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index 6384bfe342..87e08fe1d7 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -22,9 +22,11 @@ flowchart LR L_Resource(fa:fa-file Resource) L_Library(fa:fa-book Library) L_Async(fa:fa-clock Asynchronous Process) + L_LinkSource[/Link Source\] + L_LinkSink[\Link Sink/] - L_User ~~~ L_External ~~~ L_API ~~~ L_Interface ~~~ L_Resource ~~~ L_Async - L_Config ~~~ L_Controller ~~~ L_Model ~~~ L_View ~~~ L_Function ~~~ L_Library + L_User ~~~ L_External ~~~ L_API ~~~ L_Interface ~~~ L_Resource ~~~ L_Async ~~~ L_LinkSource + L_Config ~~~ L_Controller ~~~ L_Model ~~~ L_View ~~~ L_Function ~~~ L_Library ~~~ L_LinkSink InvisibleNodeA[ ] -..-> | Data flow | InvisibleNodeB[ ] -- Function calls --> InvisibleNodeC[ ] == Accessioning function calls ==> InvisibleNodeD[ ] end @@ -84,9 +86,19 @@ flowchart LR FN_SS_Studies_accession(fa:fa-caret-right accession) FN_SS_Studies_rescue_accession_errors(fa:fa-caret-right rescue_accession_errors) FN_SS_Accession_accession_sample(fa:fa-caret-right accession_sample) + FN_SS_Accession_SampleAccessioning_perform(fa:fa-caret-right Accession::SampleAccessioning#perform) FN_SS_Accession_Sample_validate(fa:fa-caret-right validate) FN_SS_Accession_Sample_update_accession_number(fa:fa-caret-right update_accession_number) FN_SS_Study_validate_study_for_accessioning(fa:fa-caret-right validate_study_for_accessioning!) + FN_SS_AccessionHelper_accessioning_enabled(fa:fa-caret-right AccessionHelper#accessioning_enabled?) + + %% Links + LK_SS_accessioning_enabled[/accessioning_enabled?\] + LK_SS_accessioning_enabled_AAS[\accessioning_enabled?/] + LK_SS_accessioning_enabled_Studies_accession[\accessioning_enabled?/] + LK_SS_accessioning_enabled_Samples_accession[\accessioning_enabled?/] + LK_SS_accessioning_enabled_trigger_accessioning[\accessioning_enabled?/] + LK_SS_accessioning_enabled_Accession_perform[\accessioning_enabled?/] %% Libraries LB_SS_AccessioningV1Client(fa:fa-book AccessioningV1Client) @@ -97,7 +109,7 @@ flowchart LR DJ_SS_SampleAccessioningJob(fa:fa-clock SampleAccessioningJob) %% Config - CF_SS_accession_samples(fa:fa-screwdriver-wrench accession_samples) + CF_SS_y25_706_enable_accessioning(fa:fa-screwdriver-wrench y25_706_enable_accessioning) CF_SS_disable_accession_check(fa:fa-screwdriver-wrench disable_accession_check) %% Resources @@ -119,7 +131,16 @@ flowchart LR FN_SS_SampleManifestExcel_Upload_Base_trigger_accessioning MD_SS_Order MD_SS_Submission_AccessionBehaviour - CF_SS_accession_samples + + CF_SS_y25_706_enable_accessioning + FN_SS_AccessionHelper_accessioning_enabled + LK_SS_accessioning_enabled + LK_SS_accessioning_enabled_AAS + LK_SS_accessioning_enabled_Studies_accession + LK_SS_accessioning_enabled_Samples_accession + LK_SS_accessioning_enabled_trigger_accessioning + LK_SS_accessioning_enabled_Accession_perform + CF_SS_disable_accession_check DJ_SS_SampleAccessioningJob @@ -159,6 +180,7 @@ flowchart LR end subgraph LB_SS_Accession[fa:fa-book Accession - samples only] FN_SS_Accession_accession_sample + FN_SS_Accession_SampleAccessioning_perform CP_SS_AccessionSubmission subgraph MD_SS_Accession_Sample[fa:fa-square-caret-down Accession::Sample Model] FN_SS_Accession_Sample_validate @@ -188,6 +210,7 @@ flowchart LR Providers ~~~ Application_Limber ~~~ Consumers Providers ~~~ RES_Manifest ~~~ Application_Sequencescape ~~~ Consumers + Providers ~~~ CF_SS_y25_706_enable_accessioning External_EBI_Studies ~~~ External_EBI_Samples %% External_EBI_Samples ~~~ External_EBI_Studies @@ -199,21 +222,28 @@ flowchart LR MD_SS_Submission_AccessionBehaviour --> CF_SS_disable_accession_check MD_SS_Submission_AccessionBehaviour -..-> | exception email | User_Developers + %% Config + CF_SS_y25_706_enable_accessioning --> FN_SS_AccessionHelper_accessioning_enabled --> LK_SS_accessioning_enabled + %% Sample save and accession User_SSR --> UI_SS_Sample_SAA UI_SS_Sample_SAA --> FN_SS_Sample_accession_and_handle_validation_errors FN_SS_Sample_accession_and_handle_validation_errors --> FN_SS_Accession_accession_sample %% Manifest upload - User_SSR --> RES_Manifest --> UI_SS_Manifest_Upload --> MD_SS_SampleManifest_Uploader + User_SSR --> RES_Manifest --> UI_SS_Manifest_Upload --> MD_SS_SampleManifest_Uploader + LK_SS_accessioning_enabled_trigger_accessioning -.-> FN_SS_SampleManifestExcel_Upload_Base_trigger_accessioning MD_SS_SampleManifest_Uploader --> FN_SS_SampleManifestExcel_Upload_Base_trigger_accessioning ---> FN_SS_Accession_accession_sample %% Sample generate accession number User_SSR --> UI_SS_Sample_GAN User_Neil --> UI_SS_Sample_GAN + LK_SS_accessioning_enabled_Samples_accession -.-> FN_SS_Samples_accession UI_SS_Sample_GAN --> FN_SS_Samples_accession FN_SS_Samples_accession <--> | 1. | FN_SS_Accession_accession_sample - FN_SS_Accession_accession_sample ==> DJ_SS_SampleAccessioningJob + LK_SS_accessioning_enabled_Accession_perform -.-> FN_SS_Accession_SampleAccessioning_perform + FN_SS_Accession_accession_sample ==> FN_SS_Accession_SampleAccessioning_perform + FN_SS_Accession_SampleAccessioning_perform ==> DJ_SS_SampleAccessioningJob DJ_SS_SampleAccessioningJob <--> | 1. | FN_SS_Accession_Sample_validate DJ_SS_SampleAccessioningJob ==> | 2. | CP_SS_AccessionSubmission CP_SS_AccessionSubmission ==> LB_SS_AccessioningV1Client @@ -228,6 +258,7 @@ flowchart LR %% Study accession all samples User_Neil --> UI_SS_Study_AAS User_Study_Owners --> UI_SS_Study_AAS + LK_SS_accessioning_enabled_AAS -.-> FN_SS_Studies_accession_all_samples UI_SS_Study_AAS --> FN_SS_Studies_accession_all_samples FN_SS_Studies_accession_all_samples <--> | 1. | FN_SS_Study_accession_all_samples FN_SS_Study_accession_all_samples --> FN_SS_Accession_accession_sample @@ -236,6 +267,7 @@ flowchart LR %% Study generate accession number User_Study_Owners --> UI_SS_Study_GAN UI_SS_Study_GAN --> FN_SS_Studies_accession + LK_SS_accessioning_enabled_Studies_accession -.-> FN_SS_Studies_accession FN_SS_Studies_accession <--> | 1. | FN_SS_Study_validate_study_for_accessioning FN_SS_Studies_accession <--> | 2. | FN_SS_AccessionService_select_for_study FN_SS_Studies_accession <--> | 3. | FN_SS_AccessionService_submit_study_for_user @@ -267,6 +299,7 @@ flowchart LR classDef legendTransparent fill:transparent,stroke:#333,stroke-width:1px; classDef application fill:#90dbf4; classDef configuration fill:#fbf8cc; + classDef link fill:#fde4cf; classDef railsModel fill:#98f5e1; classDef railsController fill:#b9fbc0; classDef users fill:#f1c0e8; @@ -275,9 +308,10 @@ flowchart LR class InvisibleNodeA,InvisibleNodeB,InvisibleNodeC,InvisibleNodeD invisible; class Legend legendTransparent; class Application_Sequencescape,Application_Limber application; - class L_Config,CF_SS_disable_accession_check,CF_SS_accession_samples configuration; + class L_Config,CF_SS_disable_accession_check,CF_SS_y25_706_enable_accessioning configuration; class SS_API,DelayedJob,Samples,Studies,Submissions SequencescapeSection; class L_Model,MD_SS_Study,MD_SS_AccessionService,MD_SS_Sample,MD_SS_SampleManifest_Uploader,MD_SS_Order,MD_SS_Submission_AccessionBehaviour railsModel; class L_Controller,CT_SS_Samples,CT_SS_Studies railsController; class L_User,User_SeqOps,User_Study_Owners,User_Neil,User_SSR,User_LB_Users,User_SS_Users,User_Developers users; class L_Interface,UI_LB_Charge_and_Pass,UI_SS_Sample_GAN,UI_SS_Sample_SAA,UI_SS_Study_GAN,UI_SS_Study_AAS,UI_SS_Manifest_Upload userInterface; + class L_LinkSource,L_LinkSink,LK_SS_accessioning_enabled,LK_SS_accessioning_enabled_AAS,LK_SS_accessioning_enabled_Studies_accession,LK_SS_accessioning_enabled_Samples_accession,LK_SS_accessioning_enabled_trigger_accessioning,LK_SS_accessioning_enabled_Accession_perform link; From e05a7492d2c51528b5405d74663f30509f63250d Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Fri, 6 Feb 2026 16:28:15 +0000 Subject: [PATCH 55/92] docs: remove references to Limber charge and pass --- docs/accessioning/accessioning.mermaid | 28 -------------------------- 1 file changed, 28 deletions(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index 87e08fe1d7..85322900e5 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -36,12 +36,9 @@ flowchart LR %% Nodes %% Users - User_SeqOps(fa:fa-user SeqOps) User_Study_Owners(fa:fa-user Study Owners) User_Neil(fa:fa-user PSD Support) User_SSR(fa:fa-user SSRs) - User_LB_Users(fa:fa-user LB Users) - User_Developers(fa:fa-user Developers) User_SS_Users(fa:fa-user SS Users) %% External Systems @@ -49,7 +46,6 @@ flowchart LR External_EBI_Samples(fa:fa-arrow-up ENA/EGA Samples) %% User Interfaces - UI_LB_Charge_and_Pass(fa:fa-computer-mouse Charge and Pass) UI_SS_Sample_GAN(fa:fa-computer-mouse Generate Accession Number) UI_SS_Sample_SAA(fa:fa-computer-mouse Save and Accession) UI_SS_Study_GAN(fa:fa-computer-mouse Generate Accession Number) @@ -57,9 +53,7 @@ flowchart LR UI_SS_Manifest_Upload(fa:fa-computer-mouse Manifest Upload) %% Models - MD_SS_Order(fa:fa-square-caret-down Order) %% MD_SS_AccessionService(fa:fa-square-caret-down AccessionService) - MD_SS_Submission_AccessionBehaviour(fa:fa-square-caret-down Submission::AccessionBehaviour) %% MD_SS_Sample(fa:fa-square-caret-down Sample) %% MD_SS_Study(fa:fa-square-caret-down Study) MD_SS_SampleManifest_Uploader(fa:fa-square-caret-down SampleManifest::Uploader) @@ -104,13 +98,11 @@ flowchart LR LB_SS_AccessioningV1Client(fa:fa-book AccessioningV1Client) %% Other Components - API_SS_OrderResource(fa:fa-arrow-right-to-bracket Order Resource) CP_SS_AccessionSubmission(fa:fa-book Accession::Submission) DJ_SS_SampleAccessioningJob(fa:fa-clock SampleAccessioningJob) %% Config CF_SS_y25_706_enable_accessioning(fa:fa-screwdriver-wrench y25_706_enable_accessioning) - CF_SS_disable_accession_check(fa:fa-screwdriver-wrench disable_accession_check) %% Resources RES_Manifest(fa:fa-file Manifest) @@ -122,15 +114,10 @@ flowchart LR User_Neil User_SSR end - subgraph Application_Limber[Limber Application] - UI_LB_Charge_and_Pass - end subgraph Application_Sequencescape[Sequencescape Application] UI_SS_Manifest_Upload MD_SS_SampleManifest_Uploader FN_SS_SampleManifestExcel_Upload_Base_trigger_accessioning - MD_SS_Order - MD_SS_Submission_AccessionBehaviour CF_SS_y25_706_enable_accessioning FN_SS_AccessionHelper_accessioning_enabled @@ -141,7 +128,6 @@ flowchart LR LK_SS_accessioning_enabled_trigger_accessioning LK_SS_accessioning_enabled_Accession_perform - CF_SS_disable_accession_check DJ_SS_SampleAccessioningJob LB_SS_AccessioningV1Client @@ -163,9 +149,6 @@ flowchart LR subgraph Sample Manifests UI_SS_Manifest_Upload end - subgraph SS_API["SS API"] - API_SS_OrderResource - end subgraph CT_SS_Studies[fa:fa-arrows-spin Studies Controller] FN_SS_Studies_accession_all_samples FN_SS_Studies_accession @@ -201,27 +184,16 @@ flowchart LR External_EBI_Samples end subgraph Consumers - User_LB_Users User_SS_Users - User_Developers end %% Edge connections between nodes - Providers ~~~ Application_Limber ~~~ Consumers - Providers ~~~ RES_Manifest ~~~ Application_Sequencescape ~~~ Consumers Providers ~~~ CF_SS_y25_706_enable_accessioning External_EBI_Studies ~~~ External_EBI_Samples %% External_EBI_Samples ~~~ External_EBI_Studies - %% Limber-related - User_SeqOps --> UI_LB_Charge_and_Pass -...-> User_LB_Users - UI_LB_Charge_and_Pass --> API_SS_OrderResource --> MD_SS_Order - MD_SS_Order --> MD_SS_Submission_AccessionBehaviour - MD_SS_Submission_AccessionBehaviour --> CF_SS_disable_accession_check - MD_SS_Submission_AccessionBehaviour -..-> | exception email | User_Developers - %% Config CF_SS_y25_706_enable_accessioning --> FN_SS_AccessionHelper_accessioning_enabled --> LK_SS_accessioning_enabled From c714d0189afd28c8b8a16a86eec6c5a9bbd35c7c Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Fri, 6 Feb 2026 16:30:02 +0000 Subject: [PATCH 56/92] docs: rename nodes to not include SS prefix --- docs/accessioning/accessioning.mermaid | 281 ++++++++++++------------- 1 file changed, 140 insertions(+), 141 deletions(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index 85322900e5..d9e2499e1d 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -39,144 +39,143 @@ flowchart LR User_Study_Owners(fa:fa-user Study Owners) User_Neil(fa:fa-user PSD Support) User_SSR(fa:fa-user SSRs) - User_SS_Users(fa:fa-user SS Users) + User_Users(fa:fa-user SS Users) %% External Systems External_EBI_Studies(fa:fa-arrow-up ENA/EGA Studies) External_EBI_Samples(fa:fa-arrow-up ENA/EGA Samples) %% User Interfaces - UI_SS_Sample_GAN(fa:fa-computer-mouse Generate Accession Number) - UI_SS_Sample_SAA(fa:fa-computer-mouse Save and Accession) - UI_SS_Study_GAN(fa:fa-computer-mouse Generate Accession Number) - UI_SS_Study_AAS(fa:fa-computer-mouse Accession all Samples) - UI_SS_Manifest_Upload(fa:fa-computer-mouse Manifest Upload) + UI_Sample_GAN(fa:fa-computer-mouse Generate Accession Number) + UI_Sample_SAA(fa:fa-computer-mouse Save and Accession) + UI_Study_GAN(fa:fa-computer-mouse Generate Accession Number) + UI_Study_AAS(fa:fa-computer-mouse Accession all Samples) + UI_Manifest_Upload(fa:fa-computer-mouse Manifest Upload) %% Models - %% MD_SS_AccessionService(fa:fa-square-caret-down AccessionService) - %% MD_SS_Sample(fa:fa-square-caret-down Sample) - %% MD_SS_Study(fa:fa-square-caret-down Study) - MD_SS_SampleManifest_Uploader(fa:fa-square-caret-down SampleManifest::Uploader) - MD_SS_AccessionStatuses(fa:fa-square-caret-down AccessionStatuses) + %% MD_AccessionService(fa:fa-square-caret-down AccessionService) + %% MD_Sample(fa:fa-square-caret-down Sample) + %% MD_Study(fa:fa-square-caret-down Study) + MD_SampleManifest_Uploader(fa:fa-square-caret-down SampleManifest::Uploader) + MD_AccessionStatuses(fa:fa-square-caret-down AccessionStatuses) %% Controllers - %% CT_SS_Samples(fa:fa-arrows-spin Samples Controller) - %% CT_SS_Studies(fa:fa-arrows-spin Studies Controller) + %% CT_Samples(fa:fa-arrows-spin Samples Controller) + %% CT_Studies(fa:fa-arrows-spin Studies Controller) %% Views - VW_SS_Sample(fa:fa-eye Sample View) - VW_SS_Study(fa:fa-eye Study View) + VW_Sample(fa:fa-eye Sample View) + VW_Study(fa:fa-eye Study View) %% Functions - FN_SS_SampleManifestExcel_Upload_Base_trigger_accessioning(fa:fa-caret-right SampleManifestExcel::Upload::Base#trigger_accessioning) - FN_SS_AccessionService_select_for_study(fa:fa-caret-right select_for_study) - FN_SS_AccessionService_submit_study_for_user(fa:fa-caret-right submit_study_for_user) - FN_SS_AccessionService_submit(fa:fa-caret-right submit) - FN_SS_Accessionable_Study_update_accession_number(fa:fa-caret-right Accessionable::Study#update_accession_number!) - FN_SS_Sample_accession_and_handle_validation_errors(fa:fa-caret-right accession_and_handle_validation_errors) - FN_SS_Samples_accession(fa:fa-caret-right accession) - FN_SS_Study_accession_all_samples(fa:fa-caret-right accession_all_samples) - FN_SS_Studies_accession_all_samples(fa:fa-caret-right accession_all_samples) - FN_SS_Studies_accession(fa:fa-caret-right accession) - FN_SS_Studies_rescue_accession_errors(fa:fa-caret-right rescue_accession_errors) - FN_SS_Accession_accession_sample(fa:fa-caret-right accession_sample) - FN_SS_Accession_SampleAccessioning_perform(fa:fa-caret-right Accession::SampleAccessioning#perform) - FN_SS_Accession_Sample_validate(fa:fa-caret-right validate) - FN_SS_Accession_Sample_update_accession_number(fa:fa-caret-right update_accession_number) - FN_SS_Study_validate_study_for_accessioning(fa:fa-caret-right validate_study_for_accessioning!) - FN_SS_AccessionHelper_accessioning_enabled(fa:fa-caret-right AccessionHelper#accessioning_enabled?) + FN_SampleManifestExcel_Upload_Base_trigger_accessioning(fa:fa-caret-right SampleManifestExcel::Upload::Base#trigger_accessioning) + FN_AccessionService_select_for_study(fa:fa-caret-right select_for_study) + FN_AccessionService_submit_study_for_user(fa:fa-caret-right submit_study_for_user) + FN_AccessionService_submit(fa:fa-caret-right submit) + FN_Accessionable_Study_update_accession_number(fa:fa-caret-right Accessionable::Study#update_accession_number!) + FN_Sample_accession_and_handle_validation_errors(fa:fa-caret-right accession_and_handle_validation_errors) + FN_Samples_accession(fa:fa-caret-right accession) + FN_Study_accession_all_samples(fa:fa-caret-right accession_all_samples) + FN_Studies_accession_all_samples(fa:fa-caret-right accession_all_samples) + FN_Studies_accession(fa:fa-caret-right accession) + FN_Studies_rescue_accession_errors(fa:fa-caret-right rescue_accession_errors) + FN_Accession_accession_sample(fa:fa-caret-right accession_sample) + FN_Accession_SampleAccessioning_perform(fa:fa-caret-right Accession::SampleAccessioning#perform) + FN_Accession_Sample_validate(fa:fa-caret-right validate) + FN_Accession_Sample_update_accession_number(fa:fa-caret-right update_accession_number) + FN_Study_validate_study_for_accessioning(fa:fa-caret-right validate_study_for_accessioning!) + FN_AccessionHelper_accessioning_enabled(fa:fa-caret-right AccessionHelper#accessioning_enabled?) %% Links - LK_SS_accessioning_enabled[/accessioning_enabled?\] - LK_SS_accessioning_enabled_AAS[\accessioning_enabled?/] - LK_SS_accessioning_enabled_Studies_accession[\accessioning_enabled?/] - LK_SS_accessioning_enabled_Samples_accession[\accessioning_enabled?/] - LK_SS_accessioning_enabled_trigger_accessioning[\accessioning_enabled?/] - LK_SS_accessioning_enabled_Accession_perform[\accessioning_enabled?/] + LK_accessioning_enabled[/accessioning_enabled?\] + LK_accessioning_enabled_AAS[\accessioning_enabled?/] + LK_accessioning_enabled_Studies_accession[\accessioning_enabled?/] + LK_accessioning_enabled_Samples_accession[\accessioning_enabled?/] + LK_accessioning_enabled_trigger_accessioning[\accessioning_enabled?/] + LK_accessioning_enabled_Accession_perform[\accessioning_enabled?/] %% Libraries - LB_SS_AccessioningV1Client(fa:fa-book AccessioningV1Client) + LB_AccessioningV1Client(fa:fa-book AccessioningV1Client) %% Other Components - CP_SS_AccessionSubmission(fa:fa-book Accession::Submission) - DJ_SS_SampleAccessioningJob(fa:fa-clock SampleAccessioningJob) + CP_AccessionSubmission(fa:fa-book Accession::Submission) + DJ_SampleAccessioningJob(fa:fa-clock SampleAccessioningJob) %% Config - CF_SS_y25_706_enable_accessioning(fa:fa-screwdriver-wrench y25_706_enable_accessioning) + CF_y25_706_enable_accessioning(fa:fa-screwdriver-wrench y25_706_enable_accessioning) %% Resources RES_Manifest(fa:fa-file Manifest) %% Groupings of nodes subgraph Providers - User_SeqOps User_Study_Owners User_Neil User_SSR end subgraph Application_Sequencescape[Sequencescape Application] - UI_SS_Manifest_Upload - MD_SS_SampleManifest_Uploader - FN_SS_SampleManifestExcel_Upload_Base_trigger_accessioning + UI_Manifest_Upload + MD_SampleManifest_Uploader + FN_SampleManifestExcel_Upload_Base_trigger_accessioning - CF_SS_y25_706_enable_accessioning - FN_SS_AccessionHelper_accessioning_enabled - LK_SS_accessioning_enabled - LK_SS_accessioning_enabled_AAS - LK_SS_accessioning_enabled_Studies_accession - LK_SS_accessioning_enabled_Samples_accession - LK_SS_accessioning_enabled_trigger_accessioning - LK_SS_accessioning_enabled_Accession_perform + CF_y25_706_enable_accessioning + FN_AccessionHelper_accessioning_enabled + LK_accessioning_enabled + LK_accessioning_enabled_AAS + LK_accessioning_enabled_Studies_accession + LK_accessioning_enabled_Samples_accession + LK_accessioning_enabled_trigger_accessioning + LK_accessioning_enabled_Accession_perform - DJ_SS_SampleAccessioningJob - LB_SS_AccessioningV1Client - MD_SS_AccessionStatuses + DJ_SampleAccessioningJob + LB_AccessioningV1Client + MD_AccessionStatuses - FN_SS_Accessionable_Study_update_accession_number + FN_Accessionable_Study_update_accession_number - VW_SS_Sample - VW_SS_Study + VW_Sample + VW_Study subgraph Samples - UI_SS_Sample_SAA - UI_SS_Sample_GAN + UI_Sample_SAA + UI_Sample_GAN end subgraph Studies - UI_SS_Study_GAN - UI_SS_Study_AAS + UI_Study_GAN + UI_Study_AAS end subgraph Sample Manifests - UI_SS_Manifest_Upload + UI_Manifest_Upload end - subgraph CT_SS_Studies[fa:fa-arrows-spin Studies Controller] - FN_SS_Studies_accession_all_samples - FN_SS_Studies_accession - FN_SS_Studies_rescue_accession_errors + subgraph CT_Studies[fa:fa-arrows-spin Studies Controller] + FN_Studies_accession_all_samples + FN_Studies_accession + FN_Studies_rescue_accession_errors end - subgraph MD_SS_Study[fa:fa-square-caret-down Study Model] - FN_SS_Study_accession_all_samples - FN_SS_Study_validate_study_for_accessioning + subgraph MD_Study[fa:fa-square-caret-down Study Model] + FN_Study_accession_all_samples + FN_Study_validate_study_for_accessioning end - subgraph CT_SS_Samples[fa:fa-arrows-spin Samples Controller] - FN_SS_Samples_accession + subgraph CT_Samples[fa:fa-arrows-spin Samples Controller] + FN_Samples_accession end - subgraph LB_SS_Accession[fa:fa-book Accession - samples only] - FN_SS_Accession_accession_sample - FN_SS_Accession_SampleAccessioning_perform - CP_SS_AccessionSubmission - subgraph MD_SS_Accession_Sample[fa:fa-square-caret-down Accession::Sample Model] - FN_SS_Accession_Sample_validate - FN_SS_Accession_Sample_update_accession_number + subgraph LB_Accession[fa:fa-book Accession - samples only] + FN_Accession_accession_sample + FN_Accession_SampleAccessioning_perform + CP_AccessionSubmission + subgraph MD_Accession_Sample[fa:fa-square-caret-down Accession::Sample Model] + FN_Accession_Sample_validate + FN_Accession_Sample_update_accession_number end end - subgraph MD_SS_AccessionService[fa:fa-square-caret-down AccessionService Model - studies only] - FN_SS_AccessionService_select_for_study - FN_SS_AccessionService_submit_study_for_user - FN_SS_AccessionService_submit + subgraph MD_AccessionService[fa:fa-square-caret-down AccessionService Model - studies only] + FN_AccessionService_select_for_study + FN_AccessionService_submit_study_for_user + FN_AccessionService_submit end - subgraph MD_SS_Sample[fa:fa-square-caret-down Sample Model] - FN_SS_Sample_accession_and_handle_validation_errors + subgraph MD_Sample[fa:fa-square-caret-down Sample Model] + FN_Sample_accession_and_handle_validation_errors end end subgraph External_EBI[fa:fa-globe EBI ENA Webin REST V1] @@ -184,73 +183,73 @@ flowchart LR External_EBI_Samples end subgraph Consumers - User_SS_Users + User_Users end %% Edge connections between nodes - Providers ~~~ CF_SS_y25_706_enable_accessioning + Providers ~~~ CF_y25_706_enable_accessioning External_EBI_Studies ~~~ External_EBI_Samples %% External_EBI_Samples ~~~ External_EBI_Studies %% Config - CF_SS_y25_706_enable_accessioning --> FN_SS_AccessionHelper_accessioning_enabled --> LK_SS_accessioning_enabled + CF_y25_706_enable_accessioning --> FN_AccessionHelper_accessioning_enabled --> LK_accessioning_enabled %% Sample save and accession - User_SSR --> UI_SS_Sample_SAA - UI_SS_Sample_SAA --> FN_SS_Sample_accession_and_handle_validation_errors - FN_SS_Sample_accession_and_handle_validation_errors --> FN_SS_Accession_accession_sample + User_SSR --> UI_Sample_SAA + UI_Sample_SAA --> FN_Sample_accession_and_handle_validation_errors + FN_Sample_accession_and_handle_validation_errors --> FN_Accession_accession_sample %% Manifest upload - User_SSR --> RES_Manifest --> UI_SS_Manifest_Upload --> MD_SS_SampleManifest_Uploader - LK_SS_accessioning_enabled_trigger_accessioning -.-> FN_SS_SampleManifestExcel_Upload_Base_trigger_accessioning - MD_SS_SampleManifest_Uploader --> FN_SS_SampleManifestExcel_Upload_Base_trigger_accessioning ---> FN_SS_Accession_accession_sample + User_SSR --> RES_Manifest --> UI_Manifest_Upload --> MD_SampleManifest_Uploader + LK_accessioning_enabled_trigger_accessioning -.-> FN_SampleManifestExcel_Upload_Base_trigger_accessioning + MD_SampleManifest_Uploader --> FN_SampleManifestExcel_Upload_Base_trigger_accessioning ---> FN_Accession_accession_sample %% Sample generate accession number - User_SSR --> UI_SS_Sample_GAN - User_Neil --> UI_SS_Sample_GAN - LK_SS_accessioning_enabled_Samples_accession -.-> FN_SS_Samples_accession - UI_SS_Sample_GAN --> FN_SS_Samples_accession - FN_SS_Samples_accession <--> | 1. | FN_SS_Accession_accession_sample - LK_SS_accessioning_enabled_Accession_perform -.-> FN_SS_Accession_SampleAccessioning_perform - FN_SS_Accession_accession_sample ==> FN_SS_Accession_SampleAccessioning_perform - FN_SS_Accession_SampleAccessioning_perform ==> DJ_SS_SampleAccessioningJob - DJ_SS_SampleAccessioningJob <--> | 1. | FN_SS_Accession_Sample_validate - DJ_SS_SampleAccessioningJob ==> | 2. | CP_SS_AccessionSubmission - CP_SS_AccessionSubmission ==> LB_SS_AccessioningV1Client - LB_SS_AccessioningV1Client ==> External_EBI_Samples - External_EBI_Samples ==> | Success: accession number | FN_SS_Accession_Sample_update_accession_number - External_EBI_Samples ==> | Failure: reason | MD_SS_AccessionStatuses - FN_SS_Accession_Sample_update_accession_number -.- VW_SS_Sample - MD_SS_AccessionStatuses -.- VW_SS_Sample - FN_SS_Samples_accession --> | 2. | VW_SS_Sample - VW_SS_Sample --> User_SS_Users + User_SSR --> UI_Sample_GAN + User_Neil --> UI_Sample_GAN + LK_accessioning_enabled_Samples_accession -.-> FN_Samples_accession + UI_Sample_GAN --> FN_Samples_accession + FN_Samples_accession <--> | 1. | FN_Accession_accession_sample + LK_accessioning_enabled_Accession_perform -.-> FN_Accession_SampleAccessioning_perform + FN_Accession_accession_sample ==> FN_Accession_SampleAccessioning_perform + FN_Accession_SampleAccessioning_perform ==> DJ_SampleAccessioningJob + DJ_SampleAccessioningJob <--> | 1. | FN_Accession_Sample_validate + DJ_SampleAccessioningJob ==> | 2. | CP_AccessionSubmission + CP_AccessionSubmission ==> LB_AccessioningV1Client + LB_AccessioningV1Client ==> External_EBI_Samples + External_EBI_Samples ==> | Success: accession number | FN_Accession_Sample_update_accession_number + External_EBI_Samples ==> | Failure: reason | MD_AccessionStatuses + FN_Accession_Sample_update_accession_number -.- VW_Sample + MD_AccessionStatuses -.- VW_Sample + FN_Samples_accession --> | 2. | VW_Sample + VW_Sample --> User_Users %% Study accession all samples - User_Neil --> UI_SS_Study_AAS - User_Study_Owners --> UI_SS_Study_AAS - LK_SS_accessioning_enabled_AAS -.-> FN_SS_Studies_accession_all_samples - UI_SS_Study_AAS --> FN_SS_Studies_accession_all_samples - FN_SS_Studies_accession_all_samples <--> | 1. | FN_SS_Study_accession_all_samples - FN_SS_Study_accession_all_samples --> FN_SS_Accession_accession_sample - FN_SS_Studies_accession_all_samples --> | 2. | VW_SS_Study + User_Neil --> UI_Study_AAS + User_Study_Owners --> UI_Study_AAS + LK_accessioning_enabled_AAS -.-> FN_Studies_accession_all_samples + UI_Study_AAS --> FN_Studies_accession_all_samples + FN_Studies_accession_all_samples <--> | 1. | FN_Study_accession_all_samples + FN_Study_accession_all_samples --> FN_Accession_accession_sample + FN_Studies_accession_all_samples --> | 2. | VW_Study %% Study generate accession number - User_Study_Owners --> UI_SS_Study_GAN - UI_SS_Study_GAN --> FN_SS_Studies_accession - LK_SS_accessioning_enabled_Studies_accession -.-> FN_SS_Studies_accession - FN_SS_Studies_accession <--> | 1. | FN_SS_Study_validate_study_for_accessioning - FN_SS_Studies_accession <--> | 2. | FN_SS_AccessionService_select_for_study - FN_SS_Studies_accession <--> | 3. | FN_SS_AccessionService_submit_study_for_user - FN_SS_Studies_accession --> | 4. IF success -- flash message | VW_SS_Study - FN_SS_Studies_accession --> | 4. IF error | FN_SS_Studies_rescue_accession_errors - FN_SS_AccessionService_submit_study_for_user ==> FN_SS_AccessionService_submit - FN_SS_AccessionService_submit ==> External_EBI_Studies - External_EBI_Studies ==> FN_SS_Accessionable_Study_update_accession_number - FN_SS_Accessionable_Study_update_accession_number --> MD_SS_Study - FN_SS_Studies_rescue_accession_errors --> | flash message | VW_SS_Study - VW_SS_Study --> User_SS_Users + User_Study_Owners --> UI_Study_GAN + UI_Study_GAN --> FN_Studies_accession + LK_accessioning_enabled_Studies_accession -.-> FN_Studies_accession + FN_Studies_accession <--> | 1. | FN_Study_validate_study_for_accessioning + FN_Studies_accession <--> | 2. | FN_AccessionService_select_for_study + FN_Studies_accession <--> | 3. | FN_AccessionService_submit_study_for_user + FN_Studies_accession --> | 4. IF success -- flash message | VW_Study + FN_Studies_accession --> | 4. IF error | FN_Studies_rescue_accession_errors + FN_AccessionService_submit_study_for_user ==> FN_AccessionService_submit + FN_AccessionService_submit ==> External_EBI_Studies + External_EBI_Studies ==> FN_Accessionable_Study_update_accession_number + FN_Accessionable_Study_update_accession_number --> MD_Study + FN_Studies_rescue_accession_errors --> | flash message | VW_Study + VW_Study --> User_Users %% Subgraph styling @@ -280,10 +279,10 @@ flowchart LR class InvisibleNodeA,InvisibleNodeB,InvisibleNodeC,InvisibleNodeD invisible; class Legend legendTransparent; class Application_Sequencescape,Application_Limber application; - class L_Config,CF_SS_disable_accession_check,CF_SS_y25_706_enable_accessioning configuration; + class L_Config,CF_disable_accession_check,CF_y25_706_enable_accessioning configuration; class SS_API,DelayedJob,Samples,Studies,Submissions SequencescapeSection; - class L_Model,MD_SS_Study,MD_SS_AccessionService,MD_SS_Sample,MD_SS_SampleManifest_Uploader,MD_SS_Order,MD_SS_Submission_AccessionBehaviour railsModel; - class L_Controller,CT_SS_Samples,CT_SS_Studies railsController; - class L_User,User_SeqOps,User_Study_Owners,User_Neil,User_SSR,User_LB_Users,User_SS_Users,User_Developers users; - class L_Interface,UI_LB_Charge_and_Pass,UI_SS_Sample_GAN,UI_SS_Sample_SAA,UI_SS_Study_GAN,UI_SS_Study_AAS,UI_SS_Manifest_Upload userInterface; - class L_LinkSource,L_LinkSink,LK_SS_accessioning_enabled,LK_SS_accessioning_enabled_AAS,LK_SS_accessioning_enabled_Studies_accession,LK_SS_accessioning_enabled_Samples_accession,LK_SS_accessioning_enabled_trigger_accessioning,LK_SS_accessioning_enabled_Accession_perform link; + class L_Model,MD_Study,MD_AccessionService,MD_Sample,MD_SampleManifest_Uploader,MD_Order,MD_Submission_AccessionBehaviour railsModel; + class L_Controller,CT_Samples,CT_Studies railsController; + class L_User,User_SeqOps,User_Study_Owners,User_Neil,User_SSR,User_LB_Users,User_Users,User_Developers users; + class L_Interface,UI_LB_Charge_and_Pass,UI_Sample_GAN,UI_Sample_SAA,UI_Study_GAN,UI_Study_AAS,UI_Manifest_Upload userInterface; + class L_LinkSource,L_LinkSink,LK_accessioning_enabled,LK_accessioning_enabled_AAS,LK_accessioning_enabled_Studies_accession,LK_accessioning_enabled_Samples_accession,LK_accessioning_enabled_trigger_accessioning,LK_accessioning_enabled_Accession_perform link; From a4e63ae17fa43d139dfcc30110dcfb93e9facb5f Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Fri, 6 Feb 2026 16:32:09 +0000 Subject: [PATCH 57/92] docs: cleanup --- docs/accessioning/accessioning.mermaid | 35 +++++++++++++++----------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index d9e2499e1d..ddeb77e8df 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -12,7 +12,6 @@ flowchart LR direction TB L_User(fa:fa-user User) L_External(fa:fa-arrow-up External API) - L_API(fa:fa-arrow-right-to-bracket API) L_Interface(fa:fa-computer-mouse User Interface) L_Model(fa:fa-square-caret-down Model) L_Controller(fa:fa-arrows-spin Controller) @@ -25,7 +24,7 @@ flowchart LR L_LinkSource[/Link Source\] L_LinkSink[\Link Sink/] - L_User ~~~ L_External ~~~ L_API ~~~ L_Interface ~~~ L_Resource ~~~ L_Async ~~~ L_LinkSource + L_User ~~~ L_External ~~~ L_Interface ~~~ L_Resource ~~~ L_Async ~~~ L_LinkSource L_Config ~~~ L_Controller ~~~ L_Model ~~~ L_View ~~~ L_Function ~~~ L_Library ~~~ L_LinkSink InvisibleNodeA[ ] -..-> | Data flow | InvisibleNodeB[ ] -- Function calls --> InvisibleNodeC[ ] == Accessioning function calls ==> InvisibleNodeD[ ] @@ -68,23 +67,24 @@ flowchart LR VW_Study(fa:fa-eye Study View) %% Functions - FN_SampleManifestExcel_Upload_Base_trigger_accessioning(fa:fa-caret-right SampleManifestExcel::Upload::Base#trigger_accessioning) + FN_Accession_accession_sample(fa:fa-caret-right accession_sample) + FN_Accession_Sample_update_accession_number(fa:fa-caret-right update_accession_number) + FN_Accession_Sample_validate(fa:fa-caret-right validate) + FN_Accession_SampleAccessioning_perform(fa:fa-caret-right Accession::SampleAccessioning#perform) + FN_Accessionable_Study_update_accession_number(fa:fa-caret-right Accessionable::Study#update_accession_number!) + FN_AccessionHelper_accessioning_enabled(fa:fa-caret-right AccessionHelper#accessioning_enabled?) + FN_AccessionService_post_files(fa:fa-caret-right post_files) FN_AccessionService_select_for_study(fa:fa-caret-right select_for_study) FN_AccessionService_submit_study_for_user(fa:fa-caret-right submit_study_for_user) FN_AccessionService_submit(fa:fa-caret-right submit) - FN_Accessionable_Study_update_accession_number(fa:fa-caret-right Accessionable::Study#update_accession_number!) FN_Sample_accession_and_handle_validation_errors(fa:fa-caret-right accession_and_handle_validation_errors) + FN_SampleManifestExcel_Upload_Base_trigger_accessioning(fa:fa-caret-right SampleManifestExcel::Upload::Base#trigger_accessioning) FN_Samples_accession(fa:fa-caret-right accession) - FN_Study_accession_all_samples(fa:fa-caret-right accession_all_samples) FN_Studies_accession_all_samples(fa:fa-caret-right accession_all_samples) FN_Studies_accession(fa:fa-caret-right accession) FN_Studies_rescue_accession_errors(fa:fa-caret-right rescue_accession_errors) - FN_Accession_accession_sample(fa:fa-caret-right accession_sample) - FN_Accession_SampleAccessioning_perform(fa:fa-caret-right Accession::SampleAccessioning#perform) - FN_Accession_Sample_validate(fa:fa-caret-right validate) - FN_Accession_Sample_update_accession_number(fa:fa-caret-right update_accession_number) + FN_Study_accession_all_samples(fa:fa-caret-right accession_all_samples) FN_Study_validate_study_for_accessioning(fa:fa-caret-right validate_study_for_accessioning!) - FN_AccessionHelper_accessioning_enabled(fa:fa-caret-right AccessionHelper#accessioning_enabled?) %% Links LK_accessioning_enabled[/accessioning_enabled?\] @@ -96,6 +96,7 @@ flowchart LR %% Libraries LB_AccessioningV1Client(fa:fa-book AccessioningV1Client) + LB_RestClient(fa:fa-book RestClient) %% Other Components CP_AccessionSubmission(fa:fa-book Accession::Submission) @@ -130,6 +131,7 @@ flowchart LR DJ_SampleAccessioningJob LB_AccessioningV1Client + LB_RestClient MD_AccessionStatuses FN_Accessionable_Study_update_accession_number @@ -173,15 +175,16 @@ flowchart LR FN_AccessionService_select_for_study FN_AccessionService_submit_study_for_user FN_AccessionService_submit + FN_AccessionService_post_files end subgraph MD_Sample[fa:fa-square-caret-down Sample Model] FN_Sample_accession_and_handle_validation_errors end end - subgraph External_EBI[fa:fa-globe EBI ENA Webin REST V1] + %% subgraph External_EBI[fa:fa-globe EBI ENA Webin REST V1] External_EBI_Studies External_EBI_Samples - end + %% end subgraph Consumers User_Users end @@ -219,8 +222,8 @@ flowchart LR DJ_SampleAccessioningJob ==> | 2. | CP_AccessionSubmission CP_AccessionSubmission ==> LB_AccessioningV1Client LB_AccessioningV1Client ==> External_EBI_Samples - External_EBI_Samples ==> | Success: accession number | FN_Accession_Sample_update_accession_number External_EBI_Samples ==> | Failure: reason | MD_AccessionStatuses + External_EBI_Samples ==> | Success: accession number | FN_Accession_Sample_update_accession_number FN_Accession_Sample_update_accession_number -.- VW_Sample MD_AccessionStatuses -.- VW_Sample FN_Samples_accession --> | 2. | VW_Sample @@ -245,7 +248,9 @@ flowchart LR FN_Studies_accession --> | 4. IF success -- flash message | VW_Study FN_Studies_accession --> | 4. IF error | FN_Studies_rescue_accession_errors FN_AccessionService_submit_study_for_user ==> FN_AccessionService_submit - FN_AccessionService_submit ==> External_EBI_Studies + FN_AccessionService_submit ==> FN_AccessionService_post_files + FN_AccessionService_post_files ==> LB_RestClient + LB_RestClient ==> External_EBI_Studies External_EBI_Studies ==> FN_Accessionable_Study_update_accession_number FN_Accessionable_Study_update_accession_number --> MD_Study FN_Studies_rescue_accession_errors --> | flash message | VW_Study @@ -280,7 +285,7 @@ flowchart LR class Legend legendTransparent; class Application_Sequencescape,Application_Limber application; class L_Config,CF_disable_accession_check,CF_y25_706_enable_accessioning configuration; - class SS_API,DelayedJob,Samples,Studies,Submissions SequencescapeSection; + class DelayedJob,Samples,Studies,Submissions SequencescapeSection; class L_Model,MD_Study,MD_AccessionService,MD_Sample,MD_SampleManifest_Uploader,MD_Order,MD_Submission_AccessionBehaviour railsModel; class L_Controller,CT_Samples,CT_Studies railsController; class L_User,User_SeqOps,User_Study_Owners,User_Neil,User_SSR,User_LB_Users,User_Users,User_Developers users; From dee805ba85e9689aa1045bb903d546f5c1024fde Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Fri, 6 Feb 2026 16:49:21 +0000 Subject: [PATCH 58/92] docs: add more colour --- docs/accessioning/accessioning.mermaid | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index ddeb77e8df..757bae11e3 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -286,8 +286,8 @@ flowchart LR class Application_Sequencescape,Application_Limber application; class L_Config,CF_disable_accession_check,CF_y25_706_enable_accessioning configuration; class DelayedJob,Samples,Studies,Submissions SequencescapeSection; - class L_Model,MD_Study,MD_AccessionService,MD_Sample,MD_SampleManifest_Uploader,MD_Order,MD_Submission_AccessionBehaviour railsModel; + class L_Model,MD_Study,MD_AccessionService,MD_Sample,MD_SampleManifest_Uploader,MD_Order,MD_Submission_AccessionBehaviour,MD_AccessionStatuses,MD_Accession_Sample,MD_AccessionService railsModel; class L_Controller,CT_Samples,CT_Studies railsController; class L_User,User_SeqOps,User_Study_Owners,User_Neil,User_SSR,User_LB_Users,User_Users,User_Developers users; - class L_Interface,UI_LB_Charge_and_Pass,UI_Sample_GAN,UI_Sample_SAA,UI_Study_GAN,UI_Study_AAS,UI_Manifest_Upload userInterface; + class L_Interface,UI_LB_Charge_and_Pass,UI_Sample_GAN,UI_Sample_SAA,UI_Study_GAN,UI_Study_AAS,UI_Manifest_Upload,VW_Sample,VW_Study userInterface; class L_LinkSource,L_LinkSink,LK_accessioning_enabled,LK_accessioning_enabled_AAS,LK_accessioning_enabled_Studies_accession,LK_accessioning_enabled_Samples_accession,LK_accessioning_enabled_trigger_accessioning,LK_accessioning_enabled_Accession_perform link; From 1ac271ca49c6cad6b0e79474cdff694dbb30c6f6 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Mon, 9 Feb 2026 11:02:03 +0000 Subject: [PATCH 59/92] fix: add missing accessioning-enabled guard clauses --- app/helpers/samples_helper.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/helpers/samples_helper.rb b/app/helpers/samples_helper.rb index 03f3753c72..6413215b32 100644 --- a/app/helpers/samples_helper.rb +++ b/app/helpers/samples_helper.rb @@ -4,7 +4,9 @@ module SamplesHelper # Indicate to the user that saving the sample will also accession it # This will not happen if the study has not been accessioned def save_text(sample) - return 'Save and Accession' if sample.should_be_accessioned? + if [accessioning_enabled?, sample.should_be_accessioned?, permitted_to_accession?(sample)].all? + return 'Save and Accession' + end 'Save Sample' end From 597434b2e9e1d42c95b0a54cf9d1475339f0b514 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Mon, 9 Feb 2026 11:18:10 +0000 Subject: [PATCH 60/92] refactor: improve reability of if statement --- app/views/studies/information/_accession_statuses.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/studies/information/_accession_statuses.html.erb b/app/views/studies/information/_accession_statuses.html.erb index 33852b716e..cccbf5536e 100644 --- a/app/views/studies/information/_accession_statuses.html.erb +++ b/app/views/studies/information/_accession_statuses.html.erb @@ -36,7 +36,7 @@ <td><%= accession_status&.status&.capitalize %></td> <td><%= accession_status&.updated_at&.in_time_zone&.strftime('%Y-%m-%d %H:%M:%S %Z') %></td> <td> - <% if permitted_to_accession?(sample) & sample.should_be_accessioned? & accessioning_enabled? %> + <% if [accessioning_enabled?, sample.should_be_accessioned?, permitted_to_accession?(sample)].all? %> <%# Add link to perform sample accessioning %> <% if sample.current_accession_status %> <%# If status exists, offer retry %> From 641aeeafddab1c317e20ba916c9d5e295edd7adc Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Mon, 9 Feb 2026 11:18:29 +0000 Subject: [PATCH 61/92] style: add reminder that some checks don't belong in a model --- app/models/sample.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/sample.rb b/app/models/sample.rb index b4cfbf7937..de8a013572 100644 --- a/app/models/sample.rb +++ b/app/models/sample.rb @@ -553,7 +553,8 @@ def should_be_accessioned? false end - # NOTE: this does not check whether the current user is permitted to accession the sample + # NOTE: this does not check whether the current user is permitted to accession the sample, + # nor if accessioning is enabled, as these belong in a controller or library, rather than the model. def accession_and_handle_validation_errors event_user = current_user # the event_user for this sample must be set from the calling controller Accession.accession_sample(self, event_user, perform_now: true) From 71757ff8bb4b31a4803c851cd065a39288b19668 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Mon, 9 Feb 2026 11:25:03 +0000 Subject: [PATCH 62/92] fix: add more missing accessioning-enabled guard clauses --- app/views/sdb/sample_manifests/_samples.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/sdb/sample_manifests/_samples.html.erb b/app/views/sdb/sample_manifests/_samples.html.erb index 535e9d9b11..5dc50d480c 100644 --- a/app/views/sdb/sample_manifests/_samples.html.erb +++ b/app/views/sdb/sample_manifests/_samples.html.erb @@ -38,7 +38,7 @@ <td><%= accession_status&.status&.capitalize %></td> <td><%= accession_status&.updated_at&.in_time_zone&.strftime('%Y-%m-%d %H:%M:%S %Z') %></td> <td> - <% if can?(:accession, sample) && configatron.accession_samples %> + <% if [accessioning_enabled?, sample.should_be_accessioned?, permitted_to_accession?(sample)].all? %> <%# Add link to perform sample accessioning %> <% if sample.current_accession_status %> <%# If status exists, offer retry %> From 55b09b99b529576baf5766cff95f32484ddfb755 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Mon, 9 Feb 2026 11:43:17 +0000 Subject: [PATCH 63/92] refactor: move accession_all_samples out of study model --- app/controllers/studies_controller.rb | 32 ++++++++++++++++++++++++--- app/models/study.rb | 25 --------------------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/app/controllers/studies_controller.rb b/app/controllers/studies_controller.rb index 7598cc2899..7d8fd8ec72 100644 --- a/app/controllers/studies_controller.rb +++ b/app/controllers/studies_controller.rb @@ -253,6 +253,16 @@ def accession end end + # Accession all samples in the study. + # + # If the study does not have an accession number, adds an error to the study and returns. + # Otherwise, iterates through each sample in the study and attempts to accession it, + # unless the sample already has an accession number. + # If an Accession::Error occurs for a sample, adds the error message to the study's errors. + # + # NOTE: this does not check if the current user has permission to accession samples in this study + # + # @return [void] def accession_all_samples # rubocop:disable Metrics/AbcSize,Metrics/MethodLength @study = Study.find(params[:id]) return accessioning_not_enabled_redirect unless accessioning_enabled? @@ -260,11 +270,27 @@ def accession_all_samples # rubocop:disable Metrics/AbcSize,Metrics/MethodLength # TODO: Y26-026 - Enforce accessioning permissions # return accession_permission_denied_redirect unless permitted_to_accession?(@study) - @study.accession_all_samples(current_user) + unless accession_number? + flash[:error] = 'Please accession the study before accessioning samples' + return redirect_to(study_path(@study)) + end + unless samples_accessionable? + flash[:error] = 'Study cannot accession samples, see Study Accessioning tab for details' + return redirect_to(study_path(@study)) + end + + samples.find_each do |sample| + next if sample.accession_number? + + begin + Accession.accession_sample(sample, current_user) + rescue Accession::Error => e + @study.errors.add(:base, e.message) + end + end if @study.errors.any? - error_messages = compile_accession_errors(@study.errors) - flash[:error] = error_messages + flash[:error] = compile_accession_errors(@study.errors) else flash[:notice] = 'All of the samples in this study have been sent for accessioning. ' \ 'Please check back in 5 minutes to confirm that accessioning was successful.' diff --git a/app/models/study.rb b/app/models/study.rb index 84bf3ac864..ca504dac2b 100644 --- a/app/models/study.rb +++ b/app/models/study.rb @@ -583,31 +583,6 @@ def samples_accessionable? ].all? end - # Accession all samples in the study. - # - # If the study does not have an accession number, adds an error to the study and returns. - # Otherwise, iterates through each sample in the study and attempts to accession it, - # unless the sample already has an accession number. - # If an Accession::Error occurs for a sample, adds the error message to the study's errors. - # - # NOTE: this does not check if the current user has permission to accession samples in this study - # - # @return [void] - def accession_all_samples(event_user) - return errors.add(:base, 'Please accession the study before accessioning samples') unless accession_number? - - unless samples_accessionable? - return errors.add(:base, - 'Study cannot accession samples, see Study Accessioning tab for details') - end - - samples.find_each do |sample| - Accession.accession_sample(sample, event_user) unless sample.accession_number? - rescue Accession::Error => e - errors.add(:base, e.message) - end - end - def abbreviation abbreviation = study_metadata.study_name_abbreviation abbreviation.presence || "#{id}STDY" From 80eed30c2d1e36a1b0162c44a11bb5c7ab956c62 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Mon, 9 Feb 2026 11:48:58 +0000 Subject: [PATCH 64/92] docs: fix minor logical errors --- docs/accessioning/accessioning.mermaid | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index 757bae11e3..215c08651f 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -83,7 +83,6 @@ flowchart LR FN_Studies_accession_all_samples(fa:fa-caret-right accession_all_samples) FN_Studies_accession(fa:fa-caret-right accession) FN_Studies_rescue_accession_errors(fa:fa-caret-right rescue_accession_errors) - FN_Study_accession_all_samples(fa:fa-caret-right accession_all_samples) FN_Study_validate_study_for_accessioning(fa:fa-caret-right validate_study_for_accessioning!) %% Links @@ -93,6 +92,8 @@ flowchart LR LK_accessioning_enabled_Samples_accession[\accessioning_enabled?/] LK_accessioning_enabled_trigger_accessioning[\accessioning_enabled?/] LK_accessioning_enabled_Accession_perform[\accessioning_enabled?/] + LK_accessioning_enabled_Study_View[\accessioning_enabled?/] + LK_accessioning_enabled_Sample_View[\accessioning_enabled?/] %% Libraries LB_AccessioningV1Client(fa:fa-book AccessioningV1Client) @@ -126,8 +127,9 @@ flowchart LR LK_accessioning_enabled_Studies_accession LK_accessioning_enabled_Samples_accession LK_accessioning_enabled_trigger_accessioning - LK_accessioning_enabled_Accession_perform - + LK_accessioning_enabled_Accession_perform + LK_accessioning_enabled_Study_View + LK_accessioning_enabled_Sample_View DJ_SampleAccessioningJob LB_AccessioningV1Client @@ -156,7 +158,6 @@ flowchart LR FN_Studies_rescue_accession_errors end subgraph MD_Study[fa:fa-square-caret-down Study Model] - FN_Study_accession_all_samples FN_Study_validate_study_for_accessioning end subgraph CT_Samples[fa:fa-arrows-spin Samples Controller] @@ -224,6 +225,7 @@ flowchart LR LB_AccessioningV1Client ==> External_EBI_Samples External_EBI_Samples ==> | Failure: reason | MD_AccessionStatuses External_EBI_Samples ==> | Success: accession number | FN_Accession_Sample_update_accession_number + LK_accessioning_enabled_Sample_View -.-> VW_Sample FN_Accession_Sample_update_accession_number -.- VW_Sample MD_AccessionStatuses -.- VW_Sample FN_Samples_accession --> | 2. | VW_Sample @@ -234,8 +236,7 @@ flowchart LR User_Study_Owners --> UI_Study_AAS LK_accessioning_enabled_AAS -.-> FN_Studies_accession_all_samples UI_Study_AAS --> FN_Studies_accession_all_samples - FN_Studies_accession_all_samples <--> | 1. | FN_Study_accession_all_samples - FN_Study_accession_all_samples --> FN_Accession_accession_sample + FN_Studies_accession_all_samples <--> | 1. | FN_Accession_accession_sample FN_Studies_accession_all_samples --> | 2. | VW_Study %% Study generate accession number @@ -255,6 +256,7 @@ flowchart LR FN_Accessionable_Study_update_accession_number --> MD_Study FN_Studies_rescue_accession_errors --> | flash message | VW_Study VW_Study --> User_Users + LK_accessioning_enabled_Study_View -.-> VW_Study %% Subgraph styling @@ -290,4 +292,4 @@ flowchart LR class L_Controller,CT_Samples,CT_Studies railsController; class L_User,User_SeqOps,User_Study_Owners,User_Neil,User_SSR,User_LB_Users,User_Users,User_Developers users; class L_Interface,UI_LB_Charge_and_Pass,UI_Sample_GAN,UI_Sample_SAA,UI_Study_GAN,UI_Study_AAS,UI_Manifest_Upload,VW_Sample,VW_Study userInterface; - class L_LinkSource,L_LinkSink,LK_accessioning_enabled,LK_accessioning_enabled_AAS,LK_accessioning_enabled_Studies_accession,LK_accessioning_enabled_Samples_accession,LK_accessioning_enabled_trigger_accessioning,LK_accessioning_enabled_Accession_perform link; + class L_LinkSource,L_LinkSink,LK_accessioning_enabled,LK_accessioning_enabled_AAS,LK_accessioning_enabled_Studies_accession,LK_accessioning_enabled_Samples_accession,LK_accessioning_enabled_trigger_accessioning,LK_accessioning_enabled_Accession_perform,LK_accessioning_enabled_Study_View,LK_accessioning_enabled_Sample_View link; From 5d776424189f8790c46d226d756ff945f9a66d66 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Mon, 9 Feb 2026 11:51:43 +0000 Subject: [PATCH 65/92] fix: repair missing model reference --- app/controllers/studies_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/studies_controller.rb b/app/controllers/studies_controller.rb index 7d8fd8ec72..8fbb039d2d 100644 --- a/app/controllers/studies_controller.rb +++ b/app/controllers/studies_controller.rb @@ -270,16 +270,16 @@ def accession_all_samples # rubocop:disable Metrics/AbcSize,Metrics/MethodLength # TODO: Y26-026 - Enforce accessioning permissions # return accession_permission_denied_redirect unless permitted_to_accession?(@study) - unless accession_number? + unless @study.accession_number? flash[:error] = 'Please accession the study before accessioning samples' return redirect_to(study_path(@study)) end - unless samples_accessionable? + unless @study.samples_accessionable? flash[:error] = 'Study cannot accession samples, see Study Accessioning tab for details' return redirect_to(study_path(@study)) end - samples.find_each do |sample| + @study.samples.find_each do |sample| next if sample.accession_number? begin From 546042e38fb24a652c98aefedfb83ec39f77930a Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Mon, 9 Feb 2026 12:45:39 +0000 Subject: [PATCH 66/92] test: update tests to match code changes --- spec/controllers/studies_controller_spec.rb | 42 +++++++ spec/lib/accession/study_spec.rb | 129 -------------------- 2 files changed, 42 insertions(+), 129 deletions(-) delete mode 100644 spec/lib/accession/study_spec.rb diff --git a/spec/controllers/studies_controller_spec.rb b/spec/controllers/studies_controller_spec.rb index ba3f93b587..3ef9f06d88 100644 --- a/spec/controllers/studies_controller_spec.rb +++ b/spec/controllers/studies_controller_spec.rb @@ -192,6 +192,48 @@ end context 'when the accessioning succeeds' do + it 'accessions all samples in the study' do + study.samples.each do |sample| + expect(sample.reload.sample_metadata.sample_ebi_accession_number).to eq('EGA00001000240') + end + end + + it 'redirects to the accession-statuses tab of the study page' do + expect(subject).to redirect_to(study_path(study, anchor: 'accession-statuses')) + end + + it 'does not set a flash error message' do + expect(flash[:error]).to be_nil + end + + it 'does not set a flash warning message' do + expect(flash[:warning]).to be_nil + end + + it 'sets a flash notice message' do + expect(flash[:notice]).to eq( + 'All of the samples in this study have been sent for accessioning. ' \ + 'Please check back in 5 minutes to confirm that accessioning was successful.' + ) + end + + it 'does not set a flash info message' do + expect(flash[:info]).to be_nil + end + end + + context 'when a sample already has an accession number' do + # add a 6th already accessioned sample to the study + let(:samples) { create_list(:sample_for_accessioning, number_of_samples) + create_list(:accessioned_sample, 1) } + let(:study) { create(:open_study, accession_number: 'ENA123', samples: samples) } + + it 'does not attempt to accession accessioned samples' do + # confirm that only 5 calls were made to the accession client, not 6 + expect(Accession::Submission.client) + .to have_received(:submit_and_fetch_accession_number) + .exactly(number_of_samples).times + end + it 'redirects to the accession-statuses tab of the study page' do expect(subject).to redirect_to(study_path(study, anchor: 'accession-statuses')) end diff --git a/spec/lib/accession/study_spec.rb b/spec/lib/accession/study_spec.rb deleted file mode 100644 index 9c10e3e4cf..0000000000 --- a/spec/lib/accession/study_spec.rb +++ /dev/null @@ -1,129 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -MISSING_METADATA = { - managed_study: %w[sample-taxon-id sample-common-name gender phenotype donor-id].sort.to_sentence, - open_study: %w[sample-taxon-id sample-common-name].sort.to_sentence -}.freeze -STUDY_TYPES = %i[open_study managed_study].freeze - -RSpec.describe Study, :accession, :accessioning_enabled, :un_delay_jobs, type: :model do - include AccessionV1ClientHelper - - let(:current_user) { create(:user) } - let(:accession_number) { 'SAMPLE123456' } - let(:accessionable_samples) { create_list(:sample_for_accessioning, 5) } - let(:non_accessionable_samples) { create_list(:sample, 3) } - - before do - create(:user, api_key: configatron.accession_local_key) - allow(Accession::Submission).to receive(:client).and_return( - stub_accession_client(:submit_and_fetch_accession_number, return_value: accession_number) - ) - end - - after do - SampleManifestExcel.reset! - end - - STUDY_TYPES.each do |study_type| - context "in a #{study_type}" do - let(:missing_metadata_for_study) { MISSING_METADATA[study_type] } - - context 'when all samples in a study are accessionable' do - let(:study) { create(study_type, accession_number: 'ENA123', samples: accessionable_samples) } - - before do - study.accession_all_samples(current_user) - study.reload - end - - it 'accessions only the samples with accession numbers' do - expect(study.samples.count { |sample| sample.sample_metadata.sample_ebi_accession_number.present? }).to eq( - accessionable_samples.count - ) - end - end - - context 'with studies missing accession numbers' do - let(:study) { create(study_type, samples: create_list(:sample_for_accessioning, 5)) } - - before do - # Verify expectation before running the method - expect(Accession).not_to receive(:accession_sample).with(study.samples.first, anything) - study.accession_all_samples(current_user) - study.reload - end - - it 'does not accession any samples' do - expect(study.samples).to be_all { |sample| sample.sample_metadata.sample_ebi_accession_number.nil? } - end - end - - context 'when some samples in a study are not accessionable' do - let(:study) do - create(study_type, accession_number: 'ENA123', samples: accessionable_samples + non_accessionable_samples) - end - - before do - study.accession_all_samples(current_user) - study.reload - end - - it 'adds errors to the sample model' do - expect(study.errors.full_messages).to eq( - [ - "Sample 'Sample6' cannot be accessioned: " \ - "Sample does not have the required metadata: #{missing_metadata_for_study}.", - "Sample 'Sample7' cannot be accessioned: " \ - "Sample does not have the required metadata: #{missing_metadata_for_study}.", - "Sample 'Sample8' cannot be accessioned: " \ - "Sample does not have the required metadata: #{missing_metadata_for_study}." - ] - ) - end - - it 'accessions only the samples with accession numbers' do - expect(study.samples.count { |sample| sample.sample_metadata.sample_ebi_accession_number.present? }).to eq( - accessionable_samples.count - ) - end - - it 'does not accession samples without accession numbers' do - expect(study.samples.count { |sample| sample.sample_metadata.sample_ebi_accession_number.nil? }).to eq( - non_accessionable_samples.count - ) - end - end - - context 'when none of the samples in a study are accessionable' do - let(:study) { create(study_type, accession_number: 'ENA123', samples: non_accessionable_samples) } - - before do - study.accession_all_samples(current_user) - study.reload - end - - it 'adds errors to the sample model' do - expect(study.errors.full_messages).to eq( - [ - "Sample 'Sample1' cannot be accessioned: " \ - "Sample does not have the required metadata: #{missing_metadata_for_study}.", - "Sample 'Sample2' cannot be accessioned: " \ - "Sample does not have the required metadata: #{missing_metadata_for_study}.", - "Sample 'Sample3' cannot be accessioned: " \ - "Sample does not have the required metadata: #{missing_metadata_for_study}." - ] - ) - end - - it 'does not accession samples without accession numbers' do - expect(study.samples.count { |sample| sample.sample_metadata.sample_ebi_accession_number.nil? }).to eq( - non_accessionable_samples.count - ) - end - end - end - end -end From 243c5ff03d9b5664439d60aab4216f016b81c349 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Mon, 9 Feb 2026 13:07:00 +0000 Subject: [PATCH 67/92] docs: move links to inside models --- docs/accessioning/accessioning.mermaid | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index 215c08651f..e8011d2605 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -71,7 +71,7 @@ flowchart LR FN_Accession_Sample_update_accession_number(fa:fa-caret-right update_accession_number) FN_Accession_Sample_validate(fa:fa-caret-right validate) FN_Accession_SampleAccessioning_perform(fa:fa-caret-right Accession::SampleAccessioning#perform) - FN_Accessionable_Study_update_accession_number(fa:fa-caret-right Accessionable::Study#update_accession_number!) + FN_Accessionable_Study_update_accession_number(fa:fa-caret-right update_accession_number!) FN_AccessionHelper_accessioning_enabled(fa:fa-caret-right AccessionHelper#accessioning_enabled?) FN_AccessionService_post_files(fa:fa-caret-right post_files) FN_AccessionService_select_for_study(fa:fa-caret-right select_for_study) @@ -123,11 +123,7 @@ flowchart LR CF_y25_706_enable_accessioning FN_AccessionHelper_accessioning_enabled LK_accessioning_enabled - LK_accessioning_enabled_AAS - LK_accessioning_enabled_Studies_accession - LK_accessioning_enabled_Samples_accession LK_accessioning_enabled_trigger_accessioning - LK_accessioning_enabled_Accession_perform LK_accessioning_enabled_Study_View LK_accessioning_enabled_Sample_View @@ -136,8 +132,6 @@ flowchart LR LB_RestClient MD_AccessionStatuses - FN_Accessionable_Study_update_accession_number - VW_Sample VW_Study @@ -153,6 +147,8 @@ flowchart LR UI_Manifest_Upload end subgraph CT_Studies[fa:fa-arrows-spin Studies Controller] + LK_accessioning_enabled_AAS + LK_accessioning_enabled_Studies_accession FN_Studies_accession_all_samples FN_Studies_accession FN_Studies_rescue_accession_errors @@ -161,9 +157,11 @@ flowchart LR FN_Study_validate_study_for_accessioning end subgraph CT_Samples[fa:fa-arrows-spin Samples Controller] + LK_accessioning_enabled_Samples_accession FN_Samples_accession end subgraph LB_Accession[fa:fa-book Accession - samples only] + LK_accessioning_enabled_Accession_perform FN_Accession_accession_sample FN_Accession_SampleAccessioning_perform CP_AccessionSubmission @@ -178,6 +176,9 @@ flowchart LR FN_AccessionService_submit FN_AccessionService_post_files end + subgraph MD_Accessionable_Study[fa:fa-square-caret-down Accessionable::Study Model] + FN_Accessionable_Study_update_accession_number + end subgraph MD_Sample[fa:fa-square-caret-down Sample Model] FN_Sample_accession_and_handle_validation_errors end @@ -192,11 +193,6 @@ flowchart LR %% Edge connections between nodes - Providers ~~~ CF_y25_706_enable_accessioning - - External_EBI_Studies ~~~ External_EBI_Samples - %% External_EBI_Samples ~~~ External_EBI_Studies - %% Config CF_y25_706_enable_accessioning --> FN_AccessionHelper_accessioning_enabled --> LK_accessioning_enabled @@ -253,7 +249,7 @@ flowchart LR FN_AccessionService_post_files ==> LB_RestClient LB_RestClient ==> External_EBI_Studies External_EBI_Studies ==> FN_Accessionable_Study_update_accession_number - FN_Accessionable_Study_update_accession_number --> MD_Study + FN_Accessionable_Study_update_accession_number -.- VW_Study FN_Studies_rescue_accession_errors --> | flash message | VW_Study VW_Study --> User_Users LK_accessioning_enabled_Study_View -.-> VW_Study @@ -288,7 +284,7 @@ flowchart LR class Application_Sequencescape,Application_Limber application; class L_Config,CF_disable_accession_check,CF_y25_706_enable_accessioning configuration; class DelayedJob,Samples,Studies,Submissions SequencescapeSection; - class L_Model,MD_Study,MD_AccessionService,MD_Sample,MD_SampleManifest_Uploader,MD_Order,MD_Submission_AccessionBehaviour,MD_AccessionStatuses,MD_Accession_Sample,MD_AccessionService railsModel; + class L_Model,MD_Study,MD_AccessionService,MD_Sample,MD_SampleManifest_Uploader,MD_Order,MD_Submission_AccessionBehaviour,MD_AccessionStatuses,MD_Accession_Sample,MD_AccessionService,MD_Accessionable_Study railsModel; class L_Controller,CT_Samples,CT_Studies railsController; class L_User,User_SeqOps,User_Study_Owners,User_Neil,User_SSR,User_LB_Users,User_Users,User_Developers users; class L_Interface,UI_LB_Charge_and_Pass,UI_Sample_GAN,UI_Sample_SAA,UI_Study_GAN,UI_Study_AAS,UI_Manifest_Upload,VW_Sample,VW_Study userInterface; From e0d10f2cc4f444e262cd1f967b185fe955a7c36a Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Mon, 9 Feb 2026 16:16:27 +0000 Subject: [PATCH 68/92] docs: update legend --- docs/accessioning/accessioning.mermaid | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index e8011d2605..ea325c4422 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -12,7 +12,7 @@ flowchart LR direction TB L_User(fa:fa-user User) L_External(fa:fa-arrow-up External API) - L_Interface(fa:fa-computer-mouse User Interface) + L_Action(fa:fa-computer-mouse User Action) L_Model(fa:fa-square-caret-down Model) L_Controller(fa:fa-arrows-spin Controller) L_View(fa:fa-eye View) @@ -24,7 +24,7 @@ flowchart LR L_LinkSource[/Link Source\] L_LinkSink[\Link Sink/] - L_User ~~~ L_External ~~~ L_Interface ~~~ L_Resource ~~~ L_Async ~~~ L_LinkSource + L_User ~~~ L_External ~~~ L_Action ~~~ L_Resource ~~~ L_Async ~~~ L_LinkSource L_Config ~~~ L_Controller ~~~ L_Model ~~~ L_View ~~~ L_Function ~~~ L_Library ~~~ L_LinkSink InvisibleNodeA[ ] -..-> | Data flow | InvisibleNodeB[ ] -- Function calls --> InvisibleNodeC[ ] == Accessioning function calls ==> InvisibleNodeD[ ] @@ -222,8 +222,8 @@ flowchart LR External_EBI_Samples ==> | Failure: reason | MD_AccessionStatuses External_EBI_Samples ==> | Success: accession number | FN_Accession_Sample_update_accession_number LK_accessioning_enabled_Sample_View -.-> VW_Sample - FN_Accession_Sample_update_accession_number -.- VW_Sample - MD_AccessionStatuses -.- VW_Sample + FN_Accession_Sample_update_accession_number -.-> VW_Sample + MD_AccessionStatuses -.-> VW_Sample FN_Samples_accession --> | 2. | VW_Sample VW_Sample --> User_Users @@ -249,7 +249,7 @@ flowchart LR FN_AccessionService_post_files ==> LB_RestClient LB_RestClient ==> External_EBI_Studies External_EBI_Studies ==> FN_Accessionable_Study_update_accession_number - FN_Accessionable_Study_update_accession_number -.- VW_Study + FN_Accessionable_Study_update_accession_number -.-> VW_Study FN_Studies_rescue_accession_errors --> | flash message | VW_Study VW_Study --> User_Users LK_accessioning_enabled_Study_View -.-> VW_Study @@ -287,5 +287,5 @@ flowchart LR class L_Model,MD_Study,MD_AccessionService,MD_Sample,MD_SampleManifest_Uploader,MD_Order,MD_Submission_AccessionBehaviour,MD_AccessionStatuses,MD_Accession_Sample,MD_AccessionService,MD_Accessionable_Study railsModel; class L_Controller,CT_Samples,CT_Studies railsController; class L_User,User_SeqOps,User_Study_Owners,User_Neil,User_SSR,User_LB_Users,User_Users,User_Developers users; - class L_Interface,UI_LB_Charge_and_Pass,UI_Sample_GAN,UI_Sample_SAA,UI_Study_GAN,UI_Study_AAS,UI_Manifest_Upload,VW_Sample,VW_Study userInterface; + class L_Action,L_View,UI_LB_Charge_and_Pass,UI_Sample_GAN,UI_Sample_SAA,UI_Study_GAN,UI_Study_AAS,UI_Manifest_Upload,VW_Sample,VW_Study userInterface; class L_LinkSource,L_LinkSink,LK_accessioning_enabled,LK_accessioning_enabled_AAS,LK_accessioning_enabled_Studies_accession,LK_accessioning_enabled_Samples_accession,LK_accessioning_enabled_trigger_accessioning,LK_accessioning_enabled_Accession_perform,LK_accessioning_enabled_Study_View,LK_accessioning_enabled_Sample_View link; From 7b1deafc70b80e7b471ccc780564e993a8eed73e Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Mon, 9 Feb 2026 17:24:15 +0000 Subject: [PATCH 69/92] docs: refactor diagram into 8 different processes --- docs/accessioning/accessioning.mermaid | 117 ++++++++++++++----------- 1 file changed, 64 insertions(+), 53 deletions(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index ea325c4422..fe454e9be5 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -10,6 +10,7 @@ flowchart LR %% Legend subgraph Legend [Legend] direction TB + L_Application(fa:fa-circle-arrow-right Application Process) L_User(fa:fa-user User) L_External(fa:fa-arrow-up External API) L_Action(fa:fa-computer-mouse User Action) @@ -24,7 +25,7 @@ flowchart LR L_LinkSource[/Link Source\] L_LinkSink[\Link Sink/] - L_User ~~~ L_External ~~~ L_Action ~~~ L_Resource ~~~ L_Async ~~~ L_LinkSource + L_Application ~~~ L_User ~~~ L_External ~~~ L_Action ~~~ L_Resource ~~~ L_Async ~~~ L_LinkSource L_Config ~~~ L_Controller ~~~ L_Model ~~~ L_View ~~~ L_Function ~~~ L_Library ~~~ L_LinkSink InvisibleNodeA[ ] -..-> | Data flow | InvisibleNodeB[ ] -- Function calls --> InvisibleNodeC[ ] == Accessioning function calls ==> InvisibleNodeD[ ] @@ -52,16 +53,9 @@ flowchart LR UI_Manifest_Upload(fa:fa-computer-mouse Manifest Upload) %% Models - %% MD_AccessionService(fa:fa-square-caret-down AccessionService) - %% MD_Sample(fa:fa-square-caret-down Sample) - %% MD_Study(fa:fa-square-caret-down Study) MD_SampleManifest_Uploader(fa:fa-square-caret-down SampleManifest::Uploader) MD_AccessionStatuses(fa:fa-square-caret-down AccessionStatuses) - %% Controllers - %% CT_Samples(fa:fa-arrows-spin Samples Controller) - %% CT_Studies(fa:fa-arrows-spin Studies Controller) - %% Views VW_Sample(fa:fa-eye Sample View) VW_Study(fa:fa-eye Study View) @@ -70,19 +64,18 @@ flowchart LR FN_Accession_accession_sample(fa:fa-caret-right accession_sample) FN_Accession_Sample_update_accession_number(fa:fa-caret-right update_accession_number) FN_Accession_Sample_validate(fa:fa-caret-right validate) - FN_Accession_SampleAccessioning_perform(fa:fa-caret-right Accession::SampleAccessioning#perform) + FN_Accession_SampleAccessioning_perform(fa:fa-caret-right Accession::SampleAccessioning<br/>#perform) FN_Accessionable_Study_update_accession_number(fa:fa-caret-right update_accession_number!) - FN_AccessionHelper_accessioning_enabled(fa:fa-caret-right AccessionHelper#accessioning_enabled?) + FN_AccessionHelper_accessioning_enabled(fa:fa-caret-right AccessionHelper<br/>#accessioning_enabled?) FN_AccessionService_post_files(fa:fa-caret-right post_files) FN_AccessionService_select_for_study(fa:fa-caret-right select_for_study) FN_AccessionService_submit_study_for_user(fa:fa-caret-right submit_study_for_user) FN_AccessionService_submit(fa:fa-caret-right submit) FN_Sample_accession_and_handle_validation_errors(fa:fa-caret-right accession_and_handle_validation_errors) - FN_SampleManifestExcel_Upload_Base_trigger_accessioning(fa:fa-caret-right SampleManifestExcel::Upload::Base#trigger_accessioning) + FN_SampleManifestExcel_Upload_Base_trigger_accessioning(fa:fa-caret-right SampleManifestExcel::Upload::Base<br/>#trigger_accessioning) FN_Samples_accession(fa:fa-caret-right accession) FN_Studies_accession_all_samples(fa:fa-caret-right accession_all_samples) FN_Studies_accession(fa:fa-caret-right accession) - FN_Studies_rescue_accession_errors(fa:fa-caret-right rescue_accession_errors) FN_Study_validate_study_for_accessioning(fa:fa-caret-right validate_study_for_accessioning!) %% Links @@ -115,61 +108,41 @@ flowchart LR User_Neil User_SSR end - subgraph Application_Sequencescape[Sequencescape Application] - UI_Manifest_Upload - MD_SampleManifest_Uploader - FN_SampleManifestExcel_Upload_Base_trigger_accessioning + subgraph App_SS_Configuration[fa:fa-screwdriver-wrench Sequencescape Configuration] + direction LR CF_y25_706_enable_accessioning FN_AccessionHelper_accessioning_enabled LK_accessioning_enabled - LK_accessioning_enabled_trigger_accessioning - LK_accessioning_enabled_Study_View - LK_accessioning_enabled_Sample_View - - DJ_SampleAccessioningJob - LB_AccessioningV1Client - LB_RestClient - MD_AccessionStatuses - - VW_Sample - VW_Study + end + subgraph App_SS_Pages[fa:fa-eye Sequencescape Pages] + subgraph Study_Page[Study Page] + UI_Study_GAN + UI_Study_AAS + end subgraph Samples UI_Sample_SAA UI_Sample_GAN end - subgraph Studies - UI_Study_GAN - UI_Study_AAS - end subgraph Sample Manifests + RES_Manifest UI_Manifest_Upload end + end + + subgraph App_SS_Study_Accessioning[fa:fa-circle-arrow-right Study Accessioning] + LB_RestClient + subgraph CT_Studies[fa:fa-arrows-spin Studies Controller] LK_accessioning_enabled_AAS LK_accessioning_enabled_Studies_accession FN_Studies_accession_all_samples FN_Studies_accession - FN_Studies_rescue_accession_errors end subgraph MD_Study[fa:fa-square-caret-down Study Model] FN_Study_validate_study_for_accessioning end - subgraph CT_Samples[fa:fa-arrows-spin Samples Controller] - LK_accessioning_enabled_Samples_accession - FN_Samples_accession - end - subgraph LB_Accession[fa:fa-book Accession - samples only] - LK_accessioning_enabled_Accession_perform - FN_Accession_accession_sample - FN_Accession_SampleAccessioning_perform - CP_AccessionSubmission - subgraph MD_Accession_Sample[fa:fa-square-caret-down Accession::Sample Model] - FN_Accession_Sample_validate - FN_Accession_Sample_update_accession_number - end - end subgraph MD_AccessionService[fa:fa-square-caret-down AccessionService Model - studies only] FN_AccessionService_select_for_study FN_AccessionService_submit_study_for_user @@ -179,10 +152,47 @@ flowchart LR subgraph MD_Accessionable_Study[fa:fa-square-caret-down Accessionable::Study Model] FN_Accessionable_Study_update_accession_number end + end + + subgraph App_SS_Sample_Accessioning[fa:fa-circle-arrow-right Sample Accessioning] + subgraph CT_Samples[fa:fa-arrows-spin Samples Controller] + LK_accessioning_enabled_Samples_accession + FN_Samples_accession + end + end + + subgraph App_SS_Sample_Save_and_Accession[fa:fa-circle-arrow-right Sample Save and Accession] subgraph MD_Sample[fa:fa-square-caret-down Sample Model] FN_Sample_accession_and_handle_validation_errors end end + + subgraph App_SS_Sample_Manifest_Accessioning[fa:fa-circle-arrow-right Sample Manifest Accessioning] + MD_SampleManifest_Uploader + LK_accessioning_enabled_trigger_accessioning + FN_SampleManifestExcel_Upload_Base_trigger_accessioning + end + subgraph App_SS_Accession_Sample[fa:fa-circle-arrow-right Accession Sample] + DJ_SampleAccessioningJob + LB_AccessioningV1Client + MD_AccessionStatuses + + LK_accessioning_enabled_Accession_perform + FN_Accession_accession_sample + FN_Accession_SampleAccessioning_perform + CP_AccessionSubmission + subgraph MD_Accession_Sample[fa:fa-square-caret-down Accession::Sample Model] + FN_Accession_Sample_validate + FN_Accession_Sample_update_accession_number + end + end + + subgraph App_SS_Views[fa:fa-circle-arrow-right Sequencescape Views] + LK_accessioning_enabled_Sample_View + VW_Sample + LK_accessioning_enabled_Study_View + VW_Study + end %% subgraph External_EBI[fa:fa-globe EBI ENA Webin REST V1] External_EBI_Studies External_EBI_Samples @@ -193,6 +203,9 @@ flowchart LR %% Edge connections between nodes + App_SS_Pages ~~~~ App_SS_Configuration + App_SS_Accession_Sample ~~~~ App_SS_Views + %% Config CF_y25_706_enable_accessioning --> FN_AccessionHelper_accessioning_enabled --> LK_accessioning_enabled @@ -202,7 +215,8 @@ flowchart LR FN_Sample_accession_and_handle_validation_errors --> FN_Accession_accession_sample %% Manifest upload - User_SSR --> RES_Manifest --> UI_Manifest_Upload --> MD_SampleManifest_Uploader + RES_Manifest -.-> MD_SampleManifest_Uploader + User_SSR --> UI_Manifest_Upload --> MD_SampleManifest_Uploader LK_accessioning_enabled_trigger_accessioning -.-> FN_SampleManifestExcel_Upload_Base_trigger_accessioning MD_SampleManifest_Uploader --> FN_SampleManifestExcel_Upload_Base_trigger_accessioning ---> FN_Accession_accession_sample @@ -222,7 +236,6 @@ flowchart LR External_EBI_Samples ==> | Failure: reason | MD_AccessionStatuses External_EBI_Samples ==> | Success: accession number | FN_Accession_Sample_update_accession_number LK_accessioning_enabled_Sample_View -.-> VW_Sample - FN_Accession_Sample_update_accession_number -.-> VW_Sample MD_AccessionStatuses -.-> VW_Sample FN_Samples_accession --> | 2. | VW_Sample VW_Sample --> User_Users @@ -236,21 +249,19 @@ flowchart LR FN_Studies_accession_all_samples --> | 2. | VW_Study %% Study generate accession number + User_SSR --> UI_Study_GAN User_Study_Owners --> UI_Study_GAN UI_Study_GAN --> FN_Studies_accession LK_accessioning_enabled_Studies_accession -.-> FN_Studies_accession FN_Studies_accession <--> | 1. | FN_Study_validate_study_for_accessioning FN_Studies_accession <--> | 2. | FN_AccessionService_select_for_study FN_Studies_accession <--> | 3. | FN_AccessionService_submit_study_for_user - FN_Studies_accession --> | 4. IF success -- flash message | VW_Study - FN_Studies_accession --> | 4. IF error | FN_Studies_rescue_accession_errors + FN_Studies_accession --> | 4. with flash message | VW_Study FN_AccessionService_submit_study_for_user ==> FN_AccessionService_submit FN_AccessionService_submit ==> FN_AccessionService_post_files FN_AccessionService_post_files ==> LB_RestClient LB_RestClient ==> External_EBI_Studies External_EBI_Studies ==> FN_Accessionable_Study_update_accession_number - FN_Accessionable_Study_update_accession_number -.-> VW_Study - FN_Studies_rescue_accession_errors --> | flash message | VW_Study VW_Study --> User_Users LK_accessioning_enabled_Study_View -.-> VW_Study @@ -281,7 +292,7 @@ flowchart LR class InvisibleNodeA,InvisibleNodeB,InvisibleNodeC,InvisibleNodeD invisible; class Legend legendTransparent; - class Application_Sequencescape,Application_Limber application; + class L_Application,App_SS_Accession_Sample,App_SS_Configuration,App_SS_Pages,App_SS_Views,App_SS_Study_Accessioning,App_SS_Sample_Save_and_Accession,App_SS_Sample_Manifest_Accessioning,App_SS_Sample_Accessioning application; class L_Config,CF_disable_accession_check,CF_y25_706_enable_accessioning configuration; class DelayedJob,Samples,Studies,Submissions SequencescapeSection; class L_Model,MD_Study,MD_AccessionService,MD_Sample,MD_SampleManifest_Uploader,MD_Order,MD_Submission_AccessionBehaviour,MD_AccessionStatuses,MD_Accession_Sample,MD_AccessionService,MD_Accessionable_Study railsModel; From 32fb21980cdacfe890aa773658996f1f93fbf899 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Mon, 9 Feb 2026 17:38:53 +0000 Subject: [PATCH 70/92] docs: remove users --- docs/accessioning/accessioning.mermaid | 39 +++++--------------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index fe454e9be5..b88dae5a23 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -1,5 +1,5 @@ --- -title: Accessioning Call Graph Overview +title: Sequencescape Accessioning Processes --- %%{ init: { 'flowchart': { 'curve': 'curvy' }, @@ -11,7 +11,6 @@ flowchart LR subgraph Legend [Legend] direction TB L_Application(fa:fa-circle-arrow-right Application Process) - L_User(fa:fa-user User) L_External(fa:fa-arrow-up External API) L_Action(fa:fa-computer-mouse User Action) L_Model(fa:fa-square-caret-down Model) @@ -25,21 +24,15 @@ flowchart LR L_LinkSource[/Link Source\] L_LinkSink[\Link Sink/] - L_Application ~~~ L_User ~~~ L_External ~~~ L_Action ~~~ L_Resource ~~~ L_Async ~~~ L_LinkSource + L_Application ~~~ L_External ~~~ L_Action ~~~ L_Resource ~~~ L_Async ~~~ L_LinkSource L_Config ~~~ L_Controller ~~~ L_Model ~~~ L_View ~~~ L_Function ~~~ L_Library ~~~ L_LinkSink InvisibleNodeA[ ] -..-> | Data flow | InvisibleNodeB[ ] -- Function calls --> InvisibleNodeC[ ] == Accessioning function calls ==> InvisibleNodeD[ ] end - Legend ~~~ Providers %% End Legend %% Nodes - %% Users - User_Study_Owners(fa:fa-user Study Owners) - User_Neil(fa:fa-user PSD Support) - User_SSR(fa:fa-user SSRs) - User_Users(fa:fa-user SS Users) %% External Systems External_EBI_Studies(fa:fa-arrow-up ENA/EGA Studies) @@ -103,12 +96,6 @@ flowchart LR RES_Manifest(fa:fa-file Manifest) %% Groupings of nodes - subgraph Providers - User_Study_Owners - User_Neil - User_SSR - end - subgraph App_SS_Configuration[fa:fa-screwdriver-wrench Sequencescape Configuration] direction LR CF_y25_706_enable_accessioning @@ -197,9 +184,7 @@ flowchart LR External_EBI_Studies External_EBI_Samples %% end - subgraph Consumers - User_Users - end + %% Edge connections between nodes @@ -210,19 +195,16 @@ flowchart LR CF_y25_706_enable_accessioning --> FN_AccessionHelper_accessioning_enabled --> LK_accessioning_enabled %% Sample save and accession - User_SSR --> UI_Sample_SAA - UI_Sample_SAA --> FN_Sample_accession_and_handle_validation_errors + UI_Sample_SAA ----> FN_Sample_accession_and_handle_validation_errors FN_Sample_accession_and_handle_validation_errors --> FN_Accession_accession_sample %% Manifest upload - RES_Manifest -.-> MD_SampleManifest_Uploader - User_SSR --> UI_Manifest_Upload --> MD_SampleManifest_Uploader + RES_Manifest -.-> UI_Manifest_Upload + UI_Manifest_Upload --> MD_SampleManifest_Uploader LK_accessioning_enabled_trigger_accessioning -.-> FN_SampleManifestExcel_Upload_Base_trigger_accessioning MD_SampleManifest_Uploader --> FN_SampleManifestExcel_Upload_Base_trigger_accessioning ---> FN_Accession_accession_sample %% Sample generate accession number - User_SSR --> UI_Sample_GAN - User_Neil --> UI_Sample_GAN LK_accessioning_enabled_Samples_accession -.-> FN_Samples_accession UI_Sample_GAN --> FN_Samples_accession FN_Samples_accession <--> | 1. | FN_Accession_accession_sample @@ -238,19 +220,14 @@ flowchart LR LK_accessioning_enabled_Sample_View -.-> VW_Sample MD_AccessionStatuses -.-> VW_Sample FN_Samples_accession --> | 2. | VW_Sample - VW_Sample --> User_Users %% Study accession all samples - User_Neil --> UI_Study_AAS - User_Study_Owners --> UI_Study_AAS LK_accessioning_enabled_AAS -.-> FN_Studies_accession_all_samples UI_Study_AAS --> FN_Studies_accession_all_samples FN_Studies_accession_all_samples <--> | 1. | FN_Accession_accession_sample FN_Studies_accession_all_samples --> | 2. | VW_Study %% Study generate accession number - User_SSR --> UI_Study_GAN - User_Study_Owners --> UI_Study_GAN UI_Study_GAN --> FN_Studies_accession LK_accessioning_enabled_Studies_accession -.-> FN_Studies_accession FN_Studies_accession <--> | 1. | FN_Study_validate_study_for_accessioning @@ -262,7 +239,6 @@ flowchart LR FN_AccessionService_post_files ==> LB_RestClient LB_RestClient ==> External_EBI_Studies External_EBI_Studies ==> FN_Accessionable_Study_update_accession_number - VW_Study --> User_Users LK_accessioning_enabled_Study_View -.-> VW_Study %% Subgraph styling @@ -287,7 +263,7 @@ flowchart LR classDef link fill:#fde4cf; classDef railsModel fill:#98f5e1; classDef railsController fill:#b9fbc0; - classDef users fill:#f1c0e8; + %% classDef users fill:#f1c0e8; classDef userInterface fill:#a3c4f3; class InvisibleNodeA,InvisibleNodeB,InvisibleNodeC,InvisibleNodeD invisible; @@ -297,6 +273,5 @@ flowchart LR class DelayedJob,Samples,Studies,Submissions SequencescapeSection; class L_Model,MD_Study,MD_AccessionService,MD_Sample,MD_SampleManifest_Uploader,MD_Order,MD_Submission_AccessionBehaviour,MD_AccessionStatuses,MD_Accession_Sample,MD_AccessionService,MD_Accessionable_Study railsModel; class L_Controller,CT_Samples,CT_Studies railsController; - class L_User,User_SeqOps,User_Study_Owners,User_Neil,User_SSR,User_LB_Users,User_Users,User_Developers users; class L_Action,L_View,UI_LB_Charge_and_Pass,UI_Sample_GAN,UI_Sample_SAA,UI_Study_GAN,UI_Study_AAS,UI_Manifest_Upload,VW_Sample,VW_Study userInterface; class L_LinkSource,L_LinkSink,LK_accessioning_enabled,LK_accessioning_enabled_AAS,LK_accessioning_enabled_Studies_accession,LK_accessioning_enabled_Samples_accession,LK_accessioning_enabled_trigger_accessioning,LK_accessioning_enabled_Accession_perform,LK_accessioning_enabled_Study_View,LK_accessioning_enabled_Sample_View link; From b9dab24a6ddba2d8735d31f420207fa000cb63c8 Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 22:40:19 +0000 Subject: [PATCH 71/92] Update faraday to version 2.14.1 --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index eace765b67..27cf5c9182 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -236,7 +236,7 @@ GEM factory_bot_rails (6.5.1) factory_bot (~> 6.5) railties (>= 6.1.0) - faraday (2.14.0) + faraday (2.14.1) faraday-net_http (>= 2.0, < 3.5) json logger @@ -338,8 +338,8 @@ GEM mutex_m (0.3.0) mysql2 (0.5.7) bigdecimal - net-http (0.7.0) - uri + net-http (0.9.1) + uri (>= 0.11.1) net-imap (0.5.9) date net-protocol From 69961ba564274bb3f01efe2fec6dbe5e24af3020 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Tue, 10 Feb 2026 08:58:49 +0000 Subject: [PATCH 72/92] docs: straighten lines --- docs/accessioning/accessioning.mermaid | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index b88dae5a23..1c952de5d5 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -194,16 +194,16 @@ flowchart LR %% Config CF_y25_706_enable_accessioning --> FN_AccessionHelper_accessioning_enabled --> LK_accessioning_enabled - %% Sample save and accession - UI_Sample_SAA ----> FN_Sample_accession_and_handle_validation_errors - FN_Sample_accession_and_handle_validation_errors --> FN_Accession_accession_sample - %% Manifest upload RES_Manifest -.-> UI_Manifest_Upload UI_Manifest_Upload --> MD_SampleManifest_Uploader LK_accessioning_enabled_trigger_accessioning -.-> FN_SampleManifestExcel_Upload_Base_trigger_accessioning - MD_SampleManifest_Uploader --> FN_SampleManifestExcel_Upload_Base_trigger_accessioning ---> FN_Accession_accession_sample + MD_SampleManifest_Uploader --> FN_SampleManifestExcel_Upload_Base_trigger_accessioning --> FN_Accession_accession_sample + %% Sample save and accession + UI_Sample_SAA ---> FN_Sample_accession_and_handle_validation_errors + FN_Sample_accession_and_handle_validation_errors --> FN_Accession_accession_sample + %% Sample generate accession number LK_accessioning_enabled_Samples_accession -.-> FN_Samples_accession UI_Sample_GAN --> FN_Samples_accession From 445d5b5b5659f9d51fa0d63821687e739797a755 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Tue, 10 Feb 2026 09:15:12 +0000 Subject: [PATCH 73/92] docs: remove old diagrams --- docs/accessioning/accessioning-calls.mermaid | 208 ------------------- docs/accessioning/accessioning-users.mermaid | 142 ------------- 2 files changed, 350 deletions(-) delete mode 100644 docs/accessioning/accessioning-calls.mermaid delete mode 100644 docs/accessioning/accessioning-users.mermaid diff --git a/docs/accessioning/accessioning-calls.mermaid b/docs/accessioning/accessioning-calls.mermaid deleted file mode 100644 index 1d570e5560..0000000000 --- a/docs/accessioning/accessioning-calls.mermaid +++ /dev/null @@ -1,208 +0,0 @@ ---- -title: Accessioning Call Graph ---- -%%{ init: { - 'flowchart': { 'curve': 'curvy' }, - 'theme': 'neutral' - } -}%% -flowchart LR - %% Legend - subgraph Legend [Legend] - direction TB - L_User(fa:fa-user User) - L_External(fa:fa-globe External System) - L_API(fa:fa-arrow-right-to-bracket API) - L_Interface(fa:fa-computer-mouse User Interface) - L_Model(fa:fa-square-caret-down Model) - L_Controller(fa:fa-arrows-spin Controller) - L_Function(fa:fa-caret-right Function) - L_Config(fa:fa-screwdriver-wrench Configuration) - L_Resource(fa:fa-file Resource) - L_Library(fa:fa-book Library) - L_Async(fa:fa-clock Asynchronous Process) - - L_User ~~~ L_External ~~~ L_API ~~~ L_Interface ~~~ L_Resource ~~~ L_Async - L_Config ~~~ L_Controller ~~~ L_Model ~~~ L_Function ~~~ L_Library - - InvisibleNodeA[ ] -..-> | Data flow | InvisibleNodeB[ ] -- Function calls --> InvisibleNodeC[ ] == Accessioning function calls ==> InvisibleNodeD[ ] - end - - Legend ~~~ User_Any - %% End Legend - - %% Nodes - %% Users - User_Any(fa:fa-user Users) - External_EBI(fa:fa-globe EBI) - - %% User Interfaces - UI_SS_Sample_GAN(fa:fa-computer-mouse Generate Accession Number) - UI_SS_Study_GAN(fa:fa-computer-mouse Generate Accession Number) - UI_SS_Study_AAS(fa:fa-computer-mouse Accession all Samples) - UI_SS_Manifest_Upload(fa:fa-computer-mouse Manifest Upload) - - %% Models - MD_SS_Order(fa:fa-square-caret-down Order) - %% MD_SS_AccessionService(fa:fa-square-caret-down AccessionService) - MD_SS_Submission_AccessionBehaviour(fa:fa-square-caret-down Submission::AccessionBehaviour) - %% MD_SS_Sample(fa:fa-square-caret-down Sample) - %% MD_SS_Study(fa:fa-square-caret-down Study) - MD_SS_SampleManifest_Uploader(fa:fa-square-caret-down SampleManifest::Uploader) - - %% Controllers - %% CT_SS_Samples(fa:fa-arrows-spin Samples Controller) - %% CT_SS_Studies(fa:fa-arrows-spin Studies Controller) - - %% Functions - FN_SS_AccessionService_submit_sample_for_user(fa:fa-caret-right submit_sample_for_user) - FN_SS_AccessionService_submit_study_for_user(fa:fa-caret-right submit_study_for_user) - FN_SS_AccessionService_submit(fa:fa-caret-right submit) - FN_SS_Sample_accession(fa:fa-caret-right accession) - FN_SS_Sample_validate_accessionable(fa:fa-caret-right validate_accessionable!) - FN_SS_Sample_validate_ena_required_fields(fa:fa-caret-right validate_ena_required_fields!) - FN_SS_Samples_accession(fa:fa-caret-right accession) - FN_SS_Study_accession_all_samples(fa:fa-caret-right accession_all_samples) - FN_SS_Studies_accession_all_samples(fa:fa-caret-right accession_all_samples) - FN_SS_Studies_accession(fa:fa-caret-right accession) - FN_SS_Studies_validate_ena_required_fields(fa:fa-caret-right validate_ena_required_fields!) - FN_SS_Studies_rescue_accession_errors(fa:fa-caret-right rescue_accession_errors) - - %% Other Components - API_SS_OrderResource(fa:fa-arrow-right-to-bracket Order Resource) - CP_SS_Delayed_Job_Accessioning(fa:fa-clock DelayedJob::SampleAccessioningJob) - CP_SS_AccessionSubmission(fa:fa-book Accession::Submission) - CP_SS_AccessionRequest(fa:fa-book Accession::Request) - CP_SS_SampleManifestExcel_Upload(SampleManifestExcel::Upload) - - %% Config - CF_SS_accession_samples(fa:fa-screwdriver-wrench accession_samples) - CF_SS_disable_accession_check(fa:fa-screwdriver-wrench disable_accession_check) - - %% Resources - RES_Manifest(fa:fa-file Manifest) - - %% Groupings of nodes - subgraph Application_Sequencescape - UI_SS_Manifest_Upload - MD_SS_SampleManifest_Uploader - CP_SS_SampleManifestExcel_Upload - MD_SS_Order - MD_SS_Submission_AccessionBehaviour - CF_SS_accession_samples - CF_SS_disable_accession_check - CP_SS_Delayed_Job_Accessioning - - subgraph Samples - UI_SS_Sample_GAN - end - subgraph Studies - UI_SS_Study_GAN - UI_SS_Study_AAS - end - subgraph SS_API["SS API"] - API_SS_OrderResource - end - subgraph CT_SS_Studies[fa:fa-arrows-spin Studies Controller] - FN_SS_Studies_accession_all_samples - FN_SS_Studies_accession - FN_SS_Studies_validate_ena_required_fields - FN_SS_Studies_rescue_accession_errors - end - subgraph MD_SS_Study[fa:fa-square-caret-down Study Model] - FN_SS_Study_accession_all_samples - end - subgraph CT_SS_Samples[fa:fa-arrows-spin Samples Controller] - FN_SS_Samples_accession - end - subgraph LB_SS_Accession[fa:fa-book Accession - newer] - CP_SS_AccessionSubmission - CP_SS_AccessionRequest - end - subgraph MD_SS_AccessionService[fa:fa-square-caret-down AccessionService Model - older] - FN_SS_AccessionService_submit_sample_for_user - FN_SS_AccessionService_submit_study_for_user - FN_SS_AccessionService_submit - end - subgraph MD_SS_Sample[fa:fa-square-caret-down Sample Model] - FN_SS_Sample_accession - FN_SS_Sample_validate_accessionable - FN_SS_Sample_validate_ena_required_fields - end - end - - %% Edge connections between nodes - Legend ~~~ User_Any - User_Any ~~~ Application_Sequencescape - CT_SS_Studies ~~~ MD_SS_Study ~~~ CT_SS_Samples ~~~ MD_SS_Sample - - %% Limber-related - User_Any --> API_SS_OrderResource --> MD_SS_Order - MD_SS_Order --> MD_SS_Submission_AccessionBehaviour - MD_SS_Submission_AccessionBehaviour --> CF_SS_disable_accession_check - - %% Manifest upload - User_Any --> RES_Manifest -..-> UI_SS_Manifest_Upload --> MD_SS_SampleManifest_Uploader - MD_SS_SampleManifest_Uploader --> CP_SS_SampleManifestExcel_Upload ---> FN_SS_Sample_accession - - %% Sample generate accession number - User_Any --> UI_SS_Sample_GAN - FN_SS_Samples_accession ----> | 1. |CF_SS_accession_samples - UI_SS_Sample_GAN ----> FN_SS_Samples_accession - FN_SS_Samples_accession --> | 2. | FN_SS_Sample_validate_ena_required_fields - FN_SS_Samples_accession ==> | 3. Validation Passed | FN_SS_AccessionService_submit_sample_for_user - FN_SS_AccessionService_submit_sample_for_user ==> FN_SS_AccessionService_submit - FN_SS_AccessionService_submit ==> External_EBI - - %% Study accession all samples - User_Any --> UI_SS_Study_AAS - UI_SS_Study_AAS --> FN_SS_Studies_accession_all_samples - FN_SS_Studies_accession_all_samples --> FN_SS_Study_accession_all_samples - FN_SS_Study_accession_all_samples --> FN_SS_Sample_accession - FN_SS_Sample_accession --> | 1. | CF_SS_accession_samples - FN_SS_Sample_accession --> | 2. | FN_SS_Sample_validate_accessionable - FN_SS_Sample_accession ==> | 3. | CP_SS_Delayed_Job_Accessioning - CP_SS_Delayed_Job_Accessioning ==> CP_SS_AccessionSubmission ==> CP_SS_AccessionRequest ==> External_EBI - - %% Study generate accession number - User_Any --> UI_SS_Study_GAN - UI_SS_Study_GAN --> FN_SS_Studies_accession - FN_SS_Studies_accession --> | 1. | FN_SS_Studies_validate_ena_required_fields - FN_SS_Studies_accession --> | 2. | FN_SS_AccessionService_submit_study_for_user - FN_SS_Studies_accession --> | IF error | FN_SS_Studies_rescue_accession_errors - FN_SS_AccessionService_submit_study_for_user --> | 1. | CF_SS_accession_samples - FN_SS_AccessionService_submit_study_for_user ==> | 2. | FN_SS_AccessionService_submit - - %% Subgraph styling - - %% lemon-chiffon: #fbf8cc - %% champagne-pink: #fde4cf - %% tea-rose-red: #ffcfd2 - %% pink-lavender: #f1c0e8 - %% mauve: #cfbaf0 - %% jordy-blue: #a3c4f3 - %% non-photo-blue: #90dbf4 - %% electric-blue: #8eecf5 - %% aquamarine: #98f5e1 - %% celadon: #b9fbc0 - - classDef default fill:#fafafa,stroke:#333,stroke-width:1px; - - classDef invisible fill:transparent,stroke:transparent; - classDef legendTransparent fill:transparent,stroke:#333,stroke-width:1px; - classDef application fill:#90dbf4; - classDef configuration fill:#fbf8cc; - classDef railsModel fill:#98f5e1; - classDef railsController fill:#b9fbc0; - classDef users fill:#f1c0e8; - classDef userInterface fill:#a3c4f3; - - class InvisibleNodeA,InvisibleNodeB,InvisibleNodeC,InvisibleNodeD invisible; - class Legend legendTransparent; - class Application_Sequencescape,Application_Limber application; - class L_Config,CF_SS_disable_accession_check,CF_SS_accession_samples configuration; - class SS_API,DelayedJob,Samples,Studies,Submissions SequencescapeSection; - class L_Model,MD_SS_Study,MD_SS_AccessionService,MD_SS_Sample,MD_SS_SampleManifest_Uploader,MD_SS_Order,MD_SS_Submission_AccessionBehaviour railsModel; - class L_Controller,CT_SS_Samples,CT_SS_Studies railsController; - class L_User,User_Any users; - class L_Interface,UI_LB_Charge_and_Pass,UI_SS_Sample_GAN,UI_SS_Study_GAN,UI_SS_Study_AAS,UI_SS_Manifest_Upload userInterface; diff --git a/docs/accessioning/accessioning-users.mermaid b/docs/accessioning/accessioning-users.mermaid deleted file mode 100644 index 925158729b..0000000000 --- a/docs/accessioning/accessioning-users.mermaid +++ /dev/null @@ -1,142 +0,0 @@ ---- -title: Accessioning User Graph ---- -%%{ init: { - 'flowchart': { 'curve': 'curvy' }, - 'theme': 'neutral' - } -}%% -flowchart LR - %% Legend - subgraph Legend [Legend] - direction TB - L_User(fa:fa-user User) - L_API(fa:fa-arrow-right-to-bracket API) - L_Interface(fa:fa-computer-mouse User Interface) - L_Model(fa:fa-square-caret-down Model) - L_Controller(fa:fa-arrows-spin Controller) - L_Function(fa:fa-caret-right Function) - L_Resource(fa:fa-file Resource) - - L_User ~~~ L_Interface ~~~ L_API ~~~ L_Resource - L_Controller ~~~ L_Model ~~~ L_Function - - InvisibleNodeA[ ] -..-> | Data flow | InvisibleNodeB[ ] -- Function calls --> InvisibleNodeC[ ] - end - %% End Legend - - %% Nodes - %% Users - User_SeqOps(fa:fa-user SeqOps) - User_Neil(fa:fa-user Neil) - User_SSR(fa:fa-user SSRs) - - %% User Interfaces - UI_LB_Charge_and_Pass(fa:fa-computer-mouse Charge and Pass) - UI_SS_Sample_GAN(fa:fa-computer-mouse Generate Accession Number) - UI_SS_Study_GAN(fa:fa-computer-mouse Generate Accession Number) - UI_SS_Study_AAS(fa:fa-computer-mouse Accession all Samples) - UI_SS_Manifest_Upload(fa:fa-computer-mouse Manifest Upload) - - %% Models - MD_SS_Order(fa:fa-square-caret-down Order) - MD_SS_SampleManifest_Uploader(fa:fa-square-caret-down SampleManifest::Uploader) - - %% Functions - FN_SS_Samples_accession(fa:fa-caret-right accession) - FN_SS_Studies_accession_all_samples(fa:fa-caret-right accession_all_samples) - FN_SS_Studies_accession(fa:fa-caret-right accession) - - %% Other Components - API_SS_OrderResource(fa:fa-arrow-right-to-bracket Order Resource) - - %% Resources - RES_Manifest(fa:fa-file Manifest) - - %% Groupings of nodes - subgraph Providers - User_SeqOps - User_Neil - User_SSR - end - subgraph Application_Limber - UI_LB_Charge_and_Pass - end - subgraph Application_Sequencescape - UI_SS_Manifest_Upload - MD_SS_SampleManifest_Uploader - MD_SS_Order - - subgraph Samples - UI_SS_Sample_GAN - end - subgraph Studies - UI_SS_Study_GAN - UI_SS_Study_AAS - end - subgraph SS_API["SS API"] - API_SS_OrderResource - end - subgraph CT_SS_Studies[fa:fa-arrows-spin Studies Controller] - FN_SS_Studies_accession_all_samples - FN_SS_Studies_accession - end - subgraph CT_SS_Samples[fa:fa-arrows-spin Samples Controller] - FN_SS_Samples_accession - end - end - - %% Edge connections between nodes - Legend ~~~ Providers - - %% Limber-related - User_SeqOps --> UI_LB_Charge_and_Pass - UI_LB_Charge_and_Pass --> API_SS_OrderResource --> MD_SS_Order - - %% Manifest upload - User_SSR --> RES_Manifest -.-> UI_SS_Manifest_Upload --> MD_SS_SampleManifest_Uploader - - %% Sample generate accession number - User_SSR --> UI_SS_Sample_GAN - User_Neil --> UI_SS_Sample_GAN - UI_SS_Sample_GAN --> FN_SS_Samples_accession - - %% Study accession all samples - User_SSR --> UI_SS_Study_AAS - User_Neil --> UI_SS_Study_AAS - UI_SS_Study_AAS --> FN_SS_Studies_accession_all_samples - - %% Study generate accession number - User_Neil --> UI_SS_Study_GAN - UI_SS_Study_GAN --> FN_SS_Studies_accession - - %% Subgraph styling - - %% lemon-chiffon: #fbf8cc - %% champagne-pink: #fde4cf - %% tea-rose-red: #ffcfd2 - %% pink-lavender: #f1c0e8 - %% mauve: #cfbaf0 - %% jordy-blue: #a3c4f3 - %% non-photo-blue: #90dbf4 - %% electric-blue: #8eecf5 - %% aquamarine: #98f5e1 - %% celadon: #b9fbc0 - - classDef default fill:#fafafa,stroke:#333,stroke-width:1px; - - classDef invisible fill:transparent,stroke:transparent; - classDef legendTransparent fill:transparent,stroke:#333,stroke-width:1px; - classDef application fill:#90dbf4; - classDef railsModel fill:#98f5e1; - classDef railsController fill:#b9fbc0; - classDef users fill:#f1c0e8; - classDef userInterface fill:#a3c4f3; - - class InvisibleNodeA,InvisibleNodeB,InvisibleNodeC,InvisibleNodeD invisible; - class Legend legendTransparent; - class Application_Sequencescape,Application_Limber application; - class L_Model,MD_SS_SampleManifest_Uploader,MD_SS_Order railsModel; - class L_Controller,CT_SS_Samples,CT_SS_Studies railsController; - class L_User,User_SeqOps,User_Neil,User_SSR users; - class L_Interface,UI_LB_Charge_and_Pass,UI_SS_Sample_GAN,UI_SS_Study_GAN,UI_SS_Study_AAS,UI_SS_Manifest_Upload userInterface; From 9fe53312c53ab2d9e27051c322423a110db44346 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Tue, 10 Feb 2026 09:31:52 +0000 Subject: [PATCH 74/92] tests: add additional helper specs --- spec/helpers/samples_helper_spec.rb | 47 +++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 spec/helpers/samples_helper_spec.rb diff --git a/spec/helpers/samples_helper_spec.rb b/spec/helpers/samples_helper_spec.rb new file mode 100644 index 0000000000..0cbb901a28 --- /dev/null +++ b/spec/helpers/samples_helper_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe SamplesHelper, type: :helper do + subject { helper.save_text(sample) } + + let(:sample) { create(:sample) } + + before do + allow(helper).to receive(:accessioning_enabled?).and_return(accessioning_enabled) + allow(helper).to receive(:permitted_to_accession?).with(sample).and_return(permitted) + allow(sample).to receive(:should_be_accessioned?).and_return(should_be_accessioned) + end + + context 'when accessioning is enabled, sample should be accessioned, and user is permitted' do + let(:accessioning_enabled) { true } + let(:should_be_accessioned) { true } + let(:permitted) { true } + + it { is_expected.to eq('Save and Accession') } + end + + context 'when accessioning is disabled' do + let(:accessioning_enabled) { false } + let(:should_be_accessioned) { true } + let(:permitted) { true } + + it { is_expected.to eq('Save Sample') } + end + + context 'when sample should not be accessioned' do + let(:accessioning_enabled) { true } + let(:should_be_accessioned) { false } + let(:permitted) { true } + + it { is_expected.to eq('Save Sample') } + end + + context 'when user is not permitted to accession' do + let(:accessioning_enabled) { true } + let(:should_be_accessioned) { true } + let(:permitted) { false } + + it { is_expected.to eq('Save Sample') } + end +end From 3fa9e631ce584ba82bc39b71ffc325acb0519fa2 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Tue, 10 Feb 2026 09:45:36 +0000 Subject: [PATCH 75/92] tests: add additional controller specs --- spec/controllers/studies_controller_spec.rb | 57 +++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/spec/controllers/studies_controller_spec.rb b/spec/controllers/studies_controller_spec.rb index 3ef9f06d88..6ac4bd4257 100644 --- a/spec/controllers/studies_controller_spec.rb +++ b/spec/controllers/studies_controller_spec.rb @@ -350,6 +350,63 @@ end end end + + context 'when the study does not have an accession number' do + let(:study) { create(:managed_study, samples:) } + + it 'does not attempt to accession samples' do + expect(Accession::Submission.client).not_to have_received(:submit_and_fetch_accession_number) + end + + it 'redirects to the study page' do + expect(subject).to redirect_to(study_path(study)) + end + + it 'does not set a flash warning message' do + expect(flash[:warning]).to be_nil + end + + it 'does not set a flash notice message' do + expect(flash[:notice]).to be_nil + end + + it 'sets a flash error message' do + expect(flash[:error]).to eq('Please accession the study before accessioning samples') + end + + it 'does not set a flash info message' do + expect(flash[:info]).to be_nil + end + end + + context 'when a study is not longer accessionable' do + let(:study_metadata) { create(:study_metadata_for_accessioning, study_ebi_accession_number: 'EGA123') } + let(:study) { create(:study, study_metadata:, samples:) } + + it 'does not attempt to accession samples' do + expect(Accession::Submission.client).not_to have_received(:submit_and_fetch_accession_number) + end + + it 'redirects to the study page' do + expect(subject).to redirect_to(study_path(study)) + end + + it 'does not set a flash warning message' do + expect(flash[:warning]).to be_nil + end + + it 'does not set a flash notice message' do + expect(flash[:notice]).to be_nil + end + + it 'sets a flash error message' do + expect(flash[:error]).to eq('Study cannot accession samples, see Study Accessioning tab for details') + end + + it 'does not set a flash info message' do + expect(flash[:info]).to be_nil + end + end end end end From 03e9f4f74b7e279e43e988cfd3a372e3e4015adf Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Tue, 10 Feb 2026 16:30:02 +0000 Subject: [PATCH 76/92] test: remove extraneous lets --- spec/helpers/samples_helper_spec.rb | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/spec/helpers/samples_helper_spec.rb b/spec/helpers/samples_helper_spec.rb index 0cbb901a28..881e25b8df 100644 --- a/spec/helpers/samples_helper_spec.rb +++ b/spec/helpers/samples_helper_spec.rb @@ -7,6 +7,10 @@ let(:sample) { create(:sample) } + let(:accessioning_enabled) { true } + let(:should_be_accessioned) { true } + let(:permitted) { true } + before do allow(helper).to receive(:accessioning_enabled?).and_return(accessioning_enabled) allow(helper).to receive(:permitted_to_accession?).with(sample).and_return(permitted) @@ -14,32 +18,22 @@ end context 'when accessioning is enabled, sample should be accessioned, and user is permitted' do - let(:accessioning_enabled) { true } - let(:should_be_accessioned) { true } - let(:permitted) { true } - it { is_expected.to eq('Save and Accession') } end context 'when accessioning is disabled' do let(:accessioning_enabled) { false } - let(:should_be_accessioned) { true } - let(:permitted) { true } it { is_expected.to eq('Save Sample') } end context 'when sample should not be accessioned' do - let(:accessioning_enabled) { true } let(:should_be_accessioned) { false } - let(:permitted) { true } it { is_expected.to eq('Save Sample') } end context 'when user is not permitted to accession' do - let(:accessioning_enabled) { true } - let(:should_be_accessioned) { true } let(:permitted) { false } it { is_expected.to eq('Save Sample') } From 0464d6fbaccbac2de2ca961553485032bc619d5b Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Tue, 10 Feb 2026 16:36:31 +0000 Subject: [PATCH 77/92] build: more gitignore to root directory --- .gitignore | 4 ++++ docs/accessioning/.gitignore | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) delete mode 100644 docs/accessioning/.gitignore diff --git a/.gitignore b/.gitignore index 1afabaef95..d42ad8f7d2 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,10 @@ doc/* .yardoc/* app/sample_manifest_excel/doc/ +# Ignore generated diagrams from Mermaid +docs/accessioning/**/*.png +docs/accessioning/**/*.svg + # Test files capybara*.html spec/examples.txt diff --git a/docs/accessioning/.gitignore b/docs/accessioning/.gitignore deleted file mode 100644 index 6cd6fa8833..0000000000 --- a/docs/accessioning/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Ignore generated diagrams from Mermaid -*.png -*.svg From 90e32fcf79a9fa8d5d9a716fe473f2c0d09e043e Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Tue, 10 Feb 2026 16:41:06 +0000 Subject: [PATCH 78/92] docs: increase fontsize --- docs/accessioning/accessioning.mermaid | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index 1c952de5d5..87201625d4 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -3,7 +3,8 @@ title: Sequencescape Accessioning Processes --- %%{ init: { 'flowchart': { 'curve': 'curvy' }, - 'theme': 'neutral' + 'theme': 'neutral', + 'themeVariables': { 'fontSize': '18px' } } }%% flowchart LR From 6c0ad46eb7f9675f848465a2e2bdfef8774cb678 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Wed, 11 Feb 2026 12:16:22 +0000 Subject: [PATCH 79/92] fix: add missing safe navigation operators to fix humanize on nil error --- .../studies/information/_study_accession_checklist.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/studies/information/_study_accession_checklist.html.erb b/app/views/studies/information/_study_accession_checklist.html.erb index c5ece5c113..3425e42975 100644 --- a/app/views/studies/information/_study_accession_checklist.html.erb +++ b/app/views/studies/information/_study_accession_checklist.html.erb @@ -25,7 +25,7 @@ <%= checklist_item( condition: !@study.study_metadata.strategy_not_applicable?, - good: "Study release strategy is #{Study::DATA_RELEASE_STRATEGY_OPEN.humanize} or #{Study::DATA_RELEASE_STRATEGY_MANAGED.humanize} #{tag.strong { "(#{@study.study_metadata.data_release_strategy.humanize})" }}".html_safe, + good: "Study release strategy is #{Study::DATA_RELEASE_STRATEGY_OPEN.humanize} or #{Study::DATA_RELEASE_STRATEGY_MANAGED.humanize} #{tag.strong { "(#{@study.study_metadata.data_release_strategy&.humanize})" }}".html_safe, bad: "Study release strategy is <strong>#{Study::DATA_RELEASE_STRATEGY_NOT_APPLICABLE.humanize}</strong>".html_safe, action: link_to('Change release strategy', edit_study_path(@study)), action_permission: can?(:edit, @study), @@ -34,7 +34,7 @@ <%= checklist_item( condition: !@study.study_metadata.never_release?, - good: "Study release timing is <strong>#{@study.study_metadata.data_release_timing.humanize}</strong>".html_safe, + good: "Study release timing is <strong>#{@study.study_metadata.data_release_timing&.humanize}</strong>".html_safe, bad: "Study release timing is <strong>#{Study::DATA_RELEASE_TIMING_NEVER.humanize}</strong>".html_safe, action: link_to('Change release settings', edit_study_path(@study)), action_permission: can?(:edit, @study), From 777f7ce09a4b7cd43eb4a18bdd683af4cc00021b Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Wed, 11 Feb 2026 15:38:22 +0000 Subject: [PATCH 80/92] docs: highlight functions that call validations --- docs/accessioning/accessioning.mermaid | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning/accessioning.mermaid index 87201625d4..e00e2cfd85 100644 --- a/docs/accessioning/accessioning.mermaid +++ b/docs/accessioning/accessioning.mermaid @@ -18,6 +18,7 @@ flowchart LR L_Controller(fa:fa-arrows-spin Controller) L_View(fa:fa-eye View) L_Function(fa:fa-caret-right Function) + L_Validation_Function(fa:fa-caret-right Validation Function) L_Config(fa:fa-screwdriver-wrench Configuration) L_Resource(fa:fa-file Resource) L_Library(fa:fa-book Library) @@ -25,8 +26,8 @@ flowchart LR L_LinkSource[/Link Source\] L_LinkSink[\Link Sink/] - L_Application ~~~ L_External ~~~ L_Action ~~~ L_Resource ~~~ L_Async ~~~ L_LinkSource - L_Config ~~~ L_Controller ~~~ L_Model ~~~ L_View ~~~ L_Function ~~~ L_Library ~~~ L_LinkSink + L_Application ~~~ L_External ~~~ L_Action ~~~ L_Resource ~~~ L_Library ~~~ L_Async ~~~ L_LinkSource + L_Config ~~~ L_Controller ~~~ L_Model ~~~ L_View ~~~ L_Validation_Function ~~~ L_Function ~~~ L_LinkSink InvisibleNodeA[ ] -..-> | Data flow | InvisibleNodeB[ ] -- Function calls --> InvisibleNodeC[ ] == Accessioning function calls ==> InvisibleNodeD[ ] end @@ -264,7 +265,7 @@ flowchart LR classDef link fill:#fde4cf; classDef railsModel fill:#98f5e1; classDef railsController fill:#b9fbc0; - %% classDef users fill:#f1c0e8; + classDef validation fill:#f1c0e8; classDef userInterface fill:#a3c4f3; class InvisibleNodeA,InvisibleNodeB,InvisibleNodeC,InvisibleNodeD invisible; @@ -276,3 +277,4 @@ flowchart LR class L_Controller,CT_Samples,CT_Studies railsController; class L_Action,L_View,UI_LB_Charge_and_Pass,UI_Sample_GAN,UI_Sample_SAA,UI_Study_GAN,UI_Study_AAS,UI_Manifest_Upload,VW_Sample,VW_Study userInterface; class L_LinkSource,L_LinkSink,LK_accessioning_enabled,LK_accessioning_enabled_AAS,LK_accessioning_enabled_Studies_accession,LK_accessioning_enabled_Samples_accession,LK_accessioning_enabled_trigger_accessioning,LK_accessioning_enabled_Accession_perform,LK_accessioning_enabled_Study_View,LK_accessioning_enabled_Sample_View link; + class L_Validation_Function,FN_Accession_Sample_validate,FN_Study_validate_study_for_accessioning validation; From 74fc35b4994079c41e21c2ab6bc3ebcbe4225da4 Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 21:45:34 +0000 Subject: [PATCH 81/92] Update bootsnap to version 1.22.0 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 27cf5c9182..9f143b2adb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -134,7 +134,7 @@ GEM base64 (0.3.0) benchmark (0.5.0) bigdecimal (3.3.1) - bootsnap (1.21.1) + bootsnap (1.22.0) msgpack (~> 1.2) builder (3.3.0) bullet (8.1.0) From b0ef8bb71c7e3d8bc3839b9e77b56d0e9dfceab4 Mon Sep 17 00:00:00 2001 From: yoldas <ay6@sanger.ac.uk> Date: Wed, 11 Feb 2026 23:36:35 +0000 Subject: [PATCH 82/92] Use Ultima tag groups and tags from database for Barcode_Plate_Num and Index_Barcode_Num indexes --- .../sample_sheet_generator.rb | 30 ++++++++++++------- .../sample_sheet_generator_spec.rb | 4 +-- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/app/controllers/ultima_sample_sheet/sample_sheet_generator.rb b/app/controllers/ultima_sample_sheet/sample_sheet_generator.rb index 2bc5b509d3..1722db917e 100644 --- a/app/controllers/ultima_sample_sheet/sample_sheet_generator.rb +++ b/app/controllers/ultima_sample_sheet/sample_sheet_generator.rb @@ -34,6 +34,11 @@ class Generator # rubocop:disable Metrics/ClassLength study_id ].freeze NUM_COLUMS = SAMPLES_HEADERS.size + # The names of the Ultima tag groups names are listed here for consistent + # index numbers for the Barcode_Plate_Num column, i.e. 1 or 2. The number is + # also used for determining the consistent starting index number for the + # Index_Barcode_Num column, i.e. Z0001 or Z097. + ULTIMA_TAG_GROUP_NAMES = ['Ultima P1', 'Ultima P2'].freeze # Initializes the generator with the given batch. # @param batch [UltimaSequencingBatch] the batch to generate sample sheets for @@ -187,28 +192,31 @@ def study_id_for(aliquot) aliquot.study_id.to_s end - # Returns a mapping of tags to their respective 1-based index numbers. - # This sorts the tags by their tag group ID and map ID to ensure consistent ordering. + # Returns a mapping of all Ultima tags to their respective 1-based index + # numbers. This sorts the tags by their tag group ID and map ID to ensure + # consistent ordering. The index numbers run across all Ultima tag groups, + # i.e. the index is 1 for the first tag in the first tag group and 97 for + # the first tag in the second tag group. # @return [Hash{Tag => Integer}] mapping of tags to index numbers def tag_index_map @tag_index_map ||= begin - tags = batch_tag_groups.flat_map { |tg| tg.tags.sort_by(&:map_id) } + tags = ultima_tag_groups.flat_map { |tg| tg.tags.sort_by(&:map_id) } tags.each_with_index.to_h { |tag, i| [tag, i + 1] } end end - # Returns a mapping of tag groups to 1-based index numbers. + # Returns a mapping of all Ultima tag groups to 1-based index numbers. + # This sorts the tag groups by their ID to ensure consistent ordering. # @return [Hash{TagGroup => Integer}] mapping of tag groups to index numbers def tag_group_index_map - @tag_group_index_map ||= batch_tag_groups.each_with_index.to_h { |tg, i| [tg, i + 1] } + @tag_group_index_map ||= ultima_tag_groups.each_with_index.to_h { |tg, i| [tg, i + 1] } end - # Returns all unique tag groups used in the batch. - # This sorts the tag groups by their ID to ensure consistent ordering. - # @return [Array<TagGroup>] the tag groups of the batch's requests - def batch_tag_groups - @batch_tag_groups ||= batch_requests - .flat_map { |request| request.asset.aliquots.map { |aliquot| aliquot.tag.tag_group } }.uniq.sort_by(&:id) + # Returns all unique tag groups used for Ultima sequencing from database. + # The tag groups are sorted by ID to ensure consistent ordering. + # @return [Array<TagGroup>] the tag groups used for Ultima sequencing + def ultima_tag_groups + @ultima_tag_groups ||= TagGroup.where(name: ULTIMA_TAG_GROUP_NAMES).order(:id) end # Returns the requests associated with the batch. diff --git a/spec/controllers/ultima_sample_sheet/sample_sheet_generator_spec.rb b/spec/controllers/ultima_sample_sheet/sample_sheet_generator_spec.rb index a1da05bb06..9cd903902f 100644 --- a/spec/controllers/ultima_sample_sheet/sample_sheet_generator_spec.rb +++ b/spec/controllers/ultima_sample_sheet/sample_sheet_generator_spec.rb @@ -50,8 +50,8 @@ before { create(:ultima_global) } # Eagerly create tag groups and tags to get consistent IDs. - let!(:tag_group1) { create(:tag_group_with_tags, tag_count: 96) } - let!(:tag_group2) { create(:tag_group_with_tags, tag_count: 96) } + let!(:tag_group1) { create(:tag_group_with_tags, tag_count: 96, name: 'Ultima P1') } + let!(:tag_group2) { create(:tag_group_with_tags, tag_count: 96, name: 'Ultima P2') } let(:tag_groups) { [tag_group1, tag_group2] } let(:request_type) { create(:ultima_sequencing) } From 9511631fda3fe6ea203089ff4bea025a690bbdd3 Mon Sep 17 00:00:00 2001 From: yoldas <ay6@sanger.ac.uk> Date: Wed, 11 Feb 2026 23:43:38 +0000 Subject: [PATCH 83/92] Fix typo in comment --- .../ultima_sample_sheet/sample_sheet_generator.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/ultima_sample_sheet/sample_sheet_generator.rb b/app/controllers/ultima_sample_sheet/sample_sheet_generator.rb index 1722db917e..62ebef1be9 100644 --- a/app/controllers/ultima_sample_sheet/sample_sheet_generator.rb +++ b/app/controllers/ultima_sample_sheet/sample_sheet_generator.rb @@ -34,9 +34,9 @@ class Generator # rubocop:disable Metrics/ClassLength study_id ].freeze NUM_COLUMS = SAMPLES_HEADERS.size - # The names of the Ultima tag groups names are listed here for consistent - # index numbers for the Barcode_Plate_Num column, i.e. 1 or 2. The number is - # also used for determining the consistent starting index number for the + # The names of the Ultima tag groups are listed here for consistent index + # numbers for the Barcode_Plate_Num column, i.e. 1 or 2. The number is also + # used for determining the consistent starting index number for the # Index_Barcode_Num column, i.e. Z0001 or Z097. ULTIMA_TAG_GROUP_NAMES = ['Ultima P1', 'Ultima P2'].freeze From dba36c2a3aef337430de9e95264c640eb60d84b3 Mon Sep 17 00:00:00 2001 From: yoldas <ay6@sanger.ac.uk> Date: Thu, 12 Feb 2026 10:58:35 +0000 Subject: [PATCH 84/92] Set the order of Ultima tag groups explicitly --- .../sample_sheet_generator.rb | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/controllers/ultima_sample_sheet/sample_sheet_generator.rb b/app/controllers/ultima_sample_sheet/sample_sheet_generator.rb index 62ebef1be9..d4938e6f03 100644 --- a/app/controllers/ultima_sample_sheet/sample_sheet_generator.rb +++ b/app/controllers/ultima_sample_sheet/sample_sheet_generator.rb @@ -34,11 +34,14 @@ class Generator # rubocop:disable Metrics/ClassLength study_id ].freeze NUM_COLUMS = SAMPLES_HEADERS.size - # The names of the Ultima tag groups are listed here for consistent index - # numbers for the Barcode_Plate_Num column, i.e. 1 or 2. The number is also - # used for determining the consistent starting index number for the + # The names of the Ultima tag groups are mapped to the index numbers for + # the Barcode_Plate_Num column, i.e. 1 or 2. The number is also used for + # determining the consistent starting index number for the # Index_Barcode_Num column, i.e. Z0001 or Z097. - ULTIMA_TAG_GROUP_NAMES = ['Ultima P1', 'Ultima P2'].freeze + ULTIMA_TAG_GROUPS = { + 'Ultima P1' => 1, + 'Ultima P2' => 2 + }.freeze # Initializes the generator with the given batch. # @param batch [UltimaSequencingBatch] the batch to generate sample sheets for @@ -206,17 +209,16 @@ def tag_index_map end # Returns a mapping of all Ultima tag groups to 1-based index numbers. - # This sorts the tag groups by their ID to ensure consistent ordering. + # This indexes the tag groups as given in the ULTIMA_TAG_GROUPS hash. # @return [Hash{TagGroup => Integer}] mapping of tag groups to index numbers def tag_group_index_map - @tag_group_index_map ||= ultima_tag_groups.each_with_index.to_h { |tg, i| [tg, i + 1] } + @tag_group_index_map ||= ultima_tag_groups.index_with { |tg| ULTIMA_TAG_GROUPS[tg.name] } end # Returns all unique tag groups used for Ultima sequencing from database. - # The tag groups are sorted by ID to ensure consistent ordering. # @return [Array<TagGroup>] the tag groups used for Ultima sequencing def ultima_tag_groups - @ultima_tag_groups ||= TagGroup.where(name: ULTIMA_TAG_GROUP_NAMES).order(:id) + @ultima_tag_groups ||= TagGroup.where(name: ULTIMA_TAG_GROUPS.keys) end # Returns the requests associated with the batch. From 750e732c2746c005e73ecf8d40985647a2c6d2e7 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Thu, 12 Feb 2026 11:19:43 +0000 Subject: [PATCH 85/92] refactor: remove unused InvalidData --- app/models/accessionable/base.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/accessionable/base.rb b/app/models/accessionable/base.rb index f2facb0877..52ca6750d3 100644 --- a/app/models/accessionable/base.rb +++ b/app/models/accessionable/base.rb @@ -2,7 +2,6 @@ # Base class to control generating XML for accessioning with the ENA or EGA # @see AccessionService class Accessionable::Base - InvalidData = Class.new(AccessionService::AccessionServiceError) attr_reader :accession_number, :name, :date, :date_short def initialize(accession_number) From 64a2ded77eb649335e9ce5eba42d0dae97337bc0 Mon Sep 17 00:00:00 2001 From: yoldas <ay6@sanger.ac.uk> Date: Thu, 12 Feb 2026 11:19:43 +0000 Subject: [PATCH 86/92] Add test for matching z-index, oligo, and plate number in files --- .../sample_sheet_generator_spec.rb | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/spec/controllers/ultima_sample_sheet/sample_sheet_generator_spec.rb b/spec/controllers/ultima_sample_sheet/sample_sheet_generator_spec.rb index 9cd903902f..923a12a490 100644 --- a/spec/controllers/ultima_sample_sheet/sample_sheet_generator_spec.rb +++ b/spec/controllers/ultima_sample_sheet/sample_sheet_generator_spec.rb @@ -45,13 +45,28 @@ # 6,Sample6,Z0099,TCAG,2,C1,native,6 # # rubocop:enable Layout/LineLength +# rubocop:disable RSpec/MultipleExpectations, RSpec/ExampleLength RSpec.describe UltimaSampleSheet::SampleSheetGenerator do # Eagerly create the global section record. before { create(:ultima_global) } + # First oligo sequences for the two tag groups. + let(:plate1_first_oligo) { 'CAGCTCGAATGCGAT' } + let(:plate2_first_oligo) { 'CAGTCAGTTGCAGAT' } + # Eagerly create tag groups and tags to get consistent IDs. - let!(:tag_group1) { create(:tag_group_with_tags, tag_count: 96, name: 'Ultima P1') } - let!(:tag_group2) { create(:tag_group_with_tags, tag_count: 96, name: 'Ultima P2') } + let!(:tag_group1) do + create(:tag_group_with_tags, tag_count: 96, name: 'Ultima P1').tap do |tg| + # To test Z0001 matching with the oligo sequence. + tg.tags.first.update!(oligo: plate1_first_oligo) + end + end + let!(:tag_group2) do + create(:tag_group_with_tags, tag_count: 96, name: 'Ultima P2').tap do |tg| + # To test Z0097 matching with the oligo sequence. + tg.tags.first.update!(oligo: plate2_first_oligo) + end + end let(:tag_groups) { [tag_group1, tag_group2] } let(:request_type) { create(:ultima_sequencing) } @@ -210,7 +225,7 @@ def map_description(map_id) expect(csv2[1].compact_blank).to eq(["Batch #{batch.id} #{tube2.human_barcode}"]) # Second CSV end - it 'generates global sections' do # rubocop:disable RSpec/MultipleExpectations + it 'generates global sections' do # Test: Add the following hardcoded values, Application(WGS native gDNA), # sequencing_recipe(UG_116cycles_Baseline_1.8.5.2) and analysis_recipe(wgs1) expect(csv1[3].compact_blank).to eq(generator.class::GLOBAL_TITLE) @@ -219,11 +234,24 @@ def map_description(map_id) expect(csv1[6].compact_blank).to eq([]) end - it 'generates samples sections' do # rubocop:disable RSpec/MultipleExpectations + it 'generates samples sections' do expect(csv1[7].compact_blank).to eq(generator.class::SAMPLES_TITLE) expect(csv1[8].compact_blank).to eq(generator.class::SAMPLES_HEADERS) expect(csv1[9..]).to eq(csv1_samples) # First CSV expect(csv2[9..]).to eq(csv2_samples) # Second CSV end + + it 'matches the z-indexes, oligo sequences, and plate numbers' do + # First CSV + expect(csv1[9][2]).to eq('Z0001') # Index_Barcode_Num + expect(csv1[9][3]).to eq(plate1_first_oligo) # Index_Barcode_Sequence + expect(csv1[9][4]).to eq('1') # Barcode_Plate_Num + + # Second CSV + expect(csv2[9][2]).to eq('Z0097') # Index_Barcode_Num + expect(csv2[9][3]).to eq(plate2_first_oligo) # Index_Barcode_Sequence + expect(csv2[9][4]).to eq('2') # Barcode_Plate_Num + end end end +# rubocop:enable RSpec/MultipleExpectations, RSpec/ExampleLength From 168af980f76a754093bb0c46d92c4abb89cf7461 Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 19:00:21 +0000 Subject: [PATCH 87/92] Update knapsack_pro to version 9.2.2 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 27cf5c9182..aa1032251f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -289,7 +289,7 @@ GEM railties (>= 4.1) jsonapi-resources-matchers (1.0.0) jsonapi-resources (>= 0.9.0) - knapsack_pro (9.2.1) + knapsack_pro (9.2.2) rake language_server-protocol (3.17.0.5) launchy (3.1.1) From b88bcc3c68777efd5e7a99ed9887ee5933e10983 Mon Sep 17 00:00:00 2001 From: yoldas <ay6@sanger.ac.uk> Date: Thu, 12 Feb 2026 23:52:42 +0000 Subject: [PATCH 88/92] Sort iteration of aliquots to avoid row shuffling --- app/controllers/ultima_sample_sheet/sample_sheet_generator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/ultima_sample_sheet/sample_sheet_generator.rb b/app/controllers/ultima_sample_sheet/sample_sheet_generator.rb index d4938e6f03..212b3738c4 100644 --- a/app/controllers/ultima_sample_sheet/sample_sheet_generator.rb +++ b/app/controllers/ultima_sample_sheet/sample_sheet_generator.rb @@ -123,7 +123,7 @@ def add_global_section(csv, _request) def add_samples_section(csv, request) csv << pad(SAMPLES_TITLE) csv << pad(SAMPLES_HEADERS) - request.asset.aliquots.each do |aliquot| + request.asset.aliquots.sort_by(&:id).each do |aliquot| csv << [ sample_id_for(aliquot), library_name_for(aliquot), From 6a3ed59de7b90cdafb0cf99501d52cb84b74ff7b Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Fri, 13 Feb 2026 08:59:00 +0000 Subject: [PATCH 89/92] docs: more accession diagram up a level --- docs/{accessioning => }/accessioning.mermaid | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{accessioning => }/accessioning.mermaid (100%) diff --git a/docs/accessioning/accessioning.mermaid b/docs/accessioning.mermaid similarity index 100% rename from docs/accessioning/accessioning.mermaid rename to docs/accessioning.mermaid From f535a92b7b4d8b0551a2855d1900070b1c973fe6 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Fri, 13 Feb 2026 12:16:32 +0000 Subject: [PATCH 90/92] fix: encapsulate samples not accessioned text --- app/helpers/samples_helper.rb | 6 ++ .../sdb/sample_manifests/_samples.html.erb | 2 +- .../information/_accession_statuses.html.erb | 2 +- spec/helpers/samples_helper_spec.rb | 76 +++++++++++++------ 4 files changed, 61 insertions(+), 25 deletions(-) diff --git a/app/helpers/samples_helper.rb b/app/helpers/samples_helper.rb index 6413215b32..bdb2cf24cd 100644 --- a/app/helpers/samples_helper.rb +++ b/app/helpers/samples_helper.rb @@ -10,4 +10,10 @@ def save_text(sample) 'Save Sample' end + + def samples_not_accessioned(samples) + return 'All samples accessioned' if samples.all?(&:accession_number?) + + "#{samples.count { |sample| !sample.accession_number? }} samples not accessioned" + end end diff --git a/app/views/sdb/sample_manifests/_samples.html.erb b/app/views/sdb/sample_manifests/_samples.html.erb index 5dc50d480c..f37d927413 100644 --- a/app/views/sdb/sample_manifests/_samples.html.erb +++ b/app/views/sdb/sample_manifests/_samples.html.erb @@ -5,7 +5,7 @@ <%= tag.div(page_summary(samples), class:'col-md-4') %> <%= tag.div(pagination(samples), class: 'col-md-4 d-flex justify-content-center mb--3') %> <%= tag.div(class: 'col-md-4 d-flex justify-content-end') do %> - <%= @sample_manifest.samples.select { |sample| !sample.accession_number? }.count %> samples not accessioned + <%= samples_not_accessioned(@sample_manifest.samples) %> <% end %> <% end %> diff --git a/app/views/studies/information/_accession_statuses.html.erb b/app/views/studies/information/_accession_statuses.html.erb index cccbf5536e..3312f19b0b 100644 --- a/app/views/studies/information/_accession_statuses.html.erb +++ b/app/views/studies/information/_accession_statuses.html.erb @@ -7,7 +7,7 @@ <%# Aggregation summary for the top right %> <% content_for :aggregation do %> - <%= @study.samples.select { |sample| !sample.accession_number? }.count %> samples not accessioned + <%= samples_not_accessioned(@study.samples) %> <% end %> <%# Table contents %> diff --git a/spec/helpers/samples_helper_spec.rb b/spec/helpers/samples_helper_spec.rb index 881e25b8df..bdf5d7de3b 100644 --- a/spec/helpers/samples_helper_spec.rb +++ b/spec/helpers/samples_helper_spec.rb @@ -3,39 +3,69 @@ require 'rails_helper' RSpec.describe SamplesHelper, type: :helper do - subject { helper.save_text(sample) } + describe '#save_text' do + subject { helper.save_text(sample) } - let(:sample) { create(:sample) } + let(:sample) { create(:sample) } - let(:accessioning_enabled) { true } - let(:should_be_accessioned) { true } - let(:permitted) { true } + let(:accessioning_enabled) { true } + let(:should_be_accessioned) { true } + let(:permitted) { true } - before do - allow(helper).to receive(:accessioning_enabled?).and_return(accessioning_enabled) - allow(helper).to receive(:permitted_to_accession?).with(sample).and_return(permitted) - allow(sample).to receive(:should_be_accessioned?).and_return(should_be_accessioned) - end + before do + allow(helper).to receive(:accessioning_enabled?).and_return(accessioning_enabled) + allow(helper).to receive(:permitted_to_accession?).with(sample).and_return(permitted) + allow(sample).to receive(:should_be_accessioned?).and_return(should_be_accessioned) + end - context 'when accessioning is enabled, sample should be accessioned, and user is permitted' do - it { is_expected.to eq('Save and Accession') } - end + context 'when accessioning is enabled, sample should be accessioned, and user is permitted' do + it { is_expected.to eq('Save and Accession') } + end - context 'when accessioning is disabled' do - let(:accessioning_enabled) { false } + context 'when accessioning is disabled' do + let(:accessioning_enabled) { false } - it { is_expected.to eq('Save Sample') } - end + it { is_expected.to eq('Save Sample') } + end + + context 'when sample should not be accessioned' do + let(:should_be_accessioned) { false } + + it { is_expected.to eq('Save Sample') } + end - context 'when sample should not be accessioned' do - let(:should_be_accessioned) { false } + context 'when user is not permitted to accession' do + let(:permitted) { false } - it { is_expected.to eq('Save Sample') } + it { is_expected.to eq('Save Sample') } + end end - context 'when user is not permitted to accession' do - let(:permitted) { false } + describe '#samples_not_accessioned' do + subject { helper.samples_not_accessioned(samples) } + + context 'when all samples are accessioned' do + let(:samples) { build_list(:accessioned_sample, 3) } + + it { is_expected.to eq('All samples accessioned') } + end + + context 'when some samples are not accessioned' do + let(:samples) do + [ + build(:accessioned_sample), + build(:sample), + build(:sample) + ] + end + + it { is_expected.to eq('2 samples not accessioned') } + end + + context 'when no samples are accessioned' do + let(:samples) { build_list(:sample, 3) } - it { is_expected.to eq('Save Sample') } + it { is_expected.to eq('3 samples not accessioned') } + end end end From cb0c98f7502eccc31729b7160627f40343f3d321 Mon Sep 17 00:00:00 2001 From: Stephen Hulme <sh51@sanger.ac.uk> Date: Fri, 13 Feb 2026 12:29:59 +0000 Subject: [PATCH 91/92] fix: handle singular case and no samples --- app/helpers/samples_helper.rb | 4 +++- spec/helpers/samples_helper_spec.rb | 20 +++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/app/helpers/samples_helper.rb b/app/helpers/samples_helper.rb index bdb2cf24cd..268545223d 100644 --- a/app/helpers/samples_helper.rb +++ b/app/helpers/samples_helper.rb @@ -12,8 +12,10 @@ def save_text(sample) end def samples_not_accessioned(samples) + return 'No samples accessioned' if samples.empty? || samples.none?(&:accession_number?) return 'All samples accessioned' if samples.all?(&:accession_number?) - "#{samples.count { |sample| !sample.accession_number? }} samples not accessioned" + count = samples.count { |sample| !sample.accession_number? } + "#{pluralize(count, 'sample')} not accessioned" end end diff --git a/spec/helpers/samples_helper_spec.rb b/spec/helpers/samples_helper_spec.rb index bdf5d7de3b..fd74b15655 100644 --- a/spec/helpers/samples_helper_spec.rb +++ b/spec/helpers/samples_helper_spec.rb @@ -60,12 +60,30 @@ end it { is_expected.to eq('2 samples not accessioned') } + + context 'when only one sample is not accessioned' do + let(:samples) do + [ + build(:accessioned_sample), + build(:accessioned_sample), + build(:sample) + ] + end + + it { is_expected.to eq('1 sample not accessioned') } + end end context 'when no samples are accessioned' do let(:samples) { build_list(:sample, 3) } - it { is_expected.to eq('3 samples not accessioned') } + it { is_expected.to eq('No samples accessioned') } + end + + context 'when there are no samples' do + let(:samples) { [] } + + it { is_expected.to eq('No samples accessioned') } end end end From f097500ed4a968629945a11ac1536c0d577298cc Mon Sep 17 00:00:00 2001 From: yoldas <ay6@sanger.ac.uk> Date: Mon, 16 Feb 2026 11:10:35 +0000 Subject: [PATCH 92/92] Update .release-version to 14.87.0 --- .release-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.release-version b/.release-version index 8da807562f..de2abfbc75 100644 --- a/.release-version +++ b/.release-version @@ -1 +1 @@ -14.86.0 +14.87.0