From 8ce0885dc4e3d36aef96cb897fae0e7a8bf8ad37 Mon Sep 17 00:00:00 2001 From: bbimber Date: Sun, 23 Jan 2022 07:05:12 -0800 Subject: [PATCH 01/20] Bugfix to update cluster resources when using more than one job --- primeseq/src/org/labkey/primeseq/PrimeseqController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/primeseq/src/org/labkey/primeseq/PrimeseqController.java b/primeseq/src/org/labkey/primeseq/PrimeseqController.java index a757d5643..bfca57b2b 100644 --- a/primeseq/src/org/labkey/primeseq/PrimeseqController.java +++ b/primeseq/src/org/labkey/primeseq/PrimeseqController.java @@ -431,7 +431,7 @@ public ApiResponse execute(GetResourceSettingsForJobForm form, BindException err } } - Map> valueMap = new HashMap<>(); + Map> valueMap = new HashMap<>(); for (Integer jobId : form.getJobRowIds()) { PipelineStatusFile sf = PipelineService.get().getStatusFile(jobId); @@ -476,7 +476,7 @@ else if (!sf.lookupContainer().hasPermission(getUser(), ReadPermission.class)) valueMap.put(name, new HashSet<>()); } - valueMap.get(name).add(String.valueOf(jobParams.get(jsonName))); + valueMap.get(name).add(jobParams.get(jsonName)); } } } From 5a6b7b9275bc17265d11a199de08ed8a545d60a1 Mon Sep 17 00:00:00 2001 From: bbimber Date: Mon, 24 Jan 2022 10:48:14 -0800 Subject: [PATCH 02/20] Add sample type to import and fully implement insertRows --- .../web/covidseq/panel/SampleImportPanel.js | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/covidseq/resources/web/covidseq/panel/SampleImportPanel.js b/covidseq/resources/web/covidseq/panel/SampleImportPanel.js index 5e0e1f1ff..c4f7c1bb3 100644 --- a/covidseq/resources/web/covidseq/panel/SampleImportPanel.js +++ b/covidseq/resources/web/covidseq/panel/SampleImportPanel.js @@ -73,6 +73,10 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { name: 'assayType', labels: ['Assay Type', 'Assay_Type'], allowBlank: true + },{ + name: 'sampleType', + labels: ['Sample Type', 'Sample_Type'], + allowBlank: true },{ name: 'comment', labels: ['Comment'], @@ -622,9 +626,21 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { }, onSubmit: function(e, dt, node, config){ - //Ext4.Msg.wait('Saving...'); - - //TODO + Ext4.Msg.wait('Saving...'); + + LABKEY.Query.insertRows({ + schemaName: 'covidseq', + queryName: 'samples', + scope: this, + success: function(){ + Ext4.Msg.hide(); + Ext4.Msg.alert('Success', 'Data Imported', function(){ + window.location = LABKEY.ActionURL.buildURL('query', 'executeQuery.view', Laboratory.Utils.getQueryContainerPath(), {'query.queryName': 'samples', schemaName: 'covidseq', 'query.sort': '-created'}) + }, this); + }, + failure: LDK.Utils.getErrorCallback(), + rows: config.rowData.parsedRows + }); }, COUNTY_LIST: [ From da3a9ee79a26d0cf9fc684cc0f2f01e043e65fd9 Mon Sep 17 00:00:00 2001 From: bbimber Date: Tue, 25 Jan 2022 22:42:54 -0800 Subject: [PATCH 03/20] Improvements to COVID sample import --- covidseq/resources/schemas/covidseq.xml | 10 +- .../sqlserver/covidseq-21.002-21.003.sql | 1 + .../web/covidseq/panel/SampleImportPanel.js | 235 ++++++++++++++++-- .../labkey/covidseq/CovidseqDataProvider.java | 10 +- .../org/labkey/covidseq/CovidseqModule.java | 2 +- .../labkey/covidseq/CovidseqUserSchema.java | 11 +- 6 files changed, 241 insertions(+), 28 deletions(-) create mode 100644 covidseq/resources/schemas/dbscripts/sqlserver/covidseq-21.002-21.003.sql diff --git a/covidseq/resources/schemas/covidseq.xml b/covidseq/resources/schemas/covidseq.xml index d5228a2d7..6de4c263e 100644 --- a/covidseq/resources/schemas/covidseq.xml +++ b/covidseq/resources/schemas/covidseq.xml @@ -23,7 +23,7 @@ Samples - /covidseq/sampleImport.view + covidseq/sampleImport.view DETAILED rowid samplename @@ -134,7 +134,7 @@ DETAILED patientId - rowid + identifier true @@ -148,7 +148,7 @@ Patient Identifier - true + false Patient Id PHI @@ -169,6 +169,10 @@ true Age (years) + + true + Age Qualifier + Gender diff --git a/covidseq/resources/schemas/dbscripts/sqlserver/covidseq-21.002-21.003.sql b/covidseq/resources/schemas/dbscripts/sqlserver/covidseq-21.002-21.003.sql new file mode 100644 index 000000000..434c460ad --- /dev/null +++ b/covidseq/resources/schemas/dbscripts/sqlserver/covidseq-21.002-21.003.sql @@ -0,0 +1 @@ +ALTER TABLE covidseq.patients ADD ageQualifier varchar(200); \ No newline at end of file diff --git a/covidseq/resources/web/covidseq/panel/SampleImportPanel.js b/covidseq/resources/web/covidseq/panel/SampleImportPanel.js index c4f7c1bb3..aa0f8817a 100644 --- a/covidseq/resources/web/covidseq/panel/SampleImportPanel.js +++ b/covidseq/resources/web/covidseq/panel/SampleImportPanel.js @@ -14,6 +14,11 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { alwaysShow: true, allowBlank: false, transform: 'samplename' + },{ + name: 'identifier', + labels: ['Patient Id', 'Patient_Id', 'Individual_ID'], + alwaysShow: true, + allowBlank: true },{ name: 'cdna_plate_location', labels: ['Well', 'Well Id', 'cDNA_Plate_Location'], @@ -47,36 +52,48 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { },{ name: 'n1_or_s', labels: ['N1_or_S', 'S'], - allowBlank: true + allowBlank: true, + transform: 'ct' },{ name: 'n2_orn', labels: ['N2_orN', 'N/N2'], - allowBlank: true + allowBlank: true, + transform: 'ct' },{ name: 'rp_or_orf1ab', labels: ['RP_or_ORF1ab'], - allowBlank: true + allowBlank: true, + transform: 'ct' },{ name: 'ms2', labels: ['MS2/Control', 'MS2'], - allowBlank: true + allowBlank: true, + transform: 'ct' },{ name: 'age', labels: ['Age'], + allowBlank: true, + transform: 'age' + },{ + name: 'ageQualifier', + labels: ['Age Qualifier'], allowBlank: true },{ name: 'sampledate', labels: ['Sample Date', 'Collection Date', 'CollectionDate', 'SampleDate', 'Collection_Date'], alwaysShow: true, - allowBlank: true + allowBlank: true, + transform: 'sampledate' },{ name: 'assayType', labels: ['Assay Type', 'Assay_Type'], + transform: 'assayType', allowBlank: true },{ name: 'sampleType', - labels: ['Sample Type', 'Sample_Type'], - allowBlank: true + labels: ['Sample Type', 'Sample_Type', 'Type'], + allowBlank: true, + transform: 'sampleType' },{ name: 'comment', labels: ['Comment'], @@ -130,6 +147,68 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { return val; }, + sampledate: function(val) { + if (val && val.toLowerCase() === 'unknown') { + val = null; + } + + return(val); + }, + + ct: function(val, panel, row) { + if (val) { + if (String(val).toLowerCase() === 'undetermined') { + val = null; + } + else if (String(val).toLowerCase() === 'na') { + val = null; + } + + // Transform values like "4.8 *" + if (String(val).match('[\\* ]')) { + val = String(val).replaceAll(/[\\* ]/g, ''); + } + } + + return(val); + }, + + age: function(val, panel, row) { + if (val && String(val).toLowerCase() === 'unknown') { + val = null; + } + else if (val && String(val).match('>')) { + row.ageQualifier = '>'; + val = String(val).replaceAll('>', ''); + } + else if (val && String(val).match('<')) { + row.ageQualifier = '<'; + val = String(val).replaceAll('<', ''); + } + + return(val); + }, + + assayType: function(val) { + if (val) { + if ('x' === val.toLowerCase() || 'tp' === val.toLowerCase()) { + val = 'TaqPath'; + } + } + + return val; + }, + + sampleType: function(val) { + if (val) { + if ('np' === val.toLowerCase()) { + val = 'Nasopharyngeal swab'; + } + } + + return val; + }, + county: function(val, panel, row, rowIdx, errorMessages, infoMessages) { if (!val) { return val; @@ -142,20 +221,25 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { return 'Not Applicable' } - if (val === 'unknown' || val === 'unk') { + if (val === 'unknown' || val === 'unk' || val === '?') { return 'Unknown' } if (panel.COUNTY_MAP[val]) { if (row.state) { + if (panel.STATE_ABBREV[val.toLowerCase()]) { + row.state = panel.STATE_ABBREV[row.state.toLowerCase()] + } + if (panel.COUNTY_MAP[val].states.indexOf(row.state) === -1) { var updated = false; + var inputState = row.state; if (panel.AUTO_UPDATE_STATE && panel.COUNTY_MAP[val].states.length === 1){ updated = true; row.state = panel.COUNTY_MAP[val].states[0]; } - infoMessages.push('State doesnt match for county: ' + val + ', input value: ' + row.state + ', expected: ' + panel.COUNTY_MAP[val].states.join(',') + ', at row: ' + (rowIdx + 1) + (updated ? '. This was corrected.' : '. This was not automatically changed.')); + infoMessages.push('State doesnt match for county: ' + val + ', input value: ' + inputState + ', expected: ' + panel.COUNTY_MAP[val].states.join(',') + ', at row: ' + (rowIdx + 1) + (updated ? '. This was corrected.' : '. This was not automatically changed.')); } } else { @@ -185,11 +269,13 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { if (!val) { data.comment = data.comment ? data.comment + '. State auto-assigned to OR' : 'State auto-assigned to OR'; return 'OR'; - } val = val.toLowerCase(); - if (['or', 'oregon'].indexOf(val) > -1) { + if (panel.STATE_ABBREV[val]) { + return panel.STATE_ABBREV[val]; + } + else if (['or', 'oregon'].indexOf(val) > -1) { return 'OR' } else if (['wa', 'washington'].indexOf(val) > -1) { @@ -200,9 +286,11 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { }, }, - DEFAULT_HEADER: ['cDNA_Plate_ID','cDNA_Plate_Location','CODED_ID','RQG_alt_ID','Collection_Date','Gender','State','County','N1_or_S','N2_orN','RP_or_ORF1ab','Assay_Type','MS2','Age'], + DEFAULT_HEADER: ['cDNA_Plate_ID','cDNA_Plate_Location','CODED_ID','RQG_alt_ID','Collection_Date','Gender','State','County','N1_or_S','N2_orN','RP_or_ORF1ab','Assay_Type','MS2','SampleType','Age'], initComponent: function () { + Ext4.QuickTips.init(); + this.COLUMN_MAP = {}; Ext4.Array.forEach(this.COLUMNS, function(col){ this.COLUMN_MAP[col.name.toLowerCase()] = col; @@ -419,9 +507,15 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { return; } + var patientRows = this.inferPatientRows(parsedRows) console.log(parsedRows) + console.log(patientRows) + this.reviewPatients(colArray, parsedRows, patientRows) + }, - this.renderPreview(colArray, parsedRows); + reviewPatients: function(colArray, parsedRows, patientRows) { + // TODO: consider querying the patients table to look for existing records? + this.renderPreview(colArray, parsedRows, patientRows); }, parseHeader: function(headerRow){ @@ -547,6 +641,7 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { // CT of record is N for taqpath, N1 for CDC? + data.patientid = data.patientid || data.samplename ret.push(data); }, this); @@ -563,7 +658,7 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { return error ? null : ret; }, - renderPreview: function(colArray, parsedRows){ + renderPreview: function(colArray, parsedRows, patientRows){ var previewArea = this.down('#previewArea'); previewArea.removeAll(); @@ -612,6 +707,7 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { rowData: { colArray: colArray, parsedRows: parsedRows, + patientRows: patientRows, panel: this } }], @@ -628,19 +724,53 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { onSubmit: function(e, dt, node, config){ Ext4.Msg.wait('Saving...'); - LABKEY.Query.insertRows({ + var request = new LABKEY.MultiRequest(); + request.add(LABKEY.Query.insertRows, { schemaName: 'covidseq', queryName: 'samples', scope: this, - success: function(){ - Ext4.Msg.hide(); - Ext4.Msg.alert('Success', 'Data Imported', function(){ - window.location = LABKEY.ActionURL.buildURL('query', 'executeQuery.view', Laboratory.Utils.getQueryContainerPath(), {'query.queryName': 'samples', schemaName: 'covidseq', 'query.sort': '-created'}) - }, this); - }, failure: LDK.Utils.getErrorCallback(), rows: config.rowData.parsedRows }); + + request.add(LABKEY.Query.insertRows, { + schemaName: 'covidseq', + queryName: 'patients', + scope: this, + failure: LDK.Utils.getErrorCallback(), + rows: config.rowData.patientRows + }); + + request.send(this.onRequestSuccess, this); + }, + + inferPatientRows: function(sampleRows) { + var patientRows = []; + + var patientColumns = ['state', 'county', 'country', 'age', 'gender']; + sampleRows.forEach(function(row) { + var guid = LABKEY.Utils.generateUUID(); + if (row.patientid) { + var newRow = {}; + newRow.patientid = guid; + newRow.identifier = row.identifier; + row.patientid = guid; + + patientColumns.forEach(function(colName){ + newRow[colName] = row[colName] + }); + patientRows.push(newRow); + } + }) + + return patientRows; + }, + + onRequestSuccess: function(){ + Ext4.Msg.hide(); + Ext4.Msg.alert('Success', 'Data Imported', function(){ + window.location = LABKEY.ActionURL.buildURL('query', 'executeQuery.view', Laboratory.Utils.getQueryContainerPath(), {'query.queryName': 'samples', schemaName: 'covidseq', 'query.sort': '-created'}) + }, this); }, COUNTY_LIST: [ @@ -905,6 +1035,7 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { ['Custer County','Custer','CO'], ['Delta County','Delta','CO'], ['Denver','Denver','CO'], + ['Denver','Denver County','CO'], ['Dolores County','Dolores','CO'], ['Douglas County','Douglas','CO'], ['Eagle County','Eagle','CO'], @@ -1006,6 +1137,8 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { ['Marion County','Marion','FL'], ['Martin County','Martin','FL'], ['Miami-Dade County','Miami-Dade','FL'], + ['Miami-Dade County','MiamiDade','FL'], + ['Miami-Dade County','MiamiDade County','FL'], ['Monroe County','Monroe','FL'], ['Nassau County','Nassau','FL'], ['Okaloosa County','Okaloosa','FL'], @@ -1022,6 +1155,7 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { ['Seminole County','Seminole','FL'], ['St. Johns County','St. Johns','FL'], ['St. Lucie County','St. Lucie','FL'], + ['St. Lucie County','Saint Lucie County','FL'], ['Sumter County','Sumter','FL'], ['Suwannee County','Suwannee','FL'], ['Taylor County','Taylor','FL'], @@ -1805,6 +1939,7 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { ['St. James Parish','St. James','LA'], ['St. John the Baptist Parish','St. John the Baptist','LA'], ['St. Landry Parish','St. Landry','LA'], + ['St. Landry Parish','Saint Landry Parish','LA'], ['St. Martin Parish','St. Martin','LA'], ['St. Mary Parish','St. Mary','LA'], ['St. Tammany Parish','St. Tammany','LA'], @@ -3869,5 +4004,61 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { ['Villalba','Villalba','PR'], ['Yabucoa','Yabucoa','PR'], ['Yauco','Yauco','PR'], - ] + ], + + STATE_ABBREV: { + 'alabama': 'AL', + 'alaska': 'AK', + 'arizona': 'AZ', + 'arkansas': 'AR', + 'california': 'CA', + 'colorado': 'CO', + 'connecticut': 'CT', + 'delaware': 'DE', + 'district of columbia': 'DC', + 'florida': 'FL', + 'georgia': 'GA', + 'hawaii': 'HI', + 'idaho': 'ID', + 'illinois': 'IL', + 'indiana': 'IN', + 'iowa': 'IA', + 'kansas': 'KS', + 'kentucky': 'KY', + 'louisiana': 'LA', + 'maine': 'ME', + 'maryland': 'MD', + 'massachusetts': 'MA', + 'michigan': 'MI', + 'minnesota': 'MN', + 'mississippi': 'MS', + 'missouri': 'MO', + 'montana': 'MT', + 'nebraska': 'NE', + 'nevada': 'NV', + 'new hampshire': 'NH', + 'new jersey': 'NJ', + 'new mexico': 'NM', + 'new york': 'NY', + 'north carolina': 'NC', + 'north dakota': 'ND', + 'ohio': 'OH', + 'oklahoma': 'OK', + 'oregon': 'OR', + 'pennsylvania': 'PA', + 'puerto rico': 'PR', + 'rhode island': 'RI', + 'south carolina': 'SC', + 'south dakota': 'SD', + 'tennessee': 'TN', + 'texas': 'TX', + 'utah': 'UT', + 'vermont': 'VT', + 'virginia': 'VA', + 'virgin islands': 'VI', + 'washington': 'WA', + 'west virginia': 'WV', + 'wisconsin': 'WI', + 'wyoming': 'WY' + } }); \ No newline at end of file diff --git a/covidseq/src/org/labkey/covidseq/CovidseqDataProvider.java b/covidseq/src/org/labkey/covidseq/CovidseqDataProvider.java index e08c45e76..b685df28e 100644 --- a/covidseq/src/org/labkey/covidseq/CovidseqDataProvider.java +++ b/covidseq/src/org/labkey/covidseq/CovidseqDataProvider.java @@ -2,6 +2,7 @@ import org.json.JSONObject; import org.labkey.api.data.Container; +import org.labkey.api.data.TableInfo; import org.labkey.api.laboratory.AbstractDataProvider; import org.labkey.api.laboratory.LaboratoryService; import org.labkey.api.laboratory.NavItem; @@ -52,7 +53,14 @@ public List getSampleNavItems(Container c, User u) QueryCache cache = new QueryCache(); return Arrays.asList( - new QueryImportNavItem(this, CovidseqSchema.NAME, CovidseqSchema.TABLE_SAMPLES, "COVID Samples", LaboratoryService.NavItemCategory.samples, "Samples", cache) + new QueryImportNavItem(this, CovidseqSchema.NAME, CovidseqSchema.TABLE_SAMPLES, "COVID Samples", LaboratoryService.NavItemCategory.samples, "Samples", cache){ + @Override + public ActionURL getImportUrl(Container c, User u) + { + TableInfo ti = getTableInfo(c, u); + return ti == null ? null : ti.getImportDataURL(c); + } + } ); } diff --git a/covidseq/src/org/labkey/covidseq/CovidseqModule.java b/covidseq/src/org/labkey/covidseq/CovidseqModule.java index a6c186ff4..b5796d777 100644 --- a/covidseq/src/org/labkey/covidseq/CovidseqModule.java +++ b/covidseq/src/org/labkey/covidseq/CovidseqModule.java @@ -41,7 +41,7 @@ public String getName() @Override public @Nullable Double getSchemaVersion() { - return 21.002; + return 21.003; } @Override diff --git a/covidseq/src/org/labkey/covidseq/CovidseqUserSchema.java b/covidseq/src/org/labkey/covidseq/CovidseqUserSchema.java index 2b7fd7cab..106211c62 100644 --- a/covidseq/src/org/labkey/covidseq/CovidseqUserSchema.java +++ b/covidseq/src/org/labkey/covidseq/CovidseqUserSchema.java @@ -7,6 +7,7 @@ import org.labkey.api.data.DbSchema; import org.labkey.api.data.DbSchemaType; import org.labkey.api.data.TableInfo; +import org.labkey.api.ldk.LDKService; import org.labkey.api.ldk.table.ContainerScopedTable; import org.labkey.api.module.Module; import org.labkey.api.query.DefaultSchema; @@ -40,8 +41,16 @@ public QuerySchema createSchema(final DefaultSchema schema, Module module) protected TableInfo createWrappedTable(String name, @NotNull TableInfo sourceTable, ContainerFilter cf) { if (CovidseqSchema.TABLE_SAMPLES.equalsIgnoreCase(name)) - return new ContainerScopedTable<>(this, sourceTable, cf, "sampleName").init(); + { + ContainerScopedTable ret = new ContainerScopedTable<>(this, sourceTable, cf, "sampleName").init(); + LDKService.get().applyNaturalSort(ret, "samplename"); + LDKService.get().applyNaturalSort(ret, "patientid"); + + return(ret); + } else + { return super.createWrappedTable(name, sourceTable, cf); + } } } From 025c5758d665dfd12e91c17d2448a25ee107ac54 Mon Sep 17 00:00:00 2001 From: bbimber Date: Wed, 26 Jan 2022 08:00:55 -0800 Subject: [PATCH 04/20] Fix typo in MCC permissions --- mcc/src/client/AnimalRequest/animal-request.tsx | 2 +- mcc/src/org/labkey/mcc/security/MccRequesterRole.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mcc/src/client/AnimalRequest/animal-request.tsx b/mcc/src/client/AnimalRequest/animal-request.tsx index def06335d..386525e01 100644 --- a/mcc/src/client/AnimalRequest/animal-request.tsx +++ b/mcc/src/client/AnimalRequest/animal-request.tsx @@ -111,7 +111,7 @@ export function AnimalRequest() { return true } - return "draft" === animalRequests.data.status + return "draft" === animalRequests.data.status || "submitting" === animalRequests.data.status } function handleNextStateSubmitButton() { diff --git a/mcc/src/org/labkey/mcc/security/MccRequesterRole.java b/mcc/src/org/labkey/mcc/security/MccRequesterRole.java index 076fd8eff..03e6b6d41 100644 --- a/mcc/src/org/labkey/mcc/security/MccRequesterRole.java +++ b/mcc/src/org/labkey/mcc/security/MccRequesterRole.java @@ -1,14 +1,16 @@ package org.labkey.mcc.security; import org.jetbrains.annotations.NotNull; +import org.labkey.api.security.permissions.InsertPermission; import org.labkey.api.security.permissions.ReadPermission; +import org.labkey.api.security.permissions.UpdatePermission; import org.labkey.api.security.roles.AbstractRole; public class MccRequesterRole extends AbstractRole { public MccRequesterRole() { - super("MCCRequestor", "These users can submit animal requests", ReadPermission.class, MccRequestorPermission.class); + super("MCCRequestor", "These users can submit animal requests", ReadPermission.class, UpdatePermission.class, InsertPermission.class, MccRequestorPermission.class); } @Override From cfb67618690161e31ed21e4e707cfc52e986f3c2 Mon Sep 17 00:00:00 2001 From: bbimber Date: Wed, 26 Jan 2022 09:03:20 -0800 Subject: [PATCH 05/20] Update path to match new LK build --- mcc/resources/views/dashboardWebpart.view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcc/resources/views/dashboardWebpart.view.xml b/mcc/resources/views/dashboardWebpart.view.xml index c7ec7befa..08164ec80 100644 --- a/mcc/resources/views/dashboardWebpart.view.xml +++ b/mcc/resources/views/dashboardWebpart.view.xml @@ -3,6 +3,6 @@ - + \ No newline at end of file From 2c92db53aad307adb39c51ab56dd485611282fca Mon Sep 17 00:00:00 2001 From: bbimber Date: Thu, 27 Jan 2022 13:36:16 -0800 Subject: [PATCH 06/20] More QC for COVID sample import --- .../web/covidseq/panel/SampleImportPanel.js | 82 +++++++++++++------ 1 file changed, 55 insertions(+), 27 deletions(-) diff --git a/covidseq/resources/web/covidseq/panel/SampleImportPanel.js b/covidseq/resources/web/covidseq/panel/SampleImportPanel.js index aa0f8817a..f7f5a22c7 100644 --- a/covidseq/resources/web/covidseq/panel/SampleImportPanel.js +++ b/covidseq/resources/web/covidseq/panel/SampleImportPanel.js @@ -15,7 +15,7 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { allowBlank: false, transform: 'samplename' },{ - name: 'identifier', + name: 'patientid', labels: ['Patient Id', 'Patient_Id', 'Individual_ID'], alwaysShow: true, allowBlank: true @@ -127,7 +127,7 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { return val || 'USA' }, - gender: function(val, panel, row, rowIdx, messages) { + gender: function(val, panel, row, rowIdx) { if (!val) { return val; } @@ -155,7 +155,7 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { return(val); }, - ct: function(val, panel, row) { + ct: function(val, panel, row, rowIdx, errorMessages, infoMessages) { if (val) { if (String(val).toLowerCase() === 'undetermined') { val = null; @@ -163,10 +163,9 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { else if (String(val).toLowerCase() === 'na') { val = null; } - - // Transform values like "4.8 *" - if (String(val).match('[\\* ]')) { - val = String(val).replaceAll(/[\\* ]/g, ''); + else if (!Ext4.isNumeric(val)) { + errorMessages.push('Non-numeric CT: ' + val + ' at row: ' + rowIdx); + return val; } } @@ -641,7 +640,7 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { // CT of record is N for taqpath, N1 for CDC? - data.patientid = data.patientid || data.samplename + data._patientid = data.patientid || data.samplename ret.push(data); }, this); @@ -724,44 +723,73 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { onSubmit: function(e, dt, node, config){ Ext4.Msg.wait('Saving...'); - var request = new LABKEY.MultiRequest(); - request.add(LABKEY.Query.insertRows, { - schemaName: 'covidseq', - queryName: 'samples', - scope: this, - failure: LDK.Utils.getErrorCallback(), - rows: config.rowData.parsedRows + config.rowData.parsedRows.forEach(function(row){ + if (row.patientidGUID) { + row.patientid = row.patientidGUID; + } }); - request.add(LABKEY.Query.insertRows, { - schemaName: 'covidseq', - queryName: 'patients', + LABKEY.Query.saveRows({ + commands: [{ + command: 'insert', + schemaName: 'covidseq', + queryName: 'samples', + rows: config.rowData.parsedRows + },{ + command: 'insert', + schemaName: 'covidseq', + queryName: 'patients', + rows: config.rowData.patientRows + }], scope: this, failure: LDK.Utils.getErrorCallback(), - rows: config.rowData.patientRows + success: this.onRequestSuccess }); - - request.send(this.onRequestSuccess, this); }, inferPatientRows: function(sampleRows) { var patientRows = []; + var patientsEncountered = {}; + var patientToGUID = {}; var patientColumns = ['state', 'county', 'country', 'age', 'gender']; sampleRows.forEach(function(row) { - var guid = LABKEY.Utils.generateUUID(); - if (row.patientid) { + if (row._patientid) { var newRow = {}; - newRow.patientid = guid; - newRow.identifier = row.identifier; - row.patientid = guid; patientColumns.forEach(function(colName){ newRow[colName] = row[colName] }); + + var rowKey = Object.values(newRow).join('<>'); + var doSave = true; + if (patientsEncountered[row._patientid]) { + var oldRowKey = patientsEncountered[row._patientid]; + if (rowKey !== oldRowKey) { + console.error("Mismatch: " + row._patientid); + console.error(newRow); + console.error(oldRowKey); + } + else { + doSave = false; + console.log('skipping second patient row: ' + row._patientid); + } + } + else { + patientsEncountered[row._patientid] = rowKey; + patientToGUID[row._patientid] = LABKEY.Utils.generateUUID(); + } + + // Create GUID to manage relationship: + newRow.patientid = patientToGUID[row._patientid]; + newRow.identifier = row._patientid; + + // update original row: + row.patientidGUID = patientToGUID[row._patientid]; + patientRows.push(newRow); } - }) + }); return patientRows; }, From f38d96b6e1e63d1622bf89c67725121dcab3a7f7 Mon Sep 17 00:00:00 2001 From: bbimber Date: Fri, 28 Jan 2022 22:58:04 -0800 Subject: [PATCH 07/20] Force unique IDs in patient rows for COVID samples --- .../resources/web/covidseq/panel/SampleImportPanel.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/covidseq/resources/web/covidseq/panel/SampleImportPanel.js b/covidseq/resources/web/covidseq/panel/SampleImportPanel.js index f7f5a22c7..b24b969f2 100644 --- a/covidseq/resources/web/covidseq/panel/SampleImportPanel.js +++ b/covidseq/resources/web/covidseq/panel/SampleImportPanel.js @@ -770,10 +770,9 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { console.error(newRow); console.error(oldRowKey); } - else { - doSave = false; - console.log('skipping second patient row: ' + row._patientid); - } + + doSave = false; + console.log('skipping second patient row: ' + row._patientid); } else { patientsEncountered[row._patientid] = rowKey; @@ -787,7 +786,9 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { // update original row: row.patientidGUID = patientToGUID[row._patientid]; - patientRows.push(newRow); + if (doSave) { + patientRows.push(newRow); + } } }); From 7cc2ab8b941bd91c5265e01217d165da8832315e Mon Sep 17 00:00:00 2001 From: bbimber Date: Mon, 31 Jan 2022 11:03:12 -0800 Subject: [PATCH 08/20] Minor improvements to COVID sample import form --- covidseq/resources/web/covidseq/panel/SampleImportPanel.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/covidseq/resources/web/covidseq/panel/SampleImportPanel.js b/covidseq/resources/web/covidseq/panel/SampleImportPanel.js index b24b969f2..772477723 100644 --- a/covidseq/resources/web/covidseq/panel/SampleImportPanel.js +++ b/covidseq/resources/web/covidseq/panel/SampleImportPanel.js @@ -141,7 +141,7 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { } if (val === 'unknown' || val === 'unk') { - return 'Unknown' + return 'unknown' } return val; @@ -221,7 +221,7 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { } if (val === 'unknown' || val === 'unk' || val === '?') { - return 'Unknown' + return 'unknown' } if (panel.COUNTY_MAP[val]) { @@ -640,7 +640,7 @@ Ext4.define('CovidSeq.panel.SampleImportPanel', { // CT of record is N for taqpath, N1 for CDC? - data._patientid = data.patientid || data.samplename + data._patientid = data.patientid || data.state + '-OHSU-' + data.samplename.replaceAll('CV', ''); ret.push(data); }, this); From 37349b1c5747f6e0e5fe9bc5715674d69420b199 Mon Sep 17 00:00:00 2001 From: bbimber Date: Tue, 1 Feb 2022 08:07:10 -0800 Subject: [PATCH 09/20] Update npm dependencies --- mcc/package-lock.json | 14 +++++++------- mcc/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/mcc/package-lock.json b/mcc/package-lock.json index 4fe3727bf..439dcafbc 100644 --- a/mcc/package-lock.json +++ b/mcc/package-lock.json @@ -8,7 +8,7 @@ "name": "mcc", "version": "0.0.0", "dependencies": { - "@labkey/api": "^1.7.2", + "@labkey/api": "^1.8.0", "chart.js": "^3.7.0", "nanoid": "^3.2.0", "react": "^17.0.2", @@ -2439,9 +2439,9 @@ } }, "node_modules/@labkey/api": { - "version": "1.7.2", - "resolved": "https://artifactory.labkey.com:443/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.7.2.tgz", - "integrity": "sha1-2G4KoFl6rLWxGVuh2qPmiwd7RdM=", + "version": "1.8.0", + "resolved": "https://artifactory.labkey.com:443/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.8.0.tgz", + "integrity": "sha1-DiGjCOphC25lFsGwX3BC7kGmhlc=", "license": "Apache-2.0" }, "node_modules/@labkey/build": { @@ -15898,9 +15898,9 @@ } }, "@labkey/api": { - "version": "1.7.2", - "resolved": "https://artifactory.labkey.com:443/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.7.2.tgz", - "integrity": "sha1-2G4KoFl6rLWxGVuh2qPmiwd7RdM=" + "version": "1.8.0", + "resolved": "https://artifactory.labkey.com:443/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.8.0.tgz", + "integrity": "sha1-DiGjCOphC25lFsGwX3BC7kGmhlc=" }, "@labkey/build": { "version": "5.0.0", diff --git a/mcc/package.json b/mcc/package.json index 09ac7175b..5640dced8 100644 --- a/mcc/package.json +++ b/mcc/package.json @@ -11,7 +11,7 @@ "test": "cross-env NODE_ENV=test jest" }, "dependencies": { - "@labkey/api": "^1.7.2", + "@labkey/api": "^1.8.0", "chart.js": "^3.7.0", "nanoid": "^3.2.0", "react": "^17.0.2", From 7893539c15dc4ae4a0bf725224308114851263e9 Mon Sep 17 00:00:00 2001 From: bbimber Date: Fri, 4 Feb 2022 10:30:09 -0800 Subject: [PATCH 10/20] Add error messages and fix validation-flow bugs (#49) * Add MCC error messages and fix validation-flow bugs Co-authored-by: hextraza --- mcc/resources/folderTypes/MCC.folderType.xml | 36 ++ mcc/resources/module.xml | 9 +- mcc/resources/queries/mcc/animalRequests.js | 18 + .../queries/mcc/animalRequests/.qview.xml | 1 + .../queries/mcc/myRequests/.qview.xml | 5 + .../client/AnimalRequest/animal-breeding.tsx | 9 +- .../client/AnimalRequest/animal-cohort.tsx | 9 +- .../client/AnimalRequest/animal-request.tsx | 159 +++--- .../client/AnimalRequest/co-investigators.tsx | 13 +- mcc/src/client/AnimalRequest/date.tsx | 2 +- .../AnimalRequest/error-message-handler.tsx | 41 ++ mcc/src/client/AnimalRequest/funding.tsx | 22 +- .../client/AnimalRequest/iacuc-protocol.tsx | 27 +- mcc/src/client/AnimalRequest/input-number.tsx | 2 +- mcc/src/client/AnimalRequest/input.tsx | 2 +- .../client/AnimalRequest/research-area.tsx | 15 +- mcc/src/client/AnimalRequest/select.tsx | 6 +- mcc/src/client/AnimalRequest/tailwind.css | 4 + mcc/src/client/AnimalRequest/text-area.tsx | 2 +- mcc/src/client/AnimalRequest/yes-no-radio.tsx | 6 +- mcc/src/org/labkey/mcc/MccManager.java | 27 +- .../org/labkey/mcc/query/TriggerHelper.java | 45 +- .../org/labkey/test/tests/mcc/MccTest.java | 457 ++++++++++++------ 23 files changed, 658 insertions(+), 259 deletions(-) create mode 100644 mcc/resources/folderTypes/MCC.folderType.xml create mode 100644 mcc/resources/queries/mcc/myRequests/.qview.xml create mode 100644 mcc/src/client/AnimalRequest/error-message-handler.tsx diff --git a/mcc/resources/folderTypes/MCC.folderType.xml b/mcc/resources/folderTypes/MCC.folderType.xml new file mode 100644 index 000000000..dc2f8a18d --- /dev/null +++ b/mcc/resources/folderTypes/MCC.folderType.xml @@ -0,0 +1,36 @@ + + MCC + The default folder layout for MCC + + + mcc + MCC + + + overview + + + + + + + + MCC Overview + body + + + MCC Dashboard Webpart + body + + + + + + MCC + Study + Query + Pipeline + Core + + Core + \ No newline at end of file diff --git a/mcc/resources/module.xml b/mcc/resources/module.xml index 0b9db0eb7..8de4b41ce 100644 --- a/mcc/resources/module.xml +++ b/mcc/resources/module.xml @@ -16,7 +16,14 @@ false - This is a comma separated list of LabKey user names of users that should be notified by email when requests are submitted through MCC. + This is a comma separated list of LabKey user names of users that should be notified by email when requests for help or user access are submitted through MCC. + + ADMIN + + + + false + This is a comma separated list of LabKey user names of users that should be notified by email when requests for help or user access are submitted through MCC. ADMIN diff --git a/mcc/resources/queries/mcc/animalRequests.js b/mcc/resources/queries/mcc/animalRequests.js index c432ca164..3d00815a7 100644 --- a/mcc/resources/queries/mcc/animalRequests.js +++ b/mcc/resources/queries/mcc/animalRequests.js @@ -1,6 +1,8 @@ var console = require("console"); var LABKEY = require("labkey"); +var triggerHelper = new org.labkey.mcc.query.TriggerHelper(LABKEY.Security.currentUser.id, LABKEY.Security.currentContainer.id); + function beforeInsert(row, errors){ beforeUpsert(row, null, errors); } @@ -11,4 +13,20 @@ function beforeUpdate(row, oldRow, errors){ function beforeUpsert(row, oldRow, errors) { row.status = row.status || 'draft' +} + +function afterInsert(row, errors){ + afterUpsert(row, null, errors); +} + +function afterUpdate(row, oldRow, errors){ + afterUpsert(row, oldRow, errors); +} + +function afterUpsert(row, oldRow, errors) { + if (row.status === 'submitted') { + if (!oldRow || oldRow.status !== 'submitted') { + triggerHelper.sendNotification(row.rowid); + } + } } \ No newline at end of file diff --git a/mcc/resources/queries/mcc/animalRequests/.qview.xml b/mcc/resources/queries/mcc/animalRequests/.qview.xml index b52834b46..b87e37920 100644 --- a/mcc/resources/queries/mcc/animalRequests/.qview.xml +++ b/mcc/resources/queries/mcc/animalRequests/.qview.xml @@ -6,6 +6,7 @@ + diff --git a/mcc/resources/queries/mcc/myRequests/.qview.xml b/mcc/resources/queries/mcc/myRequests/.qview.xml new file mode 100644 index 000000000..bfc40e10e --- /dev/null +++ b/mcc/resources/queries/mcc/myRequests/.qview.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/mcc/src/client/AnimalRequest/animal-breeding.tsx b/mcc/src/client/AnimalRequest/animal-breeding.tsx index 6bea3ec44..05066f3be 100644 --- a/mcc/src/client/AnimalRequest/animal-breeding.tsx +++ b/mcc/src/client/AnimalRequest/animal-breeding.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import YesNoRadio from './yes-no-radio' import TextArea from './text-area' +import ErrorMessageHandler from './error-message-handler' import { animalBreedingPlaceholder } from './values' @@ -18,16 +19,16 @@ export default function AnimalBreeding(props) { } return ( - <> +
- setDisplayPurposeField(e.currentTarget.value)}/>
-