-
Notifications
You must be signed in to change notification settings - Fork 7
Making it harder to delete all rows from lists #7471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release26.3-SNAPSHOT
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,6 +51,7 @@ | |
| import org.labkey.api.data.ContainerFilter; | ||
| import org.labkey.api.data.ContainerManager; | ||
| import org.labkey.api.data.DataRegionSelection; | ||
| import org.labkey.api.data.DbScope; | ||
| import org.labkey.api.data.SQLFragment; | ||
| import org.labkey.api.data.SimpleFilter; | ||
| import org.labkey.api.data.TableInfo; | ||
|
|
@@ -116,6 +117,7 @@ | |
| import org.labkey.list.model.ListDomainKindProperties; | ||
| import org.labkey.list.model.ListManager; | ||
| import org.labkey.list.model.ListManagerSchema; | ||
| import org.labkey.list.model.ListSchema; | ||
| import org.labkey.list.model.ListWriter; | ||
| import org.labkey.list.view.ListDefinitionForm; | ||
| import org.labkey.list.view.ListItemAttachmentParent; | ||
|
|
@@ -425,6 +427,103 @@ public List<Pair<Integer, Container>> getListContainerMap() | |
| } | ||
| } | ||
|
|
||
| @RequiresPermission(AdminPermission.class) | ||
| public static class TruncateListDataAction extends ConfirmAction<ListDeletionForm> | ||
| { | ||
|
Comment on lines
+430
to
+432
|
||
| private boolean canTruncate(Container listContainer, int listId) | ||
| { | ||
| ListDef listDef = ListManager.get().getList(listContainer, listId); | ||
| ListDefinitionImpl list = ListDefinitionImpl.of(listDef); | ||
|
|
||
| if (list == null || !list.getAllowDelete()) | ||
| return false; | ||
|
|
||
| return list.getContainer().hasPermission(getUser(), AdminPermission.class); | ||
| } | ||
|
|
||
| @Override | ||
| public String getConfirmText() | ||
| { | ||
| return "Confirm Delete All Data"; | ||
| } | ||
|
|
||
| @Override | ||
| public void validateCommand(ListDeletionForm form, Errors errors) | ||
| { | ||
| Container currentContainer = getContainer(); | ||
| List<String> errorMessages = new ArrayList<>(); | ||
| Collection<String> listIDs; | ||
| if (form.getListIds() != null) | ||
| listIDs = form.getListIds(); | ||
| else | ||
| listIDs = DataRegionSelection.getSelected(form.getViewContext(), true); | ||
|
|
||
| for (Pair<Integer, Container> pair : getListIdContainerPairs(listIDs, currentContainer, errorMessages)) | ||
| { | ||
| var listId = pair.first; | ||
| var listContainer = pair.second; | ||
|
|
||
| if (canTruncate(listContainer, listId)) | ||
| { | ||
| form.getListContainerMap().add(pair); | ||
| } | ||
| else | ||
| errorMessages.add(String.format("You do not have permission to delete data for list %s in container %s", listId, listContainer.getName())); | ||
| } | ||
|
|
||
| if (!errorMessages.isEmpty()) | ||
| errors.reject(ERROR_MSG, StringUtils.join(errorMessages, "\n")); | ||
|
|
||
| if (form.getListContainerMap().isEmpty()) | ||
| errors.reject(ERROR_MSG, "You must specify a list or lists to delete data from."); | ||
| } | ||
|
|
||
| @Override | ||
| public ModelAndView getConfirmView(ListDeletionForm form, BindException errors) | ||
| { | ||
| if (getPageConfig().getTitle() == null) | ||
| setTitle("Confirm Delete All Data"); | ||
| return new JspView<>("/org/labkey/list/view/truncateListData.jsp", form, errors); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean handlePost(ListDeletionForm form, BindException errors) | ||
| { | ||
| Container containerDataToDelete = getContainer(); | ||
| try (DbScope.Transaction transaction = ListSchema.getInstance().getSchema().getScope().ensureTransaction()) | ||
| { | ||
| for (Pair<Integer, Container> pair : form.getListContainerMap()) | ||
| { | ||
| Container listDefContainer = pair.second; | ||
| ListDefinition listDef = ListService.get().getList(listDefContainer, pair.first); | ||
| if (null != listDef) | ||
| { | ||
| try | ||
| { | ||
| TableInfo table = listDef.getTable(getUser(), listDefContainer); | ||
| if (table != null && table.getUpdateService() != null) | ||
| table.getUpdateService().truncateRows(getUser(), containerDataToDelete, null, null); | ||
| } | ||
| catch (Exception e) | ||
| { | ||
| errors.reject(ERROR_MSG, "Error deleting data from list '" + listDef.getName() + "': " + e.getMessage()); | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| transaction.commit(); | ||
| } | ||
|
|
||
| return !errors.hasErrors(); | ||
| } | ||
|
|
||
| @Override @NotNull | ||
| public URLHelper getSuccessURL(ListDeletionForm form) | ||
| { | ||
| return form.getReturnUrlHelper(getBeginURL(getContainer())); | ||
| } | ||
| } | ||
|
|
||
| @RequiresPermission(ReadPermission.class) | ||
| public class GridAction extends SimpleViewAction<ListQueryForm> | ||
|
|
@@ -958,10 +1057,11 @@ public Pair<AttachmentParent, String> getAttachment(ListAttachmentForm form) | |
| if (listDef == null) | ||
| throw new NotFoundException("List does not exist in this container"); | ||
|
|
||
| if (!listDef.hasListItemForEntityId(form.getEntityId(), getUser())) | ||
| Container dataContainer = listDef.getListItemContainerForDownload(form.getEntityId(), getUser(), ReadPermission.class); | ||
| if (dataContainer == null) | ||
| throw new NotFoundException("List does not have an item for the entityid"); | ||
|
|
||
| AttachmentParent parent = new ListItemAttachmentParent(form.getEntityId(), getContainer()); | ||
| AttachmentParent parent = new ListItemAttachmentParent(form.getEntityId(), dataContainer); | ||
|
|
||
| return new Pair<>(parent, form.getName()); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,70 @@ | ||||||
| <% | ||||||
| /* | ||||||
| * Copyright (c) 2009-2016 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. | ||||||
| */ | ||||||
| %> | ||||||
| <%@ page import="org.labkey.api.data.Container" %> | ||||||
| <%@ page import="org.labkey.api.data.TableInfo" %> | ||||||
| <%@ page import="org.labkey.api.data.TableSelector" %> | ||||||
| <%@ page import="org.labkey.api.exp.list.ListDefinition" %> | ||||||
| <%@ page import="org.labkey.api.exp.list.ListService" %> | ||||||
| <%@ page import="org.labkey.list.controllers.ListController" %> | ||||||
| <%@ page import="java.util.ArrayList" %> | ||||||
| <%@ page import="java.util.HashMap" %> | ||||||
| <%@ page import="java.util.List" %> | ||||||
| <%@ page import="java.util.Map" %> | ||||||
| <%@ page import="java.util.Objects" %> | ||||||
| <%@ page import="org.labkey.list.model.ListQuerySchema" %> | ||||||
| <%@ page import="org.labkey.api.security.User" %> | ||||||
| <%@ page extends="org.labkey.api.jsp.FormPage" %> | ||||||
| <%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib"%> | ||||||
| <% | ||||||
| ListController.ListDeletionForm form = (ListController.ListDeletionForm)getModelBean(); | ||||||
| Map<Container, List<ListDefinition>> definitions = new HashMap<>(); | ||||||
| Container currentContainer = form.getContainer(); | ||||||
| String currentPath = currentContainer.getPath(); | ||||||
| User user = form.getUser(); | ||||||
| ListQuerySchema listQuerySchema = new ListQuerySchema(user, currentContainer); | ||||||
|
|
||||||
| form.getListContainerMap() | ||||||
| .stream() | ||||||
| .map(pair -> ListService.get().getList(pair.second, pair.first)) | ||||||
| .filter(Objects::nonNull) | ||||||
| .forEach(listDef -> { | ||||||
| var container = listDef.getContainer(); | ||||||
| if (!definitions.containsKey(container)) | ||||||
| definitions.put(container, new ArrayList<>()); | ||||||
| definitions.get(container).add(listDef); | ||||||
| }); | ||||||
| %> | ||||||
| <labkey:errors></labkey:errors> | ||||||
| <p>Are you sure you want to delete all data in <%= h(currentPath) %> from the following Lists? This action cannot be undone and will result in an empty list.</p> | ||||||
|
||||||
| <p>Are you sure you want to delete all data in <%= h(currentPath) %> from the following Lists? This action cannot be undone and will result in an empty list.</p> | |
| <p>Are you sure you want to delete all data in <%= h(currentPath) %> from the following Lists? This action cannot be undone and will delete all rows for these lists in the folder/container path shown, but will not affect rows in other folders/containers.</p> |
Copilot
AI
Mar 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Row counts are computed via listQuerySchema.getTable(listDef.getName()), which resolves the list by name in the current container. If multiple selected lists share the same name but are defined in different containers, the displayed count can be for the wrong list. Consider deriving the TableInfo from the selected ListDefinition (e.g., use the list’s definition container to resolve the correct list, and apply an explicit container filter for the current container when counting rows).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This removes the public QueryView.createDeleteAllRowsButton() method from the API module. Even though there are no in-repo references, this is a binary/source breaking change for external modules that compile against the LabKey API. Consider keeping the method (deprecated) and altering its behavior/visibility, or providing a compatible replacement path so downstream modules don’t break on upgrade.