Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2026 LabKey Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.labkey.api.ehr.buttons;

import org.labkey.api.data.TableInfo;
import org.labkey.api.ehr.security.EHRDataAdminPermission;
import org.labkey.api.ldk.table.SimpleButtonConfigFactory;
import org.labkey.api.module.Module;

public class DiscardEmptyTasksButton extends SimpleButtonConfigFactory
{
public DiscardEmptyTasksButton(Module owner)
{
super(owner, "Delete Empty Tasks", "EHR.DatasetButtons.discardEmptyTasks(dataRegionName);");
}

@Override
public boolean isAvailable(TableInfo ti)
{
return super.isAvailable(ti) && ti.getUserSchema().getContainer().hasPermission(ti.getUserSchema().getUser(), EHRDataAdminPermission.class);
}
}
28 changes: 28 additions & 0 deletions ehr/resources/web/ehr/studyButtons.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,34 @@ EHR.DatasetButtons = new function () {
}, this);
},

discardEmptyTasks: function (dataRegionName) {
var dataRegion = LABKEY.DataRegions[dataRegionName];
var checked = dataRegion.getChecked();
if (!checked || !checked.length) {
Ext4.Msg.alert('Error', 'No records selected');
return;
}

Ext4.Msg.confirm('Delete Empty Tasks',
'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.',
function (val) {
if (val !== 'yes') return;
Ext4.Msg.wait('Deleting...');
LABKEY.Ajax.request({
url: LABKEY.ActionURL.buildURL('ehr', 'discardEmptyTasks', null, {taskIds: checked}),
method: 'POST',
success: function (response) {
Ext4.Msg.hide();
var json = LABKEY.Utils.decode(response.responseText) || {};
Ext4.Msg.alert('Success', 'Deleted ' + (json.deletedCount || 0) + ' task(s).');
dataRegion.refresh();
},
failure: LDK.Utils.getErrorCallback(),
scope: this
});
}, this);
},

limitKinshipSelection: function (dataRegionName) {
var dataRegion = LABKEY.DataRegions[dataRegionName];

Expand Down
133 changes: 133 additions & 0 deletions ehr/src/org/labkey/ehr/EHRController.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.labkey.api.ehr.dataentry.DataEntryForm;
import org.labkey.api.ehr.demographics.AnimalRecord;
import org.labkey.api.ehr.history.HistoryRow;
import org.labkey.api.ehr.security.EHRDataAdminPermission;
import org.labkey.api.ehr.security.EHRDataEntryPermission;
import org.labkey.api.exp.api.ExperimentService;
import org.labkey.api.gwt.client.AuditBehaviorType;
Expand All @@ -60,6 +61,7 @@
import org.labkey.api.query.BatchValidationException;
import org.labkey.api.query.DetailsURL;
import org.labkey.api.query.FieldKey;
import org.labkey.api.query.InvalidKeyException;
import org.labkey.api.query.QueryAction;
import org.labkey.api.query.QueryForm;
import org.labkey.api.query.QueryParseException;
Expand Down Expand Up @@ -331,6 +333,137 @@ public ApiResponse execute(DiscardFormForm form, BindException errors)
}
}

public static class DiscardEmptyTasksForm
{
private String[] taskIds;

public String[] getTaskIds()
{
return taskIds;
}

public void setTaskIds(String[] taskIds)
{
this.taskIds = taskIds;
}
}

