Skip to content

Commit b79ea18

Browse files
committed
Expand study/assignment fields
1 parent 30eda78 commit b79ea18

File tree

6 files changed

+239
-11
lines changed

6 files changed

+239
-11
lines changed

Studies/resources/module.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,11 @@
22
<clientDependencies>
33
<dependency path="LDK.context"/>
44
</clientDependencies>
5+
<properties>
6+
<propertyDescriptor name="demographicsDefaultView">
7+
<description>This view is used as the default on the demographics table</description>
8+
<canSetPerContainer>true</canSetPerContainer>
9+
<label>Demographics Default View</label>
10+
</propertyDescriptor>
11+
</properties>
512
</module>

Studies/resources/views/manageStudy.html

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,57 @@
1313

1414
const parentDiv = $('#' + webpart.wrapperDivId)
1515
parentDiv.append('<div id="' + webpart.wrapperDivId + '-studyCohorts"></div>')
16+
parentDiv.append('<div id="' + webpart.wrapperDivId + '-demographics"></div>')
1617
parentDiv.append('<div id="' + webpart.wrapperDivId + '-anchorEvents"></div>')
1718
parentDiv.append('<div id="' + webpart.wrapperDivId + '-expectedTimepoints"></div>')
1819

1920
LDK.Utils.getBasicQWP({
2021
frame: 'portal',
2122
title: 'Cohorts',
22-
name: 'query',
23+
name: 'query-studies',
2324
schemaName: 'studies',
2425
queryName: 'studyCohorts',
2526
filterArray: [LABKEY.Filter.create('studyId',studyId, LABKEY.Filter.Types.EQUAL)],
2627
maxRows: 20,
2728
renderTo: webpart.wrapperDivId + '-studyCohorts'
2829
}).render();
2930

