From 9c286cb2e535628cc020b513d594c311840860de Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Fri, 28 Feb 2025 12:03:13 -0800 Subject: [PATCH 1/5] Add frequency.meaning to formulary store (#897) --- ehr/resources/web/ehr/DataEntryUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ehr/resources/web/ehr/DataEntryUtils.js b/ehr/resources/web/ehr/DataEntryUtils.js index 99ff5eea7..7833a71af 100644 --- a/ehr/resources/web/ehr/DataEntryUtils.js +++ b/ehr/resources/web/ehr/DataEntryUtils.js @@ -728,7 +728,7 @@ EHR.DataEntryUtils = new function(){ type: 'labkey-store', schemaName: 'ehr_lookups', queryName: 'drug_defaults', - columns: 'code,code/meaning,dosage,dosage_units,concentration,conc_units,amount,amount_units,amount_rounding,volume,vol_units,volume_rounding,route,frequency,duration,remark,offset', + columns: 'code,code/meaning,dosage,dosage_units,concentration,conc_units,amount,amount_units,amount_rounding,volume,vol_units,volume_rounding,route,frequency,frequency/meaning,duration,remark,offset', sort: 'code', storeId: storeId, autoLoad: true, From 48ab18dc84d1ed037bbf48f6133c863e3eaf34b2 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Sun, 2 Mar 2025 13:28:15 -0800 Subject: [PATCH 2/5] Create CODEOWNERS (#898) --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..623678e23 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +@labkey-martyp \ No newline at end of file From b5a5ebc82af8670e8a85cc5393cbe34bba0a8ed1 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Mon, 3 Mar 2025 13:35:29 -0800 Subject: [PATCH 3/5] Formulary rounding fixes (#899) * Fix conditional when amount rounding should be done * Update rounding to nearest function to handle floating point precision issues. * Allow greater precision in rounding field --- ehr/resources/web/ehr/DataEntryUtils.js | 2 +- ehr/resources/web/ehr/utils.js | 16 +++------------- ehr/resources/web/ehr/window/DrugAmountWindow.js | 3 ++- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/ehr/resources/web/ehr/DataEntryUtils.js b/ehr/resources/web/ehr/DataEntryUtils.js index 7833a71af..b3219d0f7 100644 --- a/ehr/resources/web/ehr/DataEntryUtils.js +++ b/ehr/resources/web/ehr/DataEntryUtils.js @@ -874,7 +874,7 @@ EHR.DataEntryUtils = new function(){ } } - if (!amount && Ext4.isEmpty(rounding)){ + if (amount && !Ext4.isEmpty(rounding)){ amount = EHR.Utils.roundToNearest(amount, rounding); } diff --git a/ehr/resources/web/ehr/utils.js b/ehr/resources/web/ehr/utils.js index aa0c3926b..580e6909e 100644 --- a/ehr/resources/web/ehr/utils.js +++ b/ehr/resources/web/ehr/utils.js @@ -497,19 +497,9 @@ EHR.Utils = new function(){ return val; } - var remainder = val % round; - var remainderPct = remainder / round; - var base = Math.floor(val / round); - if (remainder === 0){ - return val; - } - //note: JS seems to handle division poorly in situations like 4 / 0.1 - else if (remainderPct < 0.5 || remainderPct > 0.9999){ - return (base * round); - } - else { - return (base * round) + round; - } + let rounded = Math.round(val / round) * round; + let precision = (round.toString().split('.')[1] || "").length; // Get decimal places in `round` + return rounded.toFixed(precision); // toFixed handles floating point precision issues }, editUIButtonCore: function(schemaName, queryName, dataRegionName, paramMap, copyFilters, params){ diff --git a/ehr/resources/web/ehr/window/DrugAmountWindow.js b/ehr/resources/web/ehr/window/DrugAmountWindow.js index 852ceb509..1fa08fb93 100644 --- a/ehr/resources/web/ehr/window/DrugAmountWindow.js +++ b/ehr/resources/web/ehr/window/DrugAmountWindow.js @@ -394,7 +394,8 @@ Ext4.define('EHR.window.DrugAmountWindow', { found = true; editor = { xtype: 'ldk-numberfield', - fieldName: fieldName + fieldName: fieldName, + decimalPrecision: 6 } } From e8a2d582da97f0a1f7189ea13131de740837e2d3 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Wed, 5 Mar 2025 19:37:24 -0800 Subject: [PATCH 4/5] EHR Action Updates (#887) * Check if TableInfo has a QUS in UpdateQueryAction before providing links to update. * Only allow option to reload 'All' if a manifest is defined in a URL parameter * Some cleanup --- .../ehr/demographics/HousingDemographicsProvider.java | 2 -- ehr/src/org/labkey/ehr/EHRController.java | 11 +++++++++-- .../org/labkey/ehr/table/DefaultEHRCustomizer.java | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ehr/api-src/org/labkey/api/ehr/demographics/HousingDemographicsProvider.java b/ehr/api-src/org/labkey/api/ehr/demographics/HousingDemographicsProvider.java index aa51a0be5..a9649c35e 100644 --- a/ehr/api-src/org/labkey/api/ehr/demographics/HousingDemographicsProvider.java +++ b/ehr/api-src/org/labkey/api/ehr/demographics/HousingDemographicsProvider.java @@ -47,8 +47,6 @@ protected Collection getFieldKeys() keys.add(FieldKey.fromString("date")); keys.add(FieldKey.fromString("cond")); keys.add(FieldKey.fromString("reason")); - - //cnprc keys.add(FieldKey.fromString("location")); return keys; diff --git a/ehr/src/org/labkey/ehr/EHRController.java b/ehr/src/org/labkey/ehr/EHRController.java index 6ea62c1f4..c7805f3d2 100644 --- a/ehr/src/org/labkey/ehr/EHRController.java +++ b/ehr/src/org/labkey/ehr/EHRController.java @@ -383,7 +383,7 @@ public ModelAndView getView(EHRQueryForm form, BindException errors) ActionURL url = getViewContext().getActionURL().clone(); - if (keyField != null) + if (keyField != null && ti.getUpdateService() != null) { String detailsStr; String importStr; @@ -1427,7 +1427,14 @@ public Object execute(PopulateLookupsForm form, BindException errors) throws Exc return null; List lookups = new ArrayList<>(); - lookups.add("All"); + + // This is too dangerous to leave in production so only include it if a manifest is provided as a URL parameter + // such as in automated testing + if (form.getManifest() != null) + { + lookups.add("All"); + } + lookups.add("lookup_sets"); BufferedReader reader = Readers.getReader(_lookupsManifest.getInputStream()); diff --git a/ehr/src/org/labkey/ehr/table/DefaultEHRCustomizer.java b/ehr/src/org/labkey/ehr/table/DefaultEHRCustomizer.java index b15e8d90b..a61293b5b 100644 --- a/ehr/src/org/labkey/ehr/table/DefaultEHRCustomizer.java +++ b/ehr/src/org/labkey/ehr/table/DefaultEHRCustomizer.java @@ -1216,7 +1216,7 @@ private void customizeAnimalTable(AbstractTableInfo ds) ds.addColumn(col20); var col8 = getWrappedIdCol(us, ds, "CageClass", "demographicsCageClass"); - col8.setLabel("Required Case Size"); + col8.setLabel("Required Cage Size"); col8.setDescription("Calculates the cage size necessary for this animal, based on weight using The Guide requirements"); ds.addColumn(col8); From 4fe66730b07820bc88516424f925a7a6766a2c7d Mon Sep 17 00:00:00 2001 From: Sweta Jewargikar Date: Thu, 13 Mar 2025 10:13:01 -0700 Subject: [PATCH 5/5] SND Query Provisioned snapshot automation test (#901) --- .../org/labkey/test/tests/snd/SNDTest.java | 110 ++++++++++++++++-- snd/webapp/snd/test/data.js | 2 +- 2 files changed, 99 insertions(+), 13 deletions(-) diff --git a/snd/test/src/org/labkey/test/tests/snd/SNDTest.java b/snd/test/src/org/labkey/test/tests/snd/SNDTest.java index 1e01a9453..824db2a20 100644 --- a/snd/test/src/org/labkey/test/tests/snd/SNDTest.java +++ b/snd/test/src/org/labkey/test/tests/snd/SNDTest.java @@ -16,6 +16,7 @@ package org.labkey.test.tests.snd; +import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; @@ -48,6 +49,7 @@ import org.labkey.test.components.snd.PackageViewerResult; import org.labkey.test.components.snd.ProjectViewerResult; import org.labkey.test.components.snd.SuperPackageRow; +import org.labkey.test.pages.query.SourceQueryPage; import org.labkey.test.pages.snd.EditCategoriesPage; import org.labkey.test.pages.snd.EditPackagePage; import org.labkey.test.pages.snd.EditProjectPage; @@ -56,7 +58,10 @@ import org.labkey.test.util.ApiPermissionsHelper; import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.Maps; +import org.labkey.test.util.OptionalFeatureHelper; +import org.labkey.test.util.PortalHelper; import org.labkey.test.util.SqlserverOnlyTest; +import org.labkey.test.util.StudyHelper; import org.labkey.test.util.core.webdav.WebDavUploadHelper; import org.openqa.selenium.WebElement; @@ -107,6 +112,7 @@ public class SNDTest extends BaseWebDriverTest implements SqlserverOnlyTest private static final int TEST_CATEGORY_ID2 = 51; private static final int TEST_CATEGORY_ID3 = 52; private static final int TEST_CATEGORY_ID4 = 53; + private static final int TEST_CATEGORY_ID5 = 54; private static final int TEST_SUPER_PKG_START_ID1 = 130; private static final int TEST_SUPER_PKG_START_ID2 = 140; private static final int TEST_SUPER_PKG_START_ID3 = 150; @@ -141,6 +147,8 @@ public class SNDTest extends BaseWebDriverTest implements SqlserverOnlyTest private static final String UITEST_PROJECT_SUBPKG2 = "Vet Comment"; private static final String UITEST_PROJECT_SUBPKG3 = "Ketamine Sedation"; + private static final String STUDY_NAME = "Query provisioned snapshot test"; + private static final String CREATEDOMAINSAPI ="LABKEY.Domain.create({\n" + " domainGroup: 'test', \n" + " domainKind: 'SND', \n" + @@ -553,7 +561,12 @@ private static String getPackageWithId(String packageId) " 'Description': 'Weight', \n"+ " 'Active': true, \n"+ " 'Comment': 'This is a weight' \n"+ - " },{ \n"+ + " },{ \n" + + " 'CategoryId': " + TEST_CATEGORY_ID5 + " ,\n" + + " 'Description': 'Sodium', \n" + + " 'Active': true, \n" + + " 'Comment': 'This is Sodium' \n" + + " },{ \n"+ " 'CategoryId': " + TEST_CATEGORY_ID4 + ",\n"+ " 'Description': 'Vitals', \n"+ " 'Active': true, \n"+ @@ -870,8 +883,7 @@ public void checkLinks() @BeforeClass public static void setupProject() { - SNDTest init = (SNDTest) getCurrentTest(); - + SNDTest init = getCurrentTest(); init.doSetup(); } @@ -879,7 +891,8 @@ private void doSetup() { _containerHelper.createProject(getProjectName(), "Collaboration"); goToProjectHome(); - _containerHelper.enableModules(Arrays.asList("SND")); + _containerHelper.enableModules(Arrays.asList("SND", "Study")); + _containerHelper.createSubfolder(getProjectName(), getProjectName(), TEST1SUBFOLDER, "Collaboration", new String[]{"SND"}); setupTest1Project(); @@ -2089,8 +2102,7 @@ public void reviseProjectViaUI() throws Exception assertFalse("Unassigned package found assigned.", viewPage.isAssignedPackagePresent(UITEST_PROJECT_SUBPKG2)); } - @Test - public void verifyTestFrameworkAPITests() + private void runTestsInAPIFrameWork() { log("Launching the Testing framework"); goToProjectHome(); @@ -2101,11 +2113,10 @@ public void verifyTestFrameworkAPITests() clickButton("Run tests", 0); waitForText("Total tests:", 1, WAIT_FOR_PAGE); - log("Verifying no test failed"); assertTextPresent("Complete","Failed tests: 0"); } - private String getPerimissionTableValue(int row, int col) + private String getPermissionTableValue(int row, int col) { List els = ((Locator.XPathLocator)getSimpleTableCell(Locator.id("category-security"), row, col)).child("div").child("a").child("input").findElements(getDriver()); if (els.size() > 0) @@ -2157,7 +2168,7 @@ public void categoryPermissionsUI() throws Exception { if (categories.contains(getTableCellText(Locator.id("category-security"), i, 0))) { - value = getPerimissionTableValue(i, 1); + value = getPermissionTableValue(i, 1); assertNotNull(value); assertTrue(value.equals("None")); categoryRows.add(i); @@ -2169,7 +2180,7 @@ public void categoryPermissionsUI() throws Exception for (Integer r : categoryRows) { - value = getPerimissionTableValue(r, 1); + value = getPermissionTableValue(r, 1); assertNotNull(value); assertTrue(value.equals("SND Reader")); } @@ -2179,7 +2190,7 @@ public void categoryPermissionsUI() throws Exception for (int k = 0; k < categoryRows.size(); k++) { - value = getPerimissionTableValue(categoryRows.get(k), 1); + value = getPermissionTableValue(categoryRows.get(k), 1); assertNotNull(value); assertTrue(value.equals("None")); click(getSimpleTableCell(Locator.id("category-security"), categoryRows.get(k), 1)); @@ -2190,13 +2201,88 @@ public void categoryPermissionsUI() throws Exception for (int j = 0; j < categoryRows.size(); j++) { - value = getPerimissionTableValue(categoryRows.get(j), 1); + value = getPermissionTableValue(categoryRows.get(j), 1); assertNotNull(value); assertTrue(value.equals(permissions.get(j))); } } + @Test + public void testQueryProvisionedSnapshot() + { + String sourceQueryName = "SND: Source query"; + String snapshotName = sourceQueryName + " Snapshot"; + String sourceQuery = "SELECT\n" + + "lsid AS _key,\n" + + "SubjectId AS participantid,\n" + + "date,\n" + + "SequenceNum,\n" + + "qcstate,\n" + + "lsid,\n" + + "amount,\n" + + "units,\n" + + "kit_type\n" + + "FROM SND.Categories.Sodium\n"; + + String updatedSourceQuery = sourceQuery.replace( + "amount,", + "'8976' AS amount,"); + + log("Enable Allow query based dataset snapshots"); + OptionalFeatureHelper.enableOptionalFeature(createDefaultConnection(), "queryBasedDatasets"); + + log("Run the tests from the framework for set up"); + runTestsInAPIFrameWork(); + + log("Enable study module and create continuous study"); + goToProjectHome(); + goToManageStudy(); + _studyHelper.startCreateStudy() + .setTimepointType(StudyHelper.TimepointType.CONTINUOUS) + .createStudy(); + + log("Adding dataset webpart"); + goToProjectHome(); + PortalHelper _portalHelper = new PortalHelper(getDriver()); + _portalHelper.addBodyWebPart("Datasets"); + + log("Create source query from study"); + goToSchemaBrowser(); + SourceQueryPage querySourcePage = createNewQuery("study", null) + .setName(sourceQueryName) + .setBaseTable("DataSets") + .clickCreate(); + querySourcePage.setSource(sourceQuery) + .clickSaveAndFinish(); + + log("Create the snapshot"); + DataRegionTable queryTable = new DataRegionTable.DataRegionFinder(getDriver()).withName("query").waitFor(); + queryTable.goToReport("Create Query Snapshot"); + checkCheckbox(Locator.name("queryDataset")); + clickButton("Create Snapshot"); + + log("Verify the dataset snapshot"); + goToProjectHome(); + clickAndWait(Locator.linkWithText(snapshotName)); + DataRegionTable dataRegionTable = new DataRegionTable.DataRegionFinder(getDriver()).withName("Dataset").waitFor(); + Assert.assertEquals("Incorrect number of rows", 3, dataRegionTable.getDataRowCount()); + Assert.assertEquals("Incorrect column titles", Arrays.asList("ParticipantId", "_key", "date", "amount", "units", "kit_type"), + dataRegionTable.getColumnNames()); + Assert.assertEquals("Incorrect value in amount column", Arrays.asList("100", "100", " "), dataRegionTable.getColumnDataAsText("amount")); + + log("Edit the source query and verify snapshot is updated"); + goToSchemaBrowser(); + querySourcePage = editQuerySource("study", sourceQueryName); + querySourcePage.setSource(updatedSourceQuery) + .clickSaveAndFinish(); + + goToProjectHome(); + clickAndWait(Locator.linkWithText(snapshotName)); + dataRegionTable = new DataRegionTable.DataRegionFinder(getDriver()).withName("Dataset").waitFor(); + Assert.assertEquals("Incorrect value in amount column", Arrays.asList("8976", "8976", "8976"), dataRegionTable.getColumnDataAsText("amount")); + } + private void truncateSndPkg() throws Exception { //cleanup - truncate snd.pkgs diff --git a/snd/webapp/snd/test/data.js b/snd/webapp/snd/test/data.js index 1746c34af..463468d76 100644 --- a/snd/webapp/snd/test/data.js +++ b/snd/webapp/snd/test/data.js @@ -59,7 +59,7 @@ active: true, repeatable: true, narrative: 'Sodium: {amount} {units} measured using {kit_type}', - categories: [], + categories: [54], subPackages: [], attributes: [ {