diff --git a/mGAP/resources/folderTypes/mGAP.folderType.xml b/mGAP/resources/folderTypes/mGAP.folderType.xml
index bee390d74..bf7576130 100644
--- a/mGAP/resources/folderTypes/mGAP.folderType.xml
+++ b/mGAP/resources/folderTypes/mGAP.folderType.xml
@@ -84,6 +84,10 @@
mGAP Variant Releasesbody
+
+ mGAP Release Notes
+ body
+ mGAP Gene Searchright
diff --git a/mGAP/resources/views/contact.html b/mGAP/resources/views/contact.html
index 1b110d8cc..e1f2f6b53 100644
--- a/mGAP/resources/views/contact.html
+++ b/mGAP/resources/views/contact.html
@@ -49,7 +49,7 @@
success: function(response){
console.log(response);
- Ext4.Msg.alert('Success', 'An account has been requested. You should receive a reply shortly.', function(){
+ Ext4.Msg.alert('Success', 'Your request has been sent. You should receive a reply shortly.', function(){
window.location = LABKEY.ActionURL.getContextPath() + '/';
});
},
diff --git a/mGAP/resources/views/releaseNotes.html b/mGAP/resources/views/releaseNotes.html
new file mode 100644
index 000000000..110244e37
--- /dev/null
+++ b/mGAP/resources/views/releaseNotes.html
@@ -0,0 +1,12 @@
+
Release 2.0:
+
+
Substantial revamp of all data. All samples have been realigned to the MMul_10 reference genome, followed by our standard GenotypeGVCFs pipeline. The MMul_10 is the most complete rhesus macaque assembly to date, and we expect this should improve accuracy of variant calls. Further, because our data are now aligned to the same assembly as NCBI/Ensembl, it should be easier to translate between mGAP and other databases.
+
Our internal variant calling process has switched to use GATK's GenomicsDB to pre-aggregate data prior to calling with GenotypeGVCFs, as opposed to CombineGVCFs, which was used in prior releases. This should be a purely technical difference with no change in the resulting data
+
+
+
Future Plans:
+
+
We expect to upgrade the genome browser to use the redesigned JBrowse 2 browser. This should provide general performance improvements and will make future mGAP-specific customization easier.
+
We will support other modes of viewing and downloading variant data, in particular tabular views by gene.
+
We recognize that the mGAP release VCF can be enormous, particularly because of all the site-specific functional annotation. To support different types of users, upcoming releases will include 'slim' versions of the data, which will be downloadable files with certain information removed to save file size.
+
\ No newline at end of file
diff --git a/mGAP/resources/views/releaseNotes.view.xml b/mGAP/resources/views/releaseNotes.view.xml
new file mode 100644
index 000000000..c9a2b3e33
--- /dev/null
+++ b/mGAP/resources/views/releaseNotes.view.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mGAP/resources/views/releaseNotes.webpart.xml b/mGAP/resources/views/releaseNotes.webpart.xml
new file mode 100644
index 000000000..f7f58d800
--- /dev/null
+++ b/mGAP/resources/views/releaseNotes.webpart.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mGAP/resources/views/variants.html b/mGAP/resources/views/variants.html
index 3d66a6667..f59586d79 100644
--- a/mGAP/resources/views/variants.html
+++ b/mGAP/resources/views/variants.html
@@ -9,7 +9,7 @@
title: 'Variant Catalog Releases',
schemaName: 'mgap',
queryName: 'variantCatalogReleases',
- maxRows: 50,
+ maxRows: 3,
showRecordSelectors: false,
showDetailsColumn: false,
buttonBar: {position: 'none', includeStandardButtons: false, items: []}
diff --git a/mGAP/src/org/labkey/mgap/pipeline/mGapReleaseGenerator.java b/mGAP/src/org/labkey/mgap/pipeline/mGapReleaseGenerator.java
index 848a0a09d..a3fd61c17 100644
--- a/mGAP/src/org/labkey/mgap/pipeline/mGapReleaseGenerator.java
+++ b/mGAP/src/org/labkey/mgap/pipeline/mGapReleaseGenerator.java
@@ -953,7 +953,7 @@ private void inspectAndSummarizeVcf(JobContext ctx, File vcfInput, GeneToNameTra
File interestingVariantTable = getVariantTableName(ctx, vcfInput);
try (VCFFileReader reader = new VCFFileReader(vcfInput); CloseableIterator it = reader.iterator(); CSVWriter writer = new CSVWriter(PrintWriters.getPrintWriter(interestingVariantTable), '\t', CSVWriter.NO_QUOTE_CHARACTER))
{
- writer.writeNext(new String[]{"Chromosome", "Position", "Reference", "Allele", "Source", "Reason", "Description", "Overlapping Gene(s)", "OMIM Entries", "OMIM Phenotypes", "AF", "CADD_PH"});
+ writer.writeNext(new String[]{"Chromosome", "Position", "Reference", "Allele", "Source", "Reason", "Description", "Overlapping Gene(s)", "OMIM Entries", "OMIM Phenotypes", "AF", "Identifier", "CADD_PH"});
while (it.hasNext())
{
Set> queuedLines = new LinkedHashSet<>();
@@ -1113,7 +1113,7 @@ private void inspectAndSummarizeVcf(JobContext ctx, File vcfInput, GeneToNameTra
try
{
String allele = clnAlleles.get(i);
- maybeWriteVariantLine(queuedLines, vc, allele, "ClinVar", diseaseSplit.get(j), description, overlappingGenes, omims, omimds, ctx.getLogger(), "ClinVar:" + clnAlleleIds.get(j));
+ maybeWriteVariantLine(queuedLines, vc, allele, "ClinVar", diseaseSplit.get(j), description, overlappingGenes, omims, omimds, ctx.getLogger(), "ClinVar:" + clnAlleleIds.get(i));
}
catch (IndexOutOfBoundsException e)
diff --git a/mcc/resources/etls/snprc.xml b/mcc/resources/etls/snprc.xml
index 127947d46..4a959b289 100644
--- a/mcc/resources/etls/snprc.xml
+++ b/mcc/resources/etls/snprc.xml
@@ -1,14 +1,121 @@
SNPRC_Data
+
+
SNPRC Clinical/Demographics Data
-
+ Copy to target
-
-
+
+
+ AnimalId
+ date
+ gender
+ geographic_origin
+ birth
+ death
+ species
+ objectid
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Copy to target
+
+
+ AnimalId
+ date
+ weight
+ objectid
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mcc/resources/etls/wnprc.xml b/mcc/resources/etls/wnprc.xml
index e5f1ee6d1..f8efab248 100644
--- a/mcc/resources/etls/wnprc.xml
+++ b/mcc/resources/etls/wnprc.xml
@@ -3,17 +3,108 @@
WNPRC_DataWNPRC Clinical/Demographics Data
-
+ Copy to target
-
-
+
+
+ Id
+ date
+ gender
+ geographic_origin
+ birth
+ death
+ species
+ objectid
+
+
+
+
+
+
+
+
+
+ Copy to target
+
+
+ Id
+ date
+ parent
+ relationship
+ method
+ objectid
+
+
+
+
+
+
+
+
+
+
+
+ Copy to target
+
+
+ Id
+ date
+ gender
+ species
+ geographic_origin
+ dam
+ sire
+ objectid
+
+
+
+
+
+
+
+
+
+
+
+ Copy to target
+
+
+ Id
+ date
+ weight
+ objectid
+
+
+
+
+
+
+
+
+
+
+ Copy to target
+
+
+ Id
+ date
+ cause
+ objectid
+
+
+
+
+
+
+
+
-
+
-
-
+
+
diff --git a/mcc/resources/module.xml b/mcc/resources/module.xml
new file mode 100644
index 000000000..b07899cb8
--- /dev/null
+++ b/mcc/resources/module.xml
@@ -0,0 +1,21 @@
+
+
+
+ false
+ This is the path to the container holding the primary MCC Study. Use of slashes is very important - it should be in the format '/myProject/mcc'
+
+ ADMIN
+
+
+
+ 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.
+
+ ADMIN
+
+
+
+
+
+
+
diff --git a/mcc/resources/queries/study/animalGroupMembership.js b/mcc/resources/queries/study/animalGroupMembership.js
new file mode 100644
index 000000000..c8510ccb2
--- /dev/null
+++ b/mcc/resources/queries/study/animalGroupMembership.js
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2011-2014 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+require("ehr/triggers").initScript(this);
+
+function onInit(event, helper){
+ helper.setScriptOptions({
+ allowFutureDates: true,
+ removeTimeFromDate: true
+ });
+}
\ No newline at end of file
diff --git a/mcc/resources/queries/study/deaths.js b/mcc/resources/queries/study/deaths.js
new file mode 100644
index 000000000..9dda9861c
--- /dev/null
+++ b/mcc/resources/queries/study/deaths.js
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2018-2019 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+require("ehr/triggers").initScript(this);
+EHR.Server.Utils = require("ehr/utils").EHR.Server.Utils;
+
+var demographicsUpdates = [];
+var validIds = [];
+
+function onInit(event, helper){
+ helper.setScriptOptions({
+ requiresStatusRecalc: true
+ });
+
+ helper.decodeExtraContextProperty('deathsInTransaction');
+
+ // Cache valid Ids for check on each row
+ LABKEY.Query.selectRows({
+ requiredVersion: 9.1,
+ schemaName: 'study',
+ queryName: 'demographics',
+ columns: ['Id'],
+ scope: this,
+ success: function (results) {
+ if (!results || !results.rows || results.rows.length < 1)
+ return;
+
+ for(var i=0; i 0) {
+ console.log('updating demographics death date for ' + demographicsUpdates.length + " animals");
+ helper.getJavaHelper().updateDemographicsRecord(demographicsUpdates);
+ }
+
+ var deaths = helper.getDeaths();
+ if (deaths){
+ var ids = [];
+ for (var id in deaths){
+ ids.push(id);
+ }
+
+ if (!helper.isETL()) {
+ console.log('sending death notification');
+ helper.getJavaHelper().sendDeathNotification(ids);
+ }
+ }
+}
\ No newline at end of file
diff --git a/mcc/resources/queries/study/demographics.js b/mcc/resources/queries/study/demographics.js
new file mode 100644
index 000000000..992138d43
--- /dev/null
+++ b/mcc/resources/queries/study/demographics.js
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2010-2019 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+require("ehr/triggers").initScript(this);
+
+function onInit(event, helper){
+ helper.setScriptOptions({
+ allowAnyId: true,
+ requiresStatusRecalc: false,
+ allowDatesInDistantPast: true
+ });
+}
+
+function onUpsert(helper, scriptErrors, row, oldRow){
+ //NOTE: this should be getting set by the birth, death, arrival & departure tables
+ //ALSO: it should be rare to insert directly into this table. usually this record will be created by inserting into either birth or arrival
+ if (!row.calculated_status && !helper.isETL()){
+ row.calculated_status = helper.getJavaHelper().getCalculatedStatusValue(row.Id);
+ }
+}
\ No newline at end of file
diff --git a/mcc/resources/queries/study/encounters.js b/mcc/resources/queries/study/encounters.js
new file mode 100644
index 000000000..fbb17ad72
--- /dev/null
+++ b/mcc/resources/queries/study/encounters.js
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2018-2019 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+require("ehr/triggers").initScript(this);
+
+function onUpsert(helper, scriptErrors, row, oldRow){
+ if (!helper.isETL() && row.date && !row.requestdate){
+ row.requestdate = row.date;
+ }
+}
\ No newline at end of file
diff --git a/mcc/resources/queries/study/flags.js b/mcc/resources/queries/study/flags.js
new file mode 100644
index 000000000..454371e4c
--- /dev/null
+++ b/mcc/resources/queries/study/flags.js
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2012-2018 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+require("ehr/triggers").initScript(this);
+
+function onInit(event, helper){
+ helper.setScriptOptions({
+ allowFutureDates: true,
+ removeTimeFromDate: true,
+ removeTimeFromEndDate: true
+ });
+}
+
+function onUpsert(helper, scriptErrors, row, oldRow){
+ //if the animal is not at the center, automatically set the enddate
+ if (!helper.isETL() && row.Id && !row.enddate){
+ EHR.Server.Utils.findDemographics({
+ participant: row.Id,
+ helper: helper,
+ scope: this,
+ callback: function(data){
+ if (!data)
+ return;
+
+ if (data && data.calculated_status && data.calculated_status != 'Alive'){
+ row.enddate = data.death || data.departure;
+ }
+ }
+ });
+
+ }
+
+ if (!helper.isETL() && row.Id && row.date && row.flag){
+ var active = helper.getJavaHelper().getOverlappingFlags(row.Id, row.flag, row.objectid || null, row.date);
+ if (active > 0){
+ EHR.Server.Utils.addError(scriptErrors, 'flag', 'There are already ' + active + ' active flag(s) of the same type spanning this date.', 'INFO');
+ }
+ }
+}
+
+function onAfterInsert(helper, errors, row){
+ //if this category enforces only a single active flag at once, enforce it
+ //note: if this flag has a future date, preemptively set enddate on flags, since isActive should handle this
+ if (!helper.isETL() && row.Id && row.flag && !row.enddate && row.date){
+ helper.getJavaHelper().ensureSingleFlagCategoryActive(row.Id, row.flag, row.objectId, row.date);
+ }
+}
\ No newline at end of file
diff --git a/mcc/resources/queries/study/labwork.js b/mcc/resources/queries/study/labwork.js
new file mode 100644
index 000000000..d4075c56c
--- /dev/null
+++ b/mcc/resources/queries/study/labwork.js
@@ -0,0 +1,8 @@
+/*
+ * Copyright (c) 2018-2019 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+require("ehr/triggers").initScript(this);
+
diff --git a/mcc/resources/queries/study/labworkResults.js b/mcc/resources/queries/study/labworkResults.js
new file mode 100644
index 000000000..2004fb6cf
--- /dev/null
+++ b/mcc/resources/queries/study/labworkResults.js
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2012-2018 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+require("ehr/triggers").initScript(this);
+
+function onInit(event, helper){
+ helper.setScriptOptions({
+ removeTimeFromDate: false,
+ allowDatesInDistantPast: true
+ });
+}
\ No newline at end of file
diff --git a/mcc/resources/queries/study/medicationAdministration.js b/mcc/resources/queries/study/medicationAdministration.js
new file mode 100644
index 000000000..d4075c56c
--- /dev/null
+++ b/mcc/resources/queries/study/medicationAdministration.js
@@ -0,0 +1,8 @@
+/*
+ * Copyright (c) 2018-2019 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+require("ehr/triggers").initScript(this);
+
diff --git a/mcc/resources/queries/study/medicationOrders.js b/mcc/resources/queries/study/medicationOrders.js
new file mode 100644
index 000000000..d4075c56c
--- /dev/null
+++ b/mcc/resources/queries/study/medicationOrders.js
@@ -0,0 +1,8 @@
+/*
+ * Copyright (c) 2018-2019 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+require("ehr/triggers").initScript(this);
+
diff --git a/mcc/resources/queries/study/parentage.js b/mcc/resources/queries/study/parentage.js
new file mode 100644
index 000000000..0cea7a1c5
--- /dev/null
+++ b/mcc/resources/queries/study/parentage.js
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2013 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+require("ehr/triggers").initScript(this);
+
+function onInit(event, helper){
+ helper.setScriptOptions({
+ lookupValidationFields: ['relationship', 'method']
+ });
+}
diff --git a/mcc/resources/queries/study/samples.js b/mcc/resources/queries/study/samples.js
new file mode 100644
index 000000000..64a117a3e
--- /dev/null
+++ b/mcc/resources/queries/study/samples.js
@@ -0,0 +1,7 @@
+/*
+ * Copyright (c) 2011-2019 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+require("ehr/triggers").initScript(this);
\ No newline at end of file
diff --git a/mcc/resources/queries/study/weight.js b/mcc/resources/queries/study/weight.js
new file mode 100644
index 000000000..a515311e1
--- /dev/null
+++ b/mcc/resources/queries/study/weight.js
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2010-2019 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+require("ehr/triggers").initScript(this);
+
+function onInit(event, helper){
+ helper.setScriptOptions({
+ allowAnyId: true,
+ allowDeadIds: true,
+ skipIdFormatCheck: true
+ });
+
+ helper.registerRowProcessor(function(helper, row){
+ if (!row)
+ return;
+
+ if (!row.Id || !row.weight){
+ return;
+ }
+
+ var weightInTransaction = helper.getProperty('weightInTransaction');
+ weightInTransaction = weightInTransaction || {};
+ weightInTransaction[row.Id] = weightInTransaction[row.Id] || [];
+
+ var shouldAdd = true;
+ if (row.objectid){
+ LABKEY.ExtAdapter.each(weightInTransaction[row.Id], function(r){
+ if (r.objectid === row.objectid){
+ if (r.weight !== row.weight){
+ r.weight = row.weight;
+ }
+ else {
+ shouldAdd = false;
+ return false;
+ }
+ }
+ }, this);
+ }
+
+ if (shouldAdd){
+ weightInTransaction[row.Id].push({
+ objectid: row.objectid,
+ date: row.date,
+ qcstate: row.QCState,
+ weight: row.weight
+ });
+ }
+
+ helper.setProperty('weightInTransaction', weightInTransaction);
+ });
+}
+
+function onUpsert(helper, scriptErrors, row, oldRow){
+ if (!row.weight){
+ EHR.Server.Utils.addError(scriptErrors, 'weight', 'This field is required', 'WARN');
+ }
+
+ // warn if more than 10% different from last weight
+ // the highest error this can produce is WARN. therefore skip this check if we would ignore it anyway in order to save the overhead.
+ // this would normally occur when finalizing a form
+ if (!helper.isETL() && row.Id && row.weight && EHR.Server.Utils.shouldIncludeError('WARN', helper.getErrorThreshold(), helper)){
+ EHR.Server.Utils.findDemographics({
+ participant: row.Id,
+ helper: helper,
+ scope: this,
+ callback: function(data){
+ if (!data)
+ return;
+
+ if (data.mostRecentWeight && (row.weight <= data.mostRecentWeight * 0.9)){
+ EHR.Server.Utils.addError(scriptErrors, 'weight', 'Weight drop of >10%. Last weight ' + data.mostRecentWeight + ' kg', 'INFO');
+ }
+ else if (data.mostRecentWeight && (row.weight >= data.mostRecentWeight / 0.9)){
+ EHR.Server.Utils.addError(scriptErrors, 'weight', 'Weight gain of >10%. Last weight ' + data.mostRecentWeight + ' kg', 'INFO');
+ }
+
+ if (data && data.species){
+ var msg = helper.getJavaHelper().verifyWeightRange(row.id, row.weight, data.species);
+ if (msg != null){
+ EHR.Server.Utils.addError(scriptErrors, 'weight', msg, 'WARN');
+ }
+ }
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/mcc/resources/queries/wnprcSource/birth.sql b/mcc/resources/queries/wnprcSource/birth.sql
new file mode 100644
index 000000000..06808b9af
--- /dev/null
+++ b/mcc/resources/queries/wnprcSource/birth.sql
@@ -0,0 +1,6 @@
+SELECT
+
+Id, date, gender, species, geographic_origin, dam, sire, objectid, modified
+
+FROM "/WNPRC/EHR/".study.birth
+WHERE species = 'Marmoset';
\ No newline at end of file
diff --git a/mcc/resources/queries/wnprcSource/deaths.sql b/mcc/resources/queries/wnprcSource/deaths.sql
new file mode 100644
index 000000000..94c301bb7
--- /dev/null
+++ b/mcc/resources/queries/wnprcSource/deaths.sql
@@ -0,0 +1,8 @@
+SELECT
+
+ Id, date,
+ cause,
+ objectid, modified
+
+FROM "/WNPRC/EHR/".study.weight
+WHERE Id.demographics.species = 'Marmoset';
\ No newline at end of file
diff --git a/mcc/resources/queries/wnprcSource/demographics.sql b/mcc/resources/queries/wnprcSource/demographics.sql
new file mode 100644
index 000000000..7a860fef3
--- /dev/null
+++ b/mcc/resources/queries/wnprcSource/demographics.sql
@@ -0,0 +1,6 @@
+SELECT
+
+Id, date, gender, geographic_origin, birth, death, species, objectid, modified
+
+FROM "/WNPRC/EHR/".study.demographics
+WHERE species = 'Marmoset';
\ No newline at end of file
diff --git a/mcc/resources/queries/wnprcSource/parentage.sql b/mcc/resources/queries/wnprcSource/parentage.sql
new file mode 100644
index 000000000..a8fe2e80c
--- /dev/null
+++ b/mcc/resources/queries/wnprcSource/parentage.sql
@@ -0,0 +1,27 @@
+SELECT
+
+ Id,
+ date,
+ sire as parent,
+ 'Sire' as relationship,
+ 'Observed' as method,
+ cast(objectid as varchar) || '-Sire' as objectid,
+ modified
+
+FROM "/WNPRC/EHR/".study.demographics
+WHERE species = 'Marmoset' and sire is not null
+
+UNION ALL
+
+SELECT
+
+ Id,
+ date,
+ sire as parent,
+ 'Dam' as relationship,
+ 'Observed' as method,
+ cast(objectid as varchar) || '-Dam' as objectid,
+ modified
+
+FROM "/WNPRC/EHR/".study.demographics
+WHERE species = 'Marmoset' and dam is not null
\ No newline at end of file
diff --git a/mcc/resources/queries/wnprcSource/weight.sql b/mcc/resources/queries/wnprcSource/weight.sql
new file mode 100644
index 000000000..db7bce107
--- /dev/null
+++ b/mcc/resources/queries/wnprcSource/weight.sql
@@ -0,0 +1,8 @@
+SELECT
+
+ Id, date,
+ weight,
+ objectid, modified
+
+FROM "/WNPRC/EHR/".study.weight
+WHERE Id.demographics.species = 'Marmoset';
\ No newline at end of file
diff --git a/mcc/resources/referenceStudy/datasets/datasets_manifest.xml b/mcc/resources/referenceStudy/datasets/datasets_manifest.xml
index 3600f8261..47d75b91c 100644
--- a/mcc/resources/referenceStudy/datasets/datasets_manifest.xml
+++ b/mcc/resources/referenceStudy/datasets/datasets_manifest.xml
@@ -18,7 +18,10 @@
-
+
+
+
+
@@ -30,26 +33,17 @@
-
+
-
-
-
-
-
-
-
-
-
diff --git a/mcc/resources/referenceStudy/datasets/datasets_metadata.xml b/mcc/resources/referenceStudy/datasets/datasets_metadata.xml
index 8f19b36f9..480c6a89d 100644
--- a/mcc/resources/referenceStudy/datasets/datasets_metadata.xml
+++ b/mcc/resources/referenceStudy/datasets/datasets_metadata.xml
@@ -135,33 +135,12 @@
varchar
-
- varchar
-
-
- varchar
- varchar
-
- varchar
-
-
- varchar
-
-
- timestamp
-
-
- integer
- varchar
-
- timestamp
- varcharurn:ehr.labkey.org/#ObjectId
@@ -207,36 +186,6 @@
urn:ehr.labkey.org/#ObjectIdtrue
-
- varchar
- urn:ehr.labkey.org/#VetReview
-
-
- timestamp
- urn:ehr.labkey.org/#VetReviewDate
-
-
- varchar
-
-
- varchar
-
-
- varchar
-
-
- varchar
-
-
- timestamp
- urn:ehr.labkey.org/#DateRequested
-
-
- varchar
-
-
- varchar
- integer
@@ -264,6 +213,9 @@
integerurn:ehr.labkey.org/#Project
+
+ varchar
+ varchar
@@ -272,35 +224,6 @@
urn:ehr.labkey.org/#ObjectIdtrue
-
- varchar
- urn:ehr.labkey.org/#VetReview
-
-
- timestamp
- urn:ehr.labkey.org/#VetReviewDate
-
-
- varchar
-
-
- varchar
-
-
- varchar
-
-
- varchar
-
-
- varchar
-
-
- varchar
-
-
- varchar
- timestampurn:ehr.labkey.org/#EndDate
@@ -308,16 +231,10 @@
timestamp
-
- varchar
-
-
- varchar
- Clinical Remarks
-
+
+
\ No newline at end of file
diff --git a/mcc/resources/views/about.html b/mcc/resources/views/about.html
new file mode 100644
index 000000000..436f532b2
--- /dev/null
+++ b/mcc/resources/views/about.html
@@ -0,0 +1,3 @@
+MCC is supported by NIH U24 xxxxxxx.
+
+Please remember to cite this funding source in all publications that make use of MCC data.
\ No newline at end of file
diff --git a/mcc/resources/views/about.view.xml b/mcc/resources/views/about.view.xml
new file mode 100644
index 000000000..48ca9ced8
--- /dev/null
+++ b/mcc/resources/views/about.view.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mcc/resources/views/about.webpart.xml b/mcc/resources/views/about.webpart.xml
new file mode 100644
index 000000000..f40c189c7
--- /dev/null
+++ b/mcc/resources/views/about.webpart.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mcc/resources/views/contact.html b/mcc/resources/views/contact.html
new file mode 100644
index 000000000..714f95170
--- /dev/null
+++ b/mcc/resources/views/contact.html
@@ -0,0 +1,75 @@
+
\ No newline at end of file
diff --git a/mcc/resources/views/contact.view.xml b/mcc/resources/views/contact.view.xml
new file mode 100644
index 000000000..0fc3bfa31
--- /dev/null
+++ b/mcc/resources/views/contact.view.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mcc/resources/views/dashboard.html b/mcc/resources/views/dashboard.html
new file mode 100644
index 000000000..014a425a7
--- /dev/null
+++ b/mcc/resources/views/dashboard.html
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/mcc/resources/views/dashboard.view.xml b/mcc/resources/views/dashboard.view.xml
new file mode 100644
index 000000000..252242cd6
--- /dev/null
+++ b/mcc/resources/views/dashboard.view.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mcc/resources/views/dashboard.webpart.xml b/mcc/resources/views/dashboard.webpart.xml
new file mode 100644
index 000000000..4e56bf8c1
--- /dev/null
+++ b/mcc/resources/views/dashboard.webpart.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mcc/resources/views/helpMenu.html b/mcc/resources/views/helpMenu.html
new file mode 100644
index 000000000..932f848cc
--- /dev/null
+++ b/mcc/resources/views/helpMenu.html
@@ -0,0 +1 @@
+We'd love to hear your feedback! Click here to send a help or feature request, or email mcc@ohsu.edu for any questions.
\ No newline at end of file
diff --git a/mcc/resources/views/helpMenu.view.xml b/mcc/resources/views/helpMenu.view.xml
new file mode 100644
index 000000000..3960a69c1
--- /dev/null
+++ b/mcc/resources/views/helpMenu.view.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mcc/resources/views/helpMenu.webpart.xml b/mcc/resources/views/helpMenu.webpart.xml
new file mode 100644
index 000000000..3f4624d6a
--- /dev/null
+++ b/mcc/resources/views/helpMenu.webpart.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mcc/resources/views/login.html b/mcc/resources/views/login.html
new file mode 100644
index 000000000..71bcbf7d3
--- /dev/null
+++ b/mcc/resources/views/login.html
@@ -0,0 +1,40 @@
+
+
+
+
Or Sign In using:
+
+
\ No newline at end of file
diff --git a/mcc/resources/views/login.view.xml b/mcc/resources/views/login.view.xml
new file mode 100644
index 000000000..7ce47abe2
--- /dev/null
+++ b/mcc/resources/views/login.view.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mcc/resources/views/overview.html b/mcc/resources/views/overview.html
new file mode 100644
index 000000000..f03b886bc
--- /dev/null
+++ b/mcc/resources/views/overview.html
@@ -0,0 +1 @@
+This will hold the overview information for MCC.
\ No newline at end of file
diff --git a/mcc/resources/views/overview.view.xml b/mcc/resources/views/overview.view.xml
new file mode 100644
index 000000000..f9e80a68a
--- /dev/null
+++ b/mcc/resources/views/overview.view.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mcc/resources/views/overview.webpart.xml b/mcc/resources/views/overview.webpart.xml
new file mode 100644
index 000000000..c81f56bb6
--- /dev/null
+++ b/mcc/resources/views/overview.webpart.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mcc/resources/views/requestLogin.html b/mcc/resources/views/requestLogin.html
new file mode 100644
index 000000000..6c3b1fb7f
--- /dev/null
+++ b/mcc/resources/views/requestLogin.html
@@ -0,0 +1,117 @@
+
\ No newline at end of file
diff --git a/mcc/resources/views/requestLogin.view.xml b/mcc/resources/views/requestLogin.view.xml
new file mode 100644
index 000000000..13c485f9f
--- /dev/null
+++ b/mcc/resources/views/requestLogin.view.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mcc/resources/web/mcc/Security.js b/mcc/resources/web/mcc/Security.js
new file mode 100644
index 000000000..5ab385833
--- /dev/null
+++ b/mcc/resources/web/mcc/Security.js
@@ -0,0 +1,49 @@
+Ext4.namespace('MCC.Security');
+
+MCC.Security = new function(){
+ return {
+ approveUserRequests: function(dataRegionName){
+ var dr = LABKEY.DataRegions[dataRegionName];
+ if (!dr){
+ alert('Unable to find DataRegion with name: ' + dataRegionName);
+ return;
+ }
+
+ var rowIds = dr.getChecked();
+ if (!rowIds.length){
+ alert('Must select one or more rows');
+ return;
+ }
+
+ Ext4.Msg.confirm('Approve Requests', 'You are able to approve ' + rowIds.length + ' user requests. Continue?', function(val){
+ if (val === 'yes'){
+ Ext4.Msg.wait('Loading...');
+ LABKEY.Ajax.request({
+ method: 'POST',
+ url: LABKEY.ActionURL.buildURL('mcc', 'approveUserRequests'),
+ params: {
+ requestIds: rowIds
+ },
+ success: function(){
+ Ext4.Msg.hide();
+ Ext4.Msg.alert('Success', 'Requests approved!', function(){
+ //note: drop view, so we see the newly added user(s)
+ LABKEY.DataRegions[dataRegionName].changeView(null);
+ });
+ },
+ failure: LDK.Utils.getErrorCallback({
+ showAlertOnError: false,
+ scope: this,
+ callback: function(responseObj){
+ if (responseObj.errorMsg){
+ Ext4.Msg.alert('Error', responseObj.errorMsg);
+ }
+ }
+ })
+ });
+
+ }
+ }, this);
+ }
+ }
+};
\ No newline at end of file
diff --git a/mcc/resources/web/mcc/dashboard.js b/mcc/resources/web/mcc/dashboard.js
new file mode 100644
index 000000000..4c94d7e87
--- /dev/null
+++ b/mcc/resources/web/mcc/dashboard.js
@@ -0,0 +1,18 @@
+var MCC = {};
+
+MCC.Dashboard = new function() {
+ return {
+ loadData: function () {
+ LABKEY.Query.selectRows({
+ schemaName: 'study',
+ queryName: 'demographics',
+ columns: 'Id,birth,death,gender,species,Id/age/AgeFriendly',
+ success: function(results) {
+ console.log(results.rows);
+ },
+ error: LDK.Utils.getErrorCallback(),
+ scope: this
+ });
+ }
+ }
+};
diff --git a/mcc/src/org/labkey/mcc/MccController.java b/mcc/src/org/labkey/mcc/MccController.java
index 0967188da..ea84cef04 100644
--- a/mcc/src/org/labkey/mcc/MccController.java
+++ b/mcc/src/org/labkey/mcc/MccController.java
@@ -16,22 +16,508 @@
package org.labkey.mcc;
-import org.labkey.api.action.SimpleViewAction;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.labkey.api.action.ApiSimpleResponse;
+import org.labkey.api.action.MutatingApiAction;
import org.labkey.api.action.SpringActionController;
+import org.labkey.api.data.Container;
+import org.labkey.api.data.CoreSchema;
+import org.labkey.api.data.DbScope;
+import org.labkey.api.data.SimpleFilter;
+import org.labkey.api.data.Table;
+import org.labkey.api.data.TableInfo;
+import org.labkey.api.data.TableSelector;
+import org.labkey.api.module.AllowedDuringUpgrade;
+import org.labkey.api.query.DetailsURL;
+import org.labkey.api.query.FieldKey;
+import org.labkey.api.security.IgnoresTermsOfUse;
+import org.labkey.api.security.MutableSecurityPolicy;
+import org.labkey.api.security.RequiresNoPermission;
import org.labkey.api.security.RequiresPermission;
+import org.labkey.api.security.SecurityManager;
+import org.labkey.api.security.SecurityPolicyManager;
+import org.labkey.api.security.User;
+import org.labkey.api.security.UserManager;
+import org.labkey.api.security.ValidEmail;
+import org.labkey.api.security.permissions.AdminPermission;
import org.labkey.api.security.permissions.ReadPermission;
-import org.labkey.api.view.JspView;
-import org.labkey.api.view.NavTree;
+import org.labkey.api.security.roles.ReaderRole;
+import org.labkey.api.settings.AppProps;
+import org.labkey.api.settings.LookAndFeelProperties;
+import org.labkey.api.util.ConfigurationException;
+import org.labkey.api.util.ExceptionUtil;
+import org.labkey.api.util.MailHelper;
+import org.labkey.api.util.PageFlowUtil;
import org.springframework.validation.BindException;
-import org.springframework.web.servlet.ModelAndView;
+import org.springframework.validation.Errors;
+
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.internet.InternetAddress;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
public class MccController extends SpringActionController
{
private static final DefaultActionResolver _actionResolver = new DefaultActionResolver(MccController.class);
public static final String NAME = "mcc";
+ private static final Logger _log = LogManager.getLogger(MccController.class);
+
public MccController()
{
setActionResolver(_actionResolver);
}
+
+ @RequiresNoPermission
+ @IgnoresTermsOfUse
+ @AllowedDuringUpgrade
+ public class RequestUserAction extends MutatingApiAction
+ {
+ @Override
+ public void validateForm(RequestUserForm form, Errors errors)
+ {
+ Container mccContainer = MccManager.get().getMCCContainer();
+ if (mccContainer == null)
+ {
+ errors.reject(ERROR_MSG, "The MCC project has not been set on this server. This is an administrator error.");
+ return;
+ }
+
+ if (StringUtils.isEmpty(form.getEmail()) || StringUtils.isEmpty(form.getEmailConfirmation()))
+ {
+ errors.reject(ERROR_REQUIRED, "No email address provided");
+ }
+ else if (StringUtils.isEmpty(form.getFirstName()) || StringUtils.isEmpty(form.getLastName()) || StringUtils.isEmpty(form.getTitle()) || StringUtils.isEmpty(form.getInstitution()) || StringUtils.isEmpty(form.getReason()))
+ {
+ errors.reject(ERROR_REQUIRED, "You must provide your first and last name, title, institution, and reason for requesting access");
+ }
+ else
+ {
+ try
+ {
+ ValidEmail email = new ValidEmail(form.getEmail());
+ if (!form.getEmail().equals(form.getEmailConfirmation()))
+ {
+ errors.reject(ERROR_MSG, "The email addresses you have entered do not match. Please verify your email addresses below.");
+ }
+
+ TableInfo ti = MccSchema.getInstance().getSchema().getTable(MccSchema.TABLE_USER_REQUESTS);
+
+ //first check if this email exists:
+ SimpleFilter filter = new SimpleFilter(FieldKey.fromString("email"), form.getEmail());
+ filter.addCondition(FieldKey.fromString("container"), mccContainer.getId());
+ if (new TableSelector(ti, filter, null).exists())
+ {
+ errors.reject(ERROR_MSG, "A login has already been requested for this email. You should receive a reply shortly from the site administrator.");
+ }
+ }
+ catch (ValidEmail.InvalidEmailException e)
+ {
+ errors.reject(ERROR_MSG, "Your email address is not valid. Please verify your email address below.");
+ }
+ }
+ }
+
+ @Override
+ public Object execute(RequestUserForm form, BindException errors) throws Exception
+ {
+ ApiSimpleResponse response = new ApiSimpleResponse();
+
+ try
+ {
+ TableInfo ti = MccSchema.getInstance().getSchema().getTable(MccSchema.TABLE_USER_REQUESTS);
+ Map row = new HashMap<>();
+ row.put("email", form.getEmail());
+ row.put("firstName", form.getFirstName());
+ row.put("lastName", form.getLastName());
+ row.put("title", form.getTitle());
+ row.put("institution", form.getInstitution());
+ row.put("reason", form.getReason());
+ row.put("container", MccManager.get().getMCCContainer().getId());
+
+ Table.insert(UserManager.getGuestUser(), ti, row);
+
+ Set users = MccManager.get().getNotificationUsers();
+ if (users != null && !users.isEmpty())
+ {
+ try
+ {
+ Set emails = new HashSet<>();
+ for (User u : users)
+ {
+ emails.add(new InternetAddress(u.getEmail()));
+ }
+
+ MailHelper.MultipartMessage mail = MailHelper.createMultipartMessage();
+ Container c = MccManager.get().getMCCContainer();
+ if (c == null)
+ {
+ c = getContainer();
+ _log.warn("MCC container was not set, using: " + c.getPath());
+ }
+
+ DetailsURL url = DetailsURL.fromString("/query/executeQuery.view?schemaName=mcc&query.queryName=userRequests&query.viewName=Pending Requests", c);
+ mail.setEncodedHtmlContent("A user requested an account on MCC. Click here to view/approve this request");
+ mail.setFrom(getReplyEmail(getContainer()));
+ mail.setSubject("MCC Account Request");
+ mail.addRecipients(Message.RecipientType.TO, emails.toArray(new Address[0]));
+
+ MailHelper.send(mail, getUser(), c);
+ }
+ catch (Exception e)
+ {
+ ExceptionUtil.logExceptionToMothership(null, e);
+ }
+ }
+
+
+ }
+ catch (ConfigurationException e)
+ {
+ errors.reject(ERROR_MSG, "There was a problem sending the registration email. Please contact your administrator.");
+ _log.error("Error adding self registered user", e);
+ }
+
+ response.put("success", !errors.hasErrors());
+ if (!errors.hasErrors())
+ response.put("email", form.getEmail());
+
+ return response;
+ }
+ }
+
+ public static class RequestUserForm extends Object
+ {
+ private String email;
+ private String emailConfirmation;
+ private String firstName;
+ private String lastName;
+ private String title;
+ private String institution;
+ private String reason;
+
+ public void setEmail(String email)
+ {
+ this.email = email;
+ }
+
+ public String getEmail()
+ {
+ return this.email;
+ }
+
+ public void setEmailConfirmation(String email)
+ {
+ this.emailConfirmation = email;
+ }
+
+ public String getEmailConfirmation()
+ {
+ return this.emailConfirmation;
+ }
+
+ public String getFirstName()
+ {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName)
+ {
+ this.firstName = firstName;
+ }
+
+ public String getLastName()
+ {
+ return lastName;
+ }
+
+ public void setLastName(String lastName)
+ {
+ this.lastName = lastName;
+ }
+
+ public String getTitle()
+ {
+ return title;
+ }
+
+ public void setTitle(String title)
+ {
+ this.title = title;
+ }
+
+ public String getInstitution()
+ {
+ return institution;
+ }
+
+ public void setInstitution(String institution)
+ {
+ this.institution = institution;
+ }
+
+ public String getReason()
+ {
+ return reason;
+ }
+
+ public void setReason(String reason)
+ {
+ this.reason = reason;
+ }
+ }
+
+ @RequiresPermission(AdminPermission.class)
+ public class ApproveUserRequestsAction extends MutatingApiAction
+ {
+ @Override
+ public void validateForm(ApproveUserRequestsForm form, Errors errors)
+ {
+ Container mccContainer = MccManager.get().getMCCContainer();
+ if (mccContainer == null)
+ {
+ errors.reject(ERROR_MSG, "The MCC project has not been set on this server. This is an administrator error.");
+ return;
+ }
+
+ if (form.getRequestIds() == null || form.getRequestIds().length == 0)
+ {
+ errors.reject(ERROR_MSG, "No request IDs provided");
+ }
+
+ TableInfo ti = MccSchema.getInstance().getSchema().getTable(MccSchema.TABLE_USER_REQUESTS);
+ for (int requestId : form.getRequestIds())
+ {
+ TableSelector ts = new TableSelector(ti, PageFlowUtil.set("userId"), new SimpleFilter(FieldKey.fromString("rowId"), requestId), null);
+ if (!ts.exists())
+ {
+ errors.reject(ERROR_MSG, "No request found for request ID: " + requestId);
+ break;
+ }
+ }
+ }
+
+ @Override
+ public Object execute(ApproveUserRequestsForm form, BindException errors) throws Exception
+ {
+ ApiSimpleResponse response = new ApiSimpleResponse();
+ MutableSecurityPolicy policy = new MutableSecurityPolicy(MccManager.get().getMCCContainer().getPolicy());
+ List newUserStatusList = new ArrayList<>();
+ List existingUsersGivenAccess = new ArrayList<>();
+ try (DbScope.Transaction transaction = CoreSchema.getInstance().getScope().ensureTransaction())
+ {
+ TableInfo ti = MccSchema.getInstance().getSchema().getTable(MccSchema.TABLE_USER_REQUESTS);
+ for (int requestId : form.getRequestIds())
+ {
+ TableSelector ts = new TableSelector(ti, new SimpleFilter(FieldKey.fromString("rowId"), requestId), null);
+ Map map = ts.getMap(requestId);
+
+ User u;
+ if (map.get("userId") != null)
+ {
+ Integer userId = (Integer)map.get("userId");
+ u = UserManager.getUser(userId);
+ existingUsersGivenAccess.add(u);
+ }
+ else
+ {
+ ValidEmail ve = new ValidEmail((String)map.get("email"));
+ u = UserManager.getUser(ve);
+ if (u != null)
+ {
+ existingUsersGivenAccess.add(u);
+ }
+ else
+ {
+ SecurityManager.NewUserStatus st = SecurityManager.addUser(ve, getUser());
+ u = st.getUser();
+ u.setFirstName((String)map.get("firstName"));
+ u.setLastName((String)map.get("lastName"));
+ UserManager.updateUser(getUser(), u);
+
+ if (st.isLdapEmail())
+ {
+ existingUsersGivenAccess.add(st.getUser());
+ }
+ else
+ {
+ newUserStatusList.add(st);
+ }
+ }
+ }
+
+ Map row = new HashMap<>();
+ row.put("rowId", requestId);
+ row.put("userId", u.getUserId());
+ Table.update(getUser(), ti, row, requestId);
+
+ if (!policy.hasPermission(u, ReadPermission.class))
+ {
+ policy.addRoleAssignment(u, ReaderRole.class);
+ }
+ else
+ {
+ _log.info("user already has read permission on MCC container: " + u.getDisplayName(getUser()));
+ }
+ }
+
+ SecurityPolicyManager.savePolicy(policy);
+
+ transaction.commit();
+ }
+
+ //send emails:
+ for (SecurityManager.NewUserStatus st : newUserStatusList)
+ {
+ SecurityManager.sendRegistrationEmail(getViewContext(), st.getEmail(), null, st, null);
+ }
+
+ for (User u : existingUsersGivenAccess)
+ {
+ Container mccContainer = MccManager.get().getMCCContainer();
+ boolean isLDAP = SecurityManager.isLdapEmail(new ValidEmail(u.getEmail()));
+
+ MailHelper.MultipartMessage mail = MailHelper.createMultipartMessage();
+ mail.setEncodedHtmlContent("Your account request has been approved for MCC! " + "Click here to access the site." + (isLDAP ? " Use your normal OHSU email/password to login." : ""));
+ mail.setFrom(getReplyEmail(getContainer()));
+ mail.setSubject("MCC Account Request");
+ mail.addRecipients(Message.RecipientType.TO, u.getEmail());
+
+ MailHelper.send(mail, getUser(), getContainer());
+ }
+
+ response.put("success", !errors.hasErrors());
+
+ return response;
+ }
+ }
+
+ private String getReplyEmail(Container c)
+ {
+ LookAndFeelProperties lfp = LookAndFeelProperties.getInstance(getContainer());
+ String email = lfp.getSystemEmailAddress();
+ if (email == null)
+ {
+ return AppProps.getInstance().getAdministratorContactEmail(true);
+ }
+
+ return email;
+ }
+
+ public static class ApproveUserRequestsForm
+ {
+ private int[] requestIds;
+
+ public int[] getRequestIds()
+ {
+ return requestIds;
+ }
+
+ public void setRequestIds(int[] requestIds)
+ {
+ this.requestIds = requestIds;
+ }
+ }
+
+ @RequiresNoPermission
+ @IgnoresTermsOfUse
+ @AllowedDuringUpgrade
+ public class RequestHelpAction extends MutatingApiAction
+ {
+ @Override
+ public void validateForm(RequestHelpForm form, Errors errors)
+ {
+ Container mccContainer = MccManager.get().getMCCContainer();
+ if (mccContainer == null)
+ {
+ errors.reject(ERROR_MSG, "The MCC project has not been set on this server. This is an administrator error.");
+ return;
+ }
+
+ if (StringUtils.isEmpty(form.getEmail()) || StringUtils.isEmpty(form.getComment()))
+ {
+ errors.reject(ERROR_REQUIRED, "Must provide both an email address and question/comment");
+ }
+ else
+ {
+ try
+ {
+ new ValidEmail(form.getEmail());
+ }
+ catch (ValidEmail.InvalidEmailException e)
+ {
+ errors.reject(ERROR_MSG, "Your email address is not valid. Please verify your email address below.");
+ }
+ }
+ }
+
+ @Override
+ public Object execute(RequestHelpForm form, BindException errors) throws Exception
+ {
+ Set users = MccManager.get().getNotificationUsers();
+ if (users != null && !users.isEmpty())
+ {
+ try
+ {
+ Set emails = new HashSet<>();
+ for (User u : users)
+ {
+ emails.add(new InternetAddress(u.getEmail()));
+ }
+
+ MailHelper.MultipartMessage mail = MailHelper.createMultipartMessage();
+ mail.setEncodedHtmlContent("A support request was submitted from MCC by: " + form.getEmail() + "
Message: " + form.getComment());
+ mail.setFrom(form.getEmail());
+ mail.setSubject("MCC Help Request");
+ mail.addRecipients(Message.RecipientType.TO, emails.toArray(new Address[0]));
+
+ MailHelper.send(mail, getUser(), getContainer());
+ }
+ catch (Exception e)
+ {
+ ExceptionUtil.logExceptionToMothership(null, e);
+ }
+ }
+ else
+ {
+ _log.error("A help request was received by MCC, but the admin emails have not been configured. The request from: " + form.getEmail());
+ _log.error(form.getComment());
+ }
+
+ return new ApiSimpleResponse("success", true);
+ }
+ }
+
+ public static class RequestHelpForm
+ {
+ private String _email;
+ private String _comment;
+
+ public String getEmail()
+ {
+ return _email;
+ }
+
+ public void setEmail(String email)
+ {
+ _email = email;
+ }
+
+ public String getComment()
+ {
+ return _comment;
+ }
+
+ public void setComment(String comment)
+ {
+ _comment = comment;
+ }
+ }
}
diff --git a/mcc/src/org/labkey/mcc/MccManager.java b/mcc/src/org/labkey/mcc/MccManager.java
index d6417b79d..d0f657de9 100644
--- a/mcc/src/org/labkey/mcc/MccManager.java
+++ b/mcc/src/org/labkey/mcc/MccManager.java
@@ -16,8 +16,28 @@
package org.labkey.mcc;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.labkey.api.data.Container;
+import org.labkey.api.data.ContainerManager;
+import org.labkey.api.module.Module;
+import org.labkey.api.module.ModuleLoader;
+import org.labkey.api.module.ModuleProperty;
+import org.labkey.api.security.User;
+import org.labkey.api.security.UserManager;
+import org.labkey.api.security.ValidEmail;
+
+import java.util.HashSet;
+import java.util.Set;
+
public class MccManager
{
+ private static final Logger _log = LogManager.getLogger(MccManager.class);
+
+ public static final String ContainerPropName = "MCCContainer";
+ public static final String NotifyPropName = "MCCContactUsers";
+
private static final MccManager _instance = new MccManager();
private MccManager()
@@ -29,4 +49,54 @@ public static MccManager get()
{
return _instance;
}
+
+ public Container getMCCContainer()
+ {
+ Module m = ModuleLoader.getInstance().getModule(MccModule.NAME);
+ ModuleProperty mp = m.getModuleProperties().get(MccManager.ContainerPropName);
+ String path = mp.getEffectiveValue(ContainerManager.getRoot());
+ if (path == null)
+ return null;
+
+ return ContainerManager.getForPath(path);
+ }
+
+ public Set getNotificationUsers()
+ {
+ Module m = ModuleLoader.getInstance().getModule(MccModule.NAME);
+ ModuleProperty mp = m.getModuleProperties().get(MccManager.NotifyPropName);
+ String userNames = mp.getEffectiveValue(ContainerManager.getRoot());
+ userNames = StringUtils.trimToNull(userNames);
+ if (userNames == null)
+ return null;
+
+ Set ret = new HashSet<>();
+ for (String username : userNames.split(","))
+ {
+ User u = UserManager.getUserByDisplayName(username);
+ if (u == null)
+ {
+ try
+ {
+ u = UserManager.getUser(new ValidEmail(username));
+ }
+ catch (ValidEmail.InvalidEmailException e)
+ {
+ //ignore
+ }
+ }
+
+ if (u == null)
+ {
+ _log.error("Unknown user registered for MCC notifcations: " + username);
+ }
+
+ if (u != null)
+ {
+ ret.add(u);
+ }
+ }
+
+ return ret;
+ }
}
\ No newline at end of file
diff --git a/mcc/src/org/labkey/mcc/MccModule.java b/mcc/src/org/labkey/mcc/MccModule.java
index 03182cb68..ea28010a7 100644
--- a/mcc/src/org/labkey/mcc/MccModule.java
+++ b/mcc/src/org/labkey/mcc/MccModule.java
@@ -41,7 +41,7 @@ public String getName()
@Override
public @Nullable Double getSchemaVersion()
{
- return 20.000;
+ return 20.001;
}
@Override
@@ -86,19 +86,5 @@ public Set getSchemaNames()
private void registerEHRResources()
{
EHRService.get().registerModule(this);
- //EHRService.get().registerTableCustomizer(this, ONPRC_EHRCustomizer.class);
-
- //Resource r = getModuleResource("/scripts/mcc/mcc_triggers.js");
- //assert r != null;
- //EHRService.get().registerTriggerScript(this, r);
-
- //EHRService.get().registerClientDependency(ClientDependency.supplierFromPath("Ext4"), this);
- //EHRService.get().registerClientDependency(ClientDependency.supplierFromPath("onprc_ehr/panel/BloodSummaryPanel.js"), this);
-
- //EHRService.get().registerReportLink(EHRService.REPORT_LINK_TYPE.housing, "List Single Housed Animals", this, DetailsURL.fromString("/query/executeQuery.view?schemaName=study&query.queryName=demographicsPaired&query.viewName=Single Housed"), "Commonly Used Queries");
- //EHRService.get().registerReportLink(EHRService.REPORT_LINK_TYPE.moreReports, "Clinical Snapshot Printable Report", this, DetailsURL.fromString("/onprc_ehr/SnapshotPrintableReport.view"), "Clinical");
-
- //EHRService.get().registerDemographicsProvider(new ActiveCasesDemographicsProvider(this));
- //EHRService.get().registerHistoryDataSource(new DefaultSustainedReleaseDatasource(this));
}
}
\ No newline at end of file
diff --git a/mcc/src/org/labkey/mcc/MccSchema.java b/mcc/src/org/labkey/mcc/MccSchema.java
index d072b94a9..be31ef6f1 100644
--- a/mcc/src/org/labkey/mcc/MccSchema.java
+++ b/mcc/src/org/labkey/mcc/MccSchema.java
@@ -25,6 +25,8 @@ public class MccSchema
private static final MccSchema _instance = new MccSchema();
public static final String NAME = "mcc";
+ public static final String TABLE_USER_REQUESTS = "userRequests";
+
public static MccSchema getInstance()
{
return _instance;
diff --git a/mcc/src/org/labkey/mcc/query/UserRequestCustomizer.java b/mcc/src/org/labkey/mcc/query/UserRequestCustomizer.java
new file mode 100644
index 000000000..7f789d464
--- /dev/null
+++ b/mcc/src/org/labkey/mcc/query/UserRequestCustomizer.java
@@ -0,0 +1,44 @@
+package org.labkey.mcc.query;
+
+import org.labkey.api.data.AbstractTableInfo;
+import org.labkey.api.data.JdbcType;
+import org.labkey.api.data.SQLFragment;
+import org.labkey.api.data.TableCustomizer;
+import org.labkey.api.data.TableInfo;
+import org.labkey.api.ldk.LDKService;
+import org.labkey.api.query.ExprColumn;
+
+public class UserRequestCustomizer implements TableCustomizer
+{
+ @Override
+ public void customize(TableInfo tableInfo)
+ {
+ LDKService.get().getDefaultTableCustomizer().customize(tableInfo);
+
+ if (tableInfo instanceof AbstractTableInfo)
+ {
+ addUserCol((AbstractTableInfo)tableInfo);
+ }
+ }
+
+ public void addUserCol(AbstractTableInfo ti)
+ {
+ String colName = "hasAccess";
+ if (ti.getColumn(colName) != null)
+ {
+ return;
+ }
+
+ ExprColumn col = new ExprColumn(ti, colName, new SQLFragment("(CASE WHEN (exists (" +
+ "select u.rowid from mcc.userrequests u " +
+ "left join core.RoleAssignments ra " +
+ "on (u.userid = ra.UserId AND u.container = ra.ResourceId) " +
+ "WHERE ra.Role = 'org.labkey.api.security.roles.ReaderRole' AND u.rowid = " + ExprColumn.STR_TABLE_ALIAS + ".rowid " +
+ ")) THEN " + ti.getSqlDialect().getBooleanTRUE() + " ELSE " + ti.getSqlDialect().getBooleanFALSE() + " END)"), JdbcType.BOOLEAN, ti.getColumn("userId"));
+ col.setLabel("Has MCC Access?");
+ col.setReadOnly(true);
+ col.setIsUnselectable(true);
+ col.setUserEditable(false);
+ ti.addColumn(col);
+ }
+}
diff --git a/primeseq/resources/views/geneticsMenu.html b/primeseq/resources/views/geneticsMenu.html
index 3ff3df451..5177cd413 100644
--- a/primeseq/resources/views/geneticsMenu.html
+++ b/primeseq/resources/views/geneticsMenu.html
@@ -44,7 +44,7 @@
'data-qtip="You do not have permission to view this page"',
'style="width: 300px;height: auto;" class="thumb-wrap thumb-wrap-side">',
'',
- '{title:htmlEncode}',
+ '{title:htmlEncode}',
'',
'',
'',
@@ -88,15 +88,15 @@
items: [{
title: 'Public Resources',
itemId: 'public'
- },{
- itemId: 'collaborations',
- title: 'Collaborations'
},{
title: 'Labs',
itemId: 'labs'
},{
itemId: 'internal',
title: 'Internal Projects'
+ },{
+ itemId: 'collaborations',
+ title: 'Bimber Lab Collaborations'
}]
});
diff --git a/primeseq/src/org/labkey/primeseq/PrimeseqController.java b/primeseq/src/org/labkey/primeseq/PrimeseqController.java
index bc680d82c..de1835eab 100644
--- a/primeseq/src/org/labkey/primeseq/PrimeseqController.java
+++ b/primeseq/src/org/labkey/primeseq/PrimeseqController.java
@@ -30,6 +30,8 @@
import org.labkey.api.data.ContainerType;
import org.labkey.api.module.Module;
import org.labkey.api.module.ModuleLoader;
+import org.labkey.api.pipeline.PipeRoot;
+import org.labkey.api.pipeline.PipelineService;
import org.labkey.api.pipeline.PipelineUrls;
import org.labkey.api.security.RequiresPermission;
import org.labkey.api.security.RequiresSiteAdmin;
@@ -39,6 +41,7 @@
import org.labkey.api.util.URLHelper;
import org.labkey.api.view.ActionURL;
import org.labkey.api.view.HtmlView;
+import org.labkey.primeseq.pipeline.MhcMigrationPipelineJob;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.servlet.ModelAndView;
@@ -68,7 +71,7 @@ public ApiResponse execute(Object form, BindException errors)
{
Map resultProperties = new HashMap<>();
- resultProperties.put("collaborations", getSection("/Public/Collaborations"));
+ resultProperties.put("collaborations", getSection("/Labs/Bimber/Collaborations"));
resultProperties.put("internal", getSection("/Internal"));
resultProperties.put("labs", getSection("/Labs"));
@@ -125,12 +128,6 @@ private List getSection(String path)
{
for (Container c : mainContainer.getChildren())
{
- //NOTE: unlike EHR, omit children if the current user cannot read them
- if (!c.hasPermission(getUser(), ReadPermission.class))
- {
- continue;
- }
-
JSONObject json = new JSONObject();
json.put("name", c.getName());
json.put("title", c.getTitle());
@@ -203,4 +200,48 @@ public URLHelper getSuccessURL(Object o)
}
}
+ @RequiresSiteAdmin
+ public class SyncMhcAction extends ConfirmAction