From bb2d0ea22a2c86001ccc28d8acc2319ca7e32b71 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Mon, 15 Jun 2026 05:51:36 -0600 Subject: [PATCH 1/3] Improve Save Template validation when no fields are selected (#1148) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Rationale Submitting the EHR "Save As Template" dialog with every field unchecked threw an uncaught `TypeError: Cannot read properties of undefined (reading 'length')`. The checkboxgroup's `getValue().fields` is `undefined` when nothing is selected, so reading `.length` crashed `onSubmit` — the user got a silent failure (the template was never saved) plus a console error, instead of any guidance. ## Related Pull Requests None. ## Changes - Guard the selected-field list before reading `.length`, so an empty selection no longer throws. - Evaluate the record-selector `'none'` case before the field check, so a section the user intends to save is distinguished from one explicitly excluded. - When no fields are selected, show a clear validation message ("At least one field is required and none are selected. Note: The field can be blank.") instead of the generic "No records selected" alert. --- ehr/resources/web/ehr/window/SaveTemplateWindow.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ehr/resources/web/ehr/window/SaveTemplateWindow.js b/ehr/resources/web/ehr/window/SaveTemplateWindow.js index 63d222e80..e5e1fc938 100644 --- a/ehr/resources/web/ehr/window/SaveTemplateWindow.js +++ b/ehr/resources/web/ehr/window/SaveTemplateWindow.js @@ -202,21 +202,25 @@ Ext4.define('EHR.window.SaveTemplateWindow', { var tn = this.down('#templateName').getValue(); var rows = []; + var noFieldsSelected = false; this.down('#theForm').items.each(function(tab){ var radioGroup = tab.down('#recordSelector'); var selections = radioGroup.getValue()[radioGroup.down('[name]').name]; + + if (selections == 'none') + return; + var fields = tab.down('#fieldSelector').getValue().fields; - if (!fields.length) + if (!fields || !fields.length){ + noFieldsSelected = true; return; + } if (!(fields instanceof Array)) // single elements aren't wrapped in an array fields = [fields]; - if (selections == 'none') - return; - var store = Ext4.StoreMgr.get(tab.storeId); var records = []; @@ -249,7 +253,7 @@ Ext4.define('EHR.window.SaveTemplateWindow', { if (!rows.length){ Ext4.Msg.hide(); - Ext4.Msg.alert('Error', "No records selected"); + Ext4.Msg.alert('Error', noFieldsSelected ? "At least one field is required and none are selected. Note: The field can be blank." : "No records selected"); return; } From 7898b18e82df94aae9b53bf90b68c23ab74e5117 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Mon, 15 Jun 2026 05:53:55 -0600 Subject: [PATCH 2/3] Fall back to unaligned pedigree plot when kinship2 alignment fails (#1147) ## Rationale kinship2's QP-based alignment (align=TRUE) can fail with "constraints are inconsistent, no solution!" on deep or looped pedigrees, which aborts the entire Pedigree report so no image renders at all. ## Related Pull Requests None. ## Changes - Wrap the kinship2 `plot()` call in the Pedigree report in `tryCatch`, retrying once with `align=FALSE` when the alignment error is detected so the report still renders. Any other error is re-raised unchanged. --- .../reports/schemas/study/Pedigree/Pedigree.r | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ehr/resources/reports/schemas/study/Pedigree/Pedigree.r b/ehr/resources/reports/schemas/study/Pedigree/Pedigree.r index 24259fdfa..684a7d708 100644 --- a/ehr/resources/reports/schemas/study/Pedigree/Pedigree.r +++ b/ehr/resources/reports/schemas/study/Pedigree/Pedigree.r @@ -300,7 +300,19 @@ if ((nrow(labkey.data) == 0) | (all(is.na(labkey.data$dam)) & all(is.na(labkey.d png(filename="${imgout:png_pedigree}", width = plotWidth, height=plotHeight); par(xpd=TRUE); - plot(ptemp, align=T, width=15, symbolsize=symSize, cex=cexSize, col=fixedPed$colors, mar=c(4.1,3.5,4.1,3.8)) + # kinship2's QP-based alignment (align=TRUE) can fail with "constraints are inconsistent, + # no solution!" on deep/looped pedigrees. Fall back to the unaligned layout in that case so + # the report still renders rather than aborting. + tryCatch( + plot(ptemp, align=TRUE, width=15, symbolsize=symSize, cex=cexSize, col=fixedPed$colors, mar=c(4.1,3.5,4.1,3.8)), + error = function(e) { + # Only the QP alignment failure is recoverable by dropping alignment; re-raise anything else. + if (!grepl("constraints are inconsistent", conditionMessage(e), fixed = TRUE)) + stop(e) + message("Pedigree alignment failed (", conditionMessage(e), "); retrying with align=FALSE.") + plot(ptemp, align=FALSE, width=15, symbolsize=symSize, cex=cexSize, col=fixedPed$colors, mar=c(4.1,3.5,4.1,3.8)) + } + ) mtext("Unknown animals are marked with xxs ## or xxd ## where ## is a randomly generated unique number. This identifier is used only in this plot and is re-generated each time the plot is rendered.", side = 1, font = 3, cex = 0.95) leg.txt <- c("Male", "Female", "Deceased") From c3a47052dca2b5ee384748bbd10f7d4005a1a006 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Tue, 16 Jun 2026 09:01:44 -0600 Subject: [PATCH 3/3] Bulk edit support for clinical observations: category-dependent Observation/Score editor (#1145) ## Rationale The bulk edit dialog for the clinical observations grid offered only a plain text field for Observation/Score regardless of the selected Category, while the grid's cell editor adapts that field to the category's configured editor. This makes the bulk edit dialog offer the same category-dependent editor as the grid, so the data type and options match. ## Related Pull Requests - https://github.com/LabKey/nircEHRModules/pull/706 ## Changes - New `EHR.plugin.ClinicalObservationsBulkEdit` plugin that rebuilds the Observation/Score field from the selected category's `editorconfig`, preserving the field's enable/disable toggle state and re-registering it with the databind plugin. - `EHR.panel.BulkEditPanel` accepts panel plugins contributed via `formConfig.bulkEditPlugins`, keeping the panel itself free of observation-specific logic; the label click-to-toggle behavior is extracted into a reusable `addLabelToggle` method. - `EHR.grid.ClinicalObservationGridPanel` registers the new plugin through its `formConfig`. - The shared observation types store now tracks `hasLoadedOnce` so consumers can distinguish a pending initial load from an empty result. --- ehr/resources/web/ehr/DataEntryUtils.js | 12 +- .../web/ehr/ehr_ext4_dataEntry.lib.xml | 1 + .../ehr/grid/ClinicalObservationGridPanel.js | 10 ++ ehr/resources/web/ehr/panel/BulkEditPanel.js | 60 ++++++--- .../plugin/ClinicalObservationsBulkEdit.js | 121 ++++++++++++++++++ 5 files changed, 182 insertions(+), 22 deletions(-) create mode 100644 ehr/resources/web/ehr/plugin/ClinicalObservationsBulkEdit.js diff --git a/ehr/resources/web/ehr/DataEntryUtils.js b/ehr/resources/web/ehr/DataEntryUtils.js index e218991d3..3c3fa72cb 100644 --- a/ehr/resources/web/ehr/DataEntryUtils.js +++ b/ehr/resources/web/ehr/DataEntryUtils.js @@ -801,7 +801,17 @@ EHR.DataEntryUtils = new function(){ schemaName: 'ehr', queryName: 'observation_types', columns: 'value,editorconfig', - autoLoad: true + autoLoad: true, + listeners: { + // unlike EHR.data.DataEntryClientStore, LABKEY.ext4.data.Store has no hasLoaded(); + // consumers need to distinguish a pending initial load from one that returned no rows + load: { + single: true, + fn: function(store){ + store.hasLoadedOnce = true; + } + } + } }); return EHR._observationTypesStore; diff --git a/ehr/resources/web/ehr/ehr_ext4_dataEntry.lib.xml b/ehr/resources/web/ehr/ehr_ext4_dataEntry.lib.xml index 60557b71e..edaa925ac 100644 --- a/ehr/resources/web/ehr/ehr_ext4_dataEntry.lib.xml +++ b/ehr/resources/web/ehr/ehr_ext4_dataEntry.lib.xml @@ -21,6 +21,7 @@