31+
if (LABKEY.getModuleContext('study')?.subject) {
32+
const hasAssignmentDataset = !!LABKEY.getModuleContext('studies')?.hasAssignmentDataset
33+
if (hasAssignmentDataset) {
34+
LABKEY.Query.selectRows({
35+
schemaName: 'studies',
36+
queryName: 'studies',
37+
columns: 'rowId,studyName',
38+
filterArray: [LABKEY.Filter.create('rowId', studyId)],
39+
failure: LDK.Utils.getErrorCallback(),
40+
success: function(results) {
41+
const studyName = results.rows?.[0].studyName;
42+
LDK.Assert.assertNotEmpty('StudyName was empty in manageStudy.view', studyName);
43+
44+
const projectFieldName = 'allProjectsPivot/' + studyName + '::lastStartDate';
45+
const demographicsDefaultView = LABKEY.getModuleProperty('studies', 'demographicsDefaultView')
46+
LDK.Utils.getBasicQWP({
47+
frame: 'portal',
48+
title: LABKEY.moduleContext.study?.subject.nounPlural,
49+
name: 'query-demographics',
50+
schemaName: 'study',
51+
queryName: 'demographics',
52+
viewName: demographicsDefaultView,
53+
filterArray: [LABKEY.Filter.create(projectFieldName, null, LABKEY.Filter.Types.NONBLANK)],
54+
maxRows: 20,
55+
renderTo: webpart.wrapperDivId + '-demographics'
56+
}).render();
57+
},
58+
scope: this
59+
});
60+
}
61+
}
62+
3063
LDK.Utils.getBasicQWP({
3164
frame: 'portal',
3265
title: 'Anchor Events',
33-
name: 'query',
66+
name: 'query-anchorEvents',
3467
schemaName: 'studies',
3568
queryName: 'anchorEvents',
3669
filterArray: [LABKEY.Filter.create('studyId',studyId, LABKEY.Filter.Types.EQUAL)],
@@ -41,7 +74,7 @@
4174
LDK.Utils.getBasicQWP({
4275
frame: 'portal',
4376
title: 'Expected Timepoints',
44-
name: 'query',
77+
name: 'query-expectedTimepoints',
4578
schemaName: 'studies',
4679
queryName: 'expectedTimepoints',
4780
filterArray: [LABKEY.Filter.create('studyId',studyId, LABKEY.Filter.Types.EQUAL)],
@@ -54,6 +87,4 @@
5487

5588
</script>
5689

57-
58-
PLACEHOLDER: Make a page that accepts a studyId and renders useful UI to manage timepoints, run QC, and show data
5990
<br><br>

Studies/src/org/labkey/studies/StudiesModule.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.jetbrains.annotations.NotNull;
44
import org.jetbrains.annotations.Nullable;
5+
import org.json.JSONObject;
56
import org.labkey.api.data.Container;
67
import org.labkey.api.laboratory.LaboratoryService;
78
import org.labkey.api.ldk.ExtendedSimpleModule;
@@ -11,9 +12,9 @@
1112
import org.labkey.api.query.QuerySchema;
1213
import org.labkey.api.security.roles.RoleManager;
1314
import org.labkey.api.studies.StudiesService;
14-
import org.labkey.api.util.PageFlowUtil;
15-
import org.labkey.studies.query.StudiesUserSchema;
1615
import org.labkey.api.studies.security.StudiesDataAdminRole;
16+
import org.labkey.api.writer.ContainerUser;
17+
import org.labkey.studies.query.StudiesUserSchema;
1718
import org.labkey.studies.study.StudiesFilterProvider;
1819
import org.labkey.studies.study.StudyEnrollmentEventProvider;
1920

@@ -68,6 +69,16 @@ public Set<String> getSchemaNames()
6869
return Collections.singleton(StudiesSchema.NAME);
6970
}
7071

72+
@Override
73+
public JSONObject getPageContextJson(ContainerUser context)
74+
{
75+
JSONObject json = super.getPageContextJson(context);
76+
77+
json.put("hasAssignmentDataset", StudiesServiceImpl.get().hasAssignmentDataset(context.getContainer()));
78+
79+
return json;
80+
}
81+
7182
@Override
7283
public void registerSchemas()
7384
{

Studies/src/org/labkey/studies/StudiesServiceImpl.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import org.labkey.api.security.User;
2222
import org.labkey.api.studies.StudiesService;
2323
import org.labkey.api.studies.study.EventProvider;
24+
import org.labkey.api.study.Study;
25+
import org.labkey.api.study.StudyService;
2426
import org.labkey.api.util.ConfigurationException;
2527
import org.labkey.api.util.FileUtil;
2628
import org.labkey.api.util.Path;
@@ -173,4 +175,17 @@ public TableCustomizer getStudiesTableCustomizer()
173175
{
174176
return new StudiesTableCustomizer();
175177
}
178+
179+
public static String ASSIGNMENT_DATASET = "assignment";
180+
181+
public boolean hasAssignmentDataset(Container c)
182+
{
183+
Study s = StudyService.get().getStudy(c.isWorkbookOrTab() ? c.getParent() : c);
184+
if (s == null)
185+
{
186+
return false;
187+
}
188+
189+
return s.getDatasetByName(ASSIGNMENT_DATASET) != null;
190+
}
176191
}

Studies/src/org/labkey/studies/query/StudiesTableCustomizer.java

Lines changed: 159 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,32 @@
11
package org.labkey.studies.query;
22

33
import org.apache.commons.collections4.MultiValuedMap;
4-
import org.apache.commons.collections4.SetValuedMap;
54
import org.apache.logging.log4j.Logger;
65
import org.labkey.api.collections.CaseInsensitiveHashMap;
7-
86
import org.labkey.api.collections.CaseInsensitiveKeyedHashSetValuedMap;
97
import org.labkey.api.data.AbstractTableInfo;
8+
import org.labkey.api.data.BaseColumnInfo;
9+
import org.labkey.api.data.ColumnInfo;
10+
import org.labkey.api.data.Container;
11+
import org.labkey.api.data.MutableColumnInfo;
1012
import org.labkey.api.data.TableCustomizer;
1113
import org.labkey.api.data.TableInfo;
1214
import org.labkey.api.ldk.LDKService;
15+
import org.labkey.api.query.ExprColumn;
16+
import org.labkey.api.query.FieldKey;
17+
import org.labkey.api.query.LookupForeignKey;
18+
import org.labkey.api.query.QueryDefinition;
19+
import org.labkey.api.query.QueryException;
20+
import org.labkey.api.query.QueryService;
21+
import org.labkey.api.query.UserSchema;
1322
import org.labkey.api.study.DatasetTable;
23+
import org.labkey.api.study.Study;
24+
import org.labkey.api.study.StudyService;
1425
import org.labkey.api.util.logging.LogHelper;
26+
import org.labkey.studies.StudiesServiceImpl;
27+
28+
import java.util.ArrayList;
29+
import java.util.List;
1530

1631
public class StudiesTableCustomizer implements TableCustomizer
1732
{
@@ -61,7 +76,148 @@ else if (tableInfo instanceof DatasetTable ds)
6176
private void doCustomize(AbstractTableInfo ati)
6277
{
6378
// TODO:
64-
// Overlapping studies/cohorts
6579
// TimepointLabel
80+
81+
addProjectAssignmentColumns(ati);
82+
}
83+
84+
private String getSubjectColName(Container c)
85+
{
86+
Study s = StudyService.get().getStudy(c.isWorkbookOrTab() ? c.getParent() : c);
87+
if (s == null)
88+
{
89+
return null;
90+
}
91+
92+
return s.getSubjectColumnName();
93+
}
94+
95+
private void addProjectAssignmentColumns(AbstractTableInfo ati)
96+
{
97+
final String pivotColName = "allProjectsPivot";
98+
if (ati.getColumn(pivotColName) != null)
99+
return;
100+
101+
List<ColumnInfo> pks = ati.getPkColumns();
102+
ColumnInfo pk;
103+
if (pks.size() == 1)
104+
{
105+
pk = pks.get(0);
106+
}
107+
else
108+
{
109+
if (! (ati instanceof DatasetTable))
110+
{
111+
_log.error("Table does not have a single PK column: " + ati.getName());
112+
return;
113+
}
114+
else
115+
{
116+
pk = pks.get(0);
117+
}
118+
}
119+
120+
if (!StudiesServiceImpl.get().hasAssignmentDataset(ati.getUserSchema().getContainer()))
121+
{
122+
return;
123+
}
124+
125+
final String subjectSelectName = getSubjectColName(ati.getUserSchema().getContainer());
126+
if (subjectSelectName == null)
127+
{
128+
_log.error("Unable to find subjectSelectName in StudiesTableCustomizer");
129+
return;
130+
}
131+
132+
final String pkColSelectName = pk.getFieldKey().toSQLString();
133+
134+
final String lookupName = ati.getName() + "_allProjectsPivot";
135+
BaseColumnInfo col2 = new ExprColumn(ati, FieldKey.fromString(pivotColName), pk.getValueSql(ExprColumn.STR_TABLE_ALIAS), pk.getJdbcType(), pk);
136+
col2.setLabel("Assignment By Study");
137+
col2.setName(pivotColName);
138+
col2.setCalculated(true);
139+
col2.setShownInInsertView(false);
140+
col2.setShownInUpdateView(false);
141+
col2.setDescription("Shows groups to which this subject belonged at any point in time.");
142+
col2.setHidden(true);
143+
col2.setReadOnly(true);
144+
col2.setIsUnselectable(true);
145+
col2.setUserEditable(false);
146+
col2.setKeyField(false);
147+
col2.setFk(new LookupForeignKey(){
148+
@Override
149+
public TableInfo getLookupTableInfo()
150+
{
151+
final UserSchema us = ati.getUserSchema();
152+
Container target = us.getContainer().isWorkbookOrTab() ? us.getContainer().getParent() : us.getContainer();
153+
QueryDefinition qd = createQueryDef(us, lookupName);
154+
155+
qd.setSql(getAssignmentPivotSql(target, ati, pkColSelectName, subjectSelectName));
156+
qd.setIsTemporary(true);
157+
158+
List<QueryException> errors = new ArrayList<>();
159+
TableInfo ti = qd.getTable(errors, true);
160+
161+
if (!errors.isEmpty()){
162+
_log.error("Problem with table customizer: " + ati.getPublicName());
163+
for (QueryException e : errors)
164+
{
165+
_log.error(e.getMessage());
166+
}
167+
}
168+
169+
if (ti != null)
170+
{
171+
MutableColumnInfo col = (MutableColumnInfo) ti.getColumn(pk.getName());
172+
col.setKeyField(true);
173+
col.setHidden(true);
174+
175+
((MutableColumnInfo)ti.getColumn("lastStartDate")).setLabel("Most Recent Assignment Date");
176+
}
177+
178+
return ti;
179+
}
180+
});
181+
182+
ati.addColumn(col2);
183+
}
184+
185+
private String getAssignmentPivotSql(Container source, final AbstractTableInfo ati, String pkColSelectName, String subjectSelectName)
186+
{
187+
return "SELECT\n" +
188+
"s." + pkColSelectName + ",\n" +
189+
"p.study,\n" +
190+
"max(p.date) as lastStartDate\n" +
191+
"\n" +
192+
"FROM " + ati.getPublicSchemaName() + "." + ati.getPublicName() + " s\n" +
193+
"JOIN \"" + source.getPath() + "\".study.assignment p\n" +
194+
"ON (s." + subjectSelectName + " = p." + subjectSelectName + ")\n" +
195+
"WHERE s." + subjectSelectName + " IS NOT NULL\n" +
196+
"\n" +
197+
"GROUP BY s." + pkColSelectName + ", p.study\n" +
198+
"PIVOT lastStartDate by study IN (select distinct studyName from studies.studies)";
199+
}
200+
201+
// TODO: move to parent class
202+
protected QueryDefinition createQueryDef(UserSchema us, String queryName)
203+
{
204+
if (!us.getContainer().isWorkbook())
205+
{
206+
return QueryService.get().createQueryDef(us.getUser(), us.getContainer(), us, queryName);
207+
}
208+
209+
// The rationale is that if we are querying from a workbook, preferentially translate to the parent US
210+
// However, there are situations like workbook-scoped lists, where that query might not exist on the parent
211+
UserSchema parentUserSchema = QueryService.get().getUserSchema(us.getUser(), us.getContainer().getParent(), us.getSchemaPath());
212+
assert parentUserSchema != null;
213+
214+
if (parentUserSchema.getTableNames().contains(queryName))
215+
{
216+
return QueryService.get().createQueryDef(parentUserSchema.getUser(), parentUserSchema.getContainer(), parentUserSchema, queryName);
217+
}
218+
else
219+
{
220+
return QueryService.get().createQueryDef(us.getUser(), us.getContainer(), us, queryName);
221+
}
66222
}
67223
}

Studies/src/org/labkey/studies/query/StudiesUserSchema.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,17 @@ private TableInfo createCohortsTable(String name, ContainerFilter cf)
179179
col2.setLabel("Cohort Name");
180180
col2.setHidden(true);
181181
col2.setDescription("This column lists the cohort label, and the name if label is blank");
182-
183182
ret.addColumn(col2);
184183

184+
SQLFragment coalesce = new SQLFragment("coalesce(" + ExprColumn.STR_TABLE_ALIAS + ".label, " + ExprColumn.STR_TABLE_ALIAS + ".cohortName)");
185+
SQLFragment select = new SQLFragment("(SELECT studyName FROM " + StudiesSchema.NAME).append(".").append(TABLE_STUDIES).append(" s WHERE s.rowId = ").append(ExprColumn.STR_TABLE_ALIAS).append(".studyId)");
186+
SQLFragment sql3 = ret.getSqlDialect().concatenate(select, new SQLFragment("': '"), coalesce);
187+
ExprColumn col3 = new ExprColumn(ret, "studyAndCohort", sql3, JdbcType.VARCHAR, ret.getColumn("cohortName"), ret.getColumn("label"), ret.getColumn("rowId"));
188+
col3.setLabel("Study/Cohort");
189+
col3.setHidden(true);
190+
col3.setDescription("This column lists the study ID and cohort label");
191+
ret.addColumn(col3);
192+
185193
return ret;
186194
}
187195

0 commit comments

Comments
 (0)