@RequiresPermission(EHRDataAdminPermission.class)
public static class DiscardEmptyTasksAction extends MutatingApiAction<DiscardEmptyTasksForm>
{
private List<String> _selectedTaskIds;
private TableInfo _tasksTable;

@Override
public void validateForm(DiscardEmptyTasksForm form, Errors errors)
{
super.validateForm(form, errors);

if (form.getTaskIds() == null || form.getTaskIds().length == 0)
{
errors.reject(ERROR_MSG, "No tasks selected.");
return;
}
_selectedTaskIds = Arrays.asList(form.getTaskIds());

UserSchema ehrSchema = QueryService.get().getUserSchema(getUser(), getContainer(), EHRSchema.EHR_SCHEMANAME);
if (ehrSchema == null)
{
errors.reject(ERROR_MSG, "EHR schema is not available in this container.");
return;
}

_tasksTable = ehrSchema.getTable(EHRSchema.TABLE_TASKS);
if (_tasksTable == null)
{
errors.reject(ERROR_MSG, "ehr.tasks table is not available in this container.");
return;
}

Set<String> nonEmpty = new LinkedHashSet<>();
UserSchema studySchema = QueryService.get().getUserSchema(getUser(), getContainer(), "study");
if (studySchema != null)
{
TableInfo studyData = studySchema.getTable("StudyData");
if (studyData != null && studyData.getColumn("taskid") != null)
{
SimpleFilter filter = new SimpleFilter(FieldKey.fromString("taskid"), _selectedTaskIds, CompareType.IN);
String[] ids = new TableSelector(studyData, Collections.singleton("taskid"), filter, null).getArray(String.class);
if (ids != null)
{
for (String id : ids)
{
if (id != null)
nonEmpty.add(id);
}
}
}
}

if (!nonEmpty.isEmpty())
{
errors.reject(ERROR_MSG, "Cannot delete: " + nonEmpty.size() + " of the selected task(s) have associated dataset records. Task ID(s): " + String.join(", ", nonEmpty));
}
}

@Override
public ApiResponse execute(DiscardEmptyTasksForm form, BindException errors)
{
int deleted;
try (DbScope.Transaction transaction = ExperimentService.get().ensureTransaction())
{
deleted = deleteRowsByTaskIds(_tasksTable, _selectedTaskIds);
transaction.commit();
}
catch (SQLException e)
{
throw new RuntimeSQLException(e);
}

Map<String, Object> resultProperties = new HashMap<>();
resultProperties.put("success", true);
resultProperties.put("deletedCount", deleted);
return new ApiSimpleResponse(resultProperties);
}

private int deleteRowsByTaskIds(TableInfo ti, List<String> taskIds) throws SQLException
{
QueryUpdateService qus = ti.getUpdateService();
if (qus == null)
return 0;

int total = 0;
final int chunkSize = 1000;
for (int start = 0; start < taskIds.size(); start += chunkSize)
{
List<String> chunk = taskIds.subList(start, Math.min(start + chunkSize, taskIds.size()));
SimpleFilter filter = new SimpleFilter(FieldKey.fromString("taskid"), chunk, CompareType.IN);
String[] pkColumns = ti.getPkColumnNames().toArray(new String[0]);
Set<String> selectCols = new LinkedHashSet<>(Arrays.asList(pkColumns));
selectCols.add("taskid");

Map<String, Object>[] rows = new TableSelector(ti, selectCols, filter, null).getMapArray();
if (rows == null || rows.length == 0)
continue;

List<Map<String, Object>> keys = new ArrayList<>(rows.length);
for (Map<String, Object> row : rows)
keys.add(new HashMap<>(row));

try
{
qus.deleteRows(getUser(), getContainer(), keys, null, new HashMap<>());
}
catch (InvalidKeyException | QueryUpdateServiceException | BatchValidationException e)
{
throw new RuntimeException(e);
}
total += rows.length;
}
return total;
}
}

public static class EHRQueryForm extends QueryForm
{
private boolean _showImport = false;
Expand Down
2 changes: 2 additions & 0 deletions ehr/src/org/labkey/ehr/EHRModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.labkey.api.data.UpgradeCode;
import org.labkey.api.ehr.EHRDemographicsService;
import org.labkey.api.ehr.EHRService;
import org.labkey.api.ehr.buttons.DiscardEmptyTasksButton;
import org.labkey.api.ehr.buttons.EHRShowEditUIButton;
import org.labkey.api.ehr.buttons.MarkCompletedButton;
import org.labkey.api.ehr.demographics.ActiveAssignmentsDemographicsProvider;
Expand Down Expand Up @@ -249,6 +250,7 @@ public void moduleStartupComplete(ServletContext servletContext)
EHRService.get().registerMoreActionsButton(new CompareWeightsButton(this), "study", "weight");
EHRService.get().registerMoreActionsButton(new TaskAssignButton(this), "ehr", "my_tasks");
EHRService.get().registerMoreActionsButton(new TaskAssignButton(this), "ehr", "tasks");
EHRService.get().registerMoreActionsButton(new DiscardEmptyTasksButton(this), "ehr", "tasks");
EHRService.get().registerMoreActionsButton(new MarkCompletedButton(this, "study", "treatment_order", "Set End Date"), "study", "treatment_order");
EHRService.get().registerMoreActionsButton(new MarkCompletedButton(this, "study", "problem", "End Problem(s)", true), "study", "problem");
EHRService.get().registerMoreActionsButton(new MarkCompletedButton(this, "study", "feeding"), "study", "feeding");
Expand Down