Skip to content

Commit 91b1ce6

Browse files
Merge 26.3 to develop
2 parents 6857e69 + d88ccb7 commit 91b1ce6

5 files changed

Lines changed: 199 additions & 1 deletion

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright (c) 2026 LabKey Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.labkey.api.ehr.buttons;
17+
18+
import org.labkey.api.data.TableInfo;
19+
import org.labkey.api.ehr.security.EHRDataAdminPermission;
20+
import org.labkey.api.ldk.table.SimpleButtonConfigFactory;
21+
import org.labkey.api.module.Module;
22+
23+
public class DiscardEmptyTasksButton extends SimpleButtonConfigFactory
24+
{
25+
public DiscardEmptyTasksButton(Module owner)
26+
{
27+
super(owner, "Delete Empty Tasks", "EHR.DatasetButtons.discardEmptyTasks(dataRegionName);");
28+
}
29+
30+
@Override
31+
public boolean isAvailable(TableInfo ti)
32+
{
33+
return super.isAvailable(ti) && ti.getUserSchema().getContainer().hasPermission(ti.getUserSchema().getUser(), EHRDataAdminPermission.class);
34+
}
35+
}

ehr/resources/web/ehr/studyButtons.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,34 @@ EHR.DatasetButtons = new function () {
266266
}, this);
267267
},
268268

269+
discardEmptyTasks: function (dataRegionName) {
270+
var dataRegion = LABKEY.DataRegions[dataRegionName];
271+
var checked = dataRegion.getChecked();
272+
if (!checked || !checked.length) {
273+
Ext4.Msg.alert('Error', 'No records selected');
274+
return;
275+
}
276+
277+
Ext4.Msg.confirm('Delete Empty Tasks',
278+
'Permanently delete the selected task(s)? This will fail if any selected task has related data in a study dataset. Note: Task related data in other schemas is not checked.',
279+
function (val) {
280+
if (val !== 'yes') return;
281+
Ext4.Msg.wait('Deleting...');
282+
LABKEY.Ajax.request({
283+
url: LABKEY.ActionURL.buildURL('ehr', 'discardEmptyTasks', null, {taskIds: checked}),
284+
method: 'POST',
285+
success: function (response) {
286+
Ext4.Msg.hide();
287+
var json = LABKEY.Utils.decode(response.responseText) || {};
288+
Ext4.Msg.alert('Success', 'Deleted ' + (json.deletedCount || 0) + ' task(s).');
289+
dataRegion.refresh();
290+
},
291+
failure: LDK.Utils.getErrorCallback(),
292+
scope: this
293+
});
294+
}, this);
295+
},
296+
269297
limitKinshipSelection: function (dataRegionName) {
270298
var dataRegion = LABKEY.DataRegions[dataRegionName];
271299

ehr/src/org/labkey/ehr/EHRController.java

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.labkey.api.ehr.dataentry.DataEntryForm;
5050
import org.labkey.api.ehr.demographics.AnimalRecord;
5151
import org.labkey.api.ehr.history.HistoryRow;
52+
import org.labkey.api.ehr.security.EHRDataAdminPermission;
5253
import org.labkey.api.ehr.security.EHRDataEntryPermission;
5354
import org.labkey.api.exp.api.ExperimentService;
5455
import org.labkey.api.gwt.client.AuditBehaviorType;
@@ -60,6 +61,7 @@
6061
import org.labkey.api.query.BatchValidationException;
6162
import org.labkey.api.query.DetailsURL;
6263
import org.labkey.api.query.FieldKey;
64+
import org.labkey.api.query.InvalidKeyException;
6365
import org.labkey.api.query.QueryAction;
6466
import org.labkey.api.query.QueryForm;
6567
import org.labkey.api.query.QueryParseException;
@@ -331,6 +333,137 @@ public ApiResponse execute(DiscardFormForm form, BindException errors)
331333
}
332334
}
333335

336+
public static class DiscardEmptyTasksForm
337+
{
338+
private String[] taskIds;
339+
340+
public String[] getTaskIds()
341+
{
342+
return taskIds;
343+
}
344+
345+
public void setTaskIds(String[] taskIds)
346+
{
347+
this.taskIds = taskIds;
348+
}
349+
}
350+
351+
@RequiresPermission(EHRDataAdminPermission.class)
352+
public static class DiscardEmptyTasksAction extends MutatingApiAction<DiscardEmptyTasksForm>
353+
{
354+
private List<String> _selectedTaskIds;
355+
private TableInfo _tasksTable;
356+
357+
@Override
358+
public void validateForm(DiscardEmptyTasksForm form, Errors errors)
359+
{
360+
super.validateForm(form, errors);
361+
362+
if (form.getTaskIds() == null || form.getTaskIds().length == 0)
363+
{
364+
errors.reject(ERROR_MSG, "No tasks selected.");
365+
return;
366+
}
367+
_selectedTaskIds = Arrays.asList(form.getTaskIds());
368+
369+
UserSchema ehrSchema = QueryService.get().getUserSchema(getUser(), getContainer(), EHRSchema.EHR_SCHEMANAME);
370+
if (ehrSchema == null)
371+
{
372+
errors.reject(ERROR_MSG, "EHR schema is not available in this container.");
373+
return;
374+
}
375+
376+
_tasksTable = ehrSchema.getTable(EHRSchema.TABLE_TASKS);
377+
if (_tasksTable == null)
378+
{
379+
errors.reject(ERROR_MSG, "ehr.tasks table is not available in this container.");
380+
return;
381+
}
382+
383+
Set<String> nonEmpty = new LinkedHashSet<>();
384+
UserSchema studySchema = QueryService.get().getUserSchema(getUser(), getContainer(), "study");
385+
if (studySchema != null)
386+
{
387+
TableInfo studyData = studySchema.getTable("StudyData");
388+
if (studyData != null && studyData.getColumn("taskid") != null)
389+
{
390+
SimpleFilter filter = new SimpleFilter(FieldKey.fromString("taskid"), _selectedTaskIds, CompareType.IN);
391+
String[] ids = new TableSelector(studyData, Collections.singleton("taskid"), filter, null).getArray(String.class);
392+
if (ids != null)
393+
{
394+
for (String id : ids)
395+
{
396+
if (id != null)
397+
nonEmpty.add(id);
398+
}
399+
}
400+
}
401+
}
402+
403+
if (!nonEmpty.isEmpty())
404+
{
405+
errors.reject(ERROR_MSG, "Cannot delete: " + nonEmpty.size() + " of the selected task(s) have associated dataset records. Task ID(s): " + String.join(", ", nonEmpty));
406+
}
407+
}
408+
409+
@Override
410+
public ApiResponse execute(DiscardEmptyTasksForm form, BindException errors)
411+
{
412+
int deleted;
413+
try (DbScope.Transaction transaction = ExperimentService.get().ensureTransaction())
414+
{
415+
deleted = deleteRowsByTaskIds(_tasksTable, _selectedTaskIds);
416+
transaction.commit();
417+
}
418+
catch (SQLException e)
419+
{
420+
throw new RuntimeSQLException(e);
421+
}
422+
423+
Map<String, Object> resultProperties = new HashMap<>();
424+
resultProperties.put("success", true);
425+
resultProperties.put("deletedCount", deleted);
426+
return new ApiSimpleResponse(resultProperties);
427+
}
428+
429+
private int deleteRowsByTaskIds(TableInfo ti, List<String> taskIds) throws SQLException
430+
{
431+
QueryUpdateService qus = ti.getUpdateService();
432+
if (qus == null)
433+
return 0;
434+
435+
int total = 0;
436+
final int chunkSize = 1000;
437+
for (int start = 0; start < taskIds.size(); start += chunkSize)
438+
{
439+
List<String> chunk = taskIds.subList(start, Math.min(start + chunkSize, taskIds.size()));
440+
SimpleFilter filter = new SimpleFilter(FieldKey.fromString("taskid"), chunk, CompareType.IN);
441+
String[] pkColumns = ti.getPkColumnNames().toArray(new String[0]);
442+
Set<String> selectCols = new LinkedHashSet<>(Arrays.asList(pkColumns));
443+
selectCols.add("taskid");
444+
445+
Map<String, Object>[] rows = new TableSelector(ti, selectCols, filter, null).getMapArray();
446+
if (rows == null || rows.length == 0)
447+
continue;
448+
449+
List<Map<String, Object>> keys = new ArrayList<>(rows.length);
450+
for (Map<String, Object> row : rows)
451+
keys.add(new HashMap<>(row));
452+
453+
try
454+
{
455+
qus.deleteRows(getUser(), getContainer(), keys, null, new HashMap<>());
456+
}
457+
catch (InvalidKeyException | QueryUpdateServiceException | BatchValidationException e)
458+
{
459+
throw new RuntimeException(e);
460+
}
461+
total += rows.length;
462+
}
463+
return total;
464+
}
465+
}
466+
334467
public static class EHRQueryForm extends QueryForm
335468
{
336469
private boolean _showImport = false;

ehr/src/org/labkey/ehr/EHRModule.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.labkey.api.data.UpgradeCode;
2626
import org.labkey.api.ehr.EHRDemographicsService;
2727
import org.labkey.api.ehr.EHRService;
28+
import org.labkey.api.ehr.buttons.DiscardEmptyTasksButton;
2829
import org.labkey.api.ehr.buttons.EHRShowEditUIButton;
2930
import org.labkey.api.ehr.buttons.MarkCompletedButton;
3031
import org.labkey.api.ehr.demographics.ActiveAssignmentsDemographicsProvider;
@@ -249,6 +250,7 @@ public void moduleStartupComplete(ServletContext servletContext)
249250
EHRService.get().registerMoreActionsButton(new CompareWeightsButton(this), "study", "weight");
250251
EHRService.get().registerMoreActionsButton(new TaskAssignButton(this), "ehr", "my_tasks");
251252
EHRService.get().registerMoreActionsButton(new TaskAssignButton(this), "ehr", "tasks");
253+
EHRService.get().registerMoreActionsButton(new DiscardEmptyTasksButton(this), "ehr", "tasks");
252254
EHRService.get().registerMoreActionsButton(new MarkCompletedButton(this, "study", "treatment_order", "Set End Date"), "study", "treatment_order");
253255
EHRService.get().registerMoreActionsButton(new MarkCompletedButton(this, "study", "problem", "End Problem(s)", true), "study", "problem");
254256
EHRService.get().registerMoreActionsButton(new MarkCompletedButton(this, "study", "feeding"), "study", "feeding");

ehr/test/src/org/labkey/test/tests/ehr/AbstractEHRTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ protected void populateInitialData() throws Exception
477477
repopulate("All");
478478
}
479479

480-
private void setFormatStrings()
480+
protected void setFormatStrings()
481481
{
482482
navigateToFolder(getProjectName(), getFolderName());
483483
goToProjectSettings();

0 commit comments

Comments
 (0)