Skip to content
Open
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
75 changes: 75 additions & 0 deletions src/org/labkey/remoteapi/query/SaveQueryViewsCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* 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.remoteapi.query;

import org.json.JSONObject;
import org.labkey.remoteapi.CommandResponse;
import org.labkey.remoteapi.PostCommand;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Saves custom grid views via query-saveQueryViews.api.
*/
public class SaveQueryViewsCommand extends PostCommand<CommandResponse>
{
private final String _schemaName;
private final String _queryName;
private final List<Map<String, Object>> _views = new ArrayList<>();

public SaveQueryViewsCommand(String schemaName, String queryName)
{
super("query", "saveQueryViews");
_schemaName = schemaName;
_queryName = queryName;
}

/**
* @param viewName the view name; empty for the default view
* @param columns field keys to show, in order; at least one is required
* @param shared make the view available to all users
* @param inherit make the view available in child folders
*/
public SaveQueryViewsCommand addView(String viewName, List<String> columns, boolean shared, boolean inherit)
{
List<Map<String, Object>> columnList = columns.stream()
.map(fieldKey -> Map.<String, Object>of("fieldKey", fieldKey))
.toList();

Map<String, Object> view = new HashMap<>();
view.put("name", viewName);
view.put("columns", columnList);
view.put("shared", shared);
view.put("inherit", inherit);
view.put("replace", true);
_views.add(view);

return this;
}

@Override
public JSONObject getJsonObject()
{
JSONObject json = new JSONObject();
json.put("schemaName", _schemaName);
json.put("queryName", _queryName);
json.put("views", _views);
return json;
}
}
68 changes: 68 additions & 0 deletions src/org/labkey/test/tests/CustomizeViewTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.labkey.remoteapi.query.SaveQueryViewsCommand;
import org.labkey.test.BaseWebDriverTest;
import org.labkey.test.Locator;
import org.labkey.test.SortDirection;
import org.labkey.test.WebTestHelper;
import org.labkey.test.categories.Daily;
import org.labkey.test.pages.list.GridPage;
import org.labkey.test.params.FieldDefinition;
Expand Down Expand Up @@ -82,6 +84,12 @@ public class CustomizeViewTest extends BaseWebDriverTest
private final static int WIDE_LIST_COLUMN_COUNT = 160;
private final static String WIDE_STAT_COLUMN = "Field001";

// GitHub Issue 899: view used for subfolder testing
private final static String SUBFOLDER_NAME = "InheritViewSubfolder";
private final static String INHERIT_LIST_NAME = "InheritViewList";
private final static String INHERITED_VIEW_NAME = "ProjectWideView";
private final static List<String> INHERITED_VIEW_COLUMNS = List.of(LIST_KEY_COLUMN, FIRST_NAME_COLUMN, AGE_COLUMN);

private SummaryStatisticsHelper _summaryStatisticsHelper;

@Override
Expand All @@ -102,6 +110,9 @@ private void doSetup() throws Exception
_containerHelper.createProject(PROJECT_NAME, null);
createList();
createWideList();

_containerHelper.createSubfolder(PROJECT_NAME, SUBFOLDER_NAME);
createInheritViewList();
}

@Before
Expand Down Expand Up @@ -406,6 +417,63 @@ public void testCustomViewsQueryTable()
assertEquals("Deleted view should no longer appear in the query.CustomViews table", -1, drt.getRowIndex("Name", viewName));
}

// GitHub Issue 899
@Test
public void testSaveInheritedViewFromSubfolder() throws Exception
{
final String subfolderPath = PROJECT_NAME + "/" + SUBFOLDER_NAME;

new SaveQueryViewsCommand("lists", INHERIT_LIST_NAME)
.addView(INHERITED_VIEW_NAME, INHERITED_VIEW_COLUMNS, true, true)
.execute(createDefaultConnection(), PROJECT_NAME);

GridPage.beginAt(this, subfolderPath, INHERIT_LIST_NAME);
DataRegionTable grid = new DataRegionTable("query", getDriver());
grid.goToView(INHERITED_VIEW_NAME);
assertEquals("Subfolder should start out showing the view inherited from the project",
INHERITED_VIEW_COLUMNS, grid.getColumnNames());

// Leaving "available in child folders" unchecked is what makes the save target folder implicit
_customizeViewsHelper.openCustomizeViewPanel();
_customizeViewsHelper.removeColumn(FieldKey.fromParts(AGE_COLUMN).toString());
_customizeViewsHelper.saveCustomView(INHERITED_VIEW_NAME, true, false);

grid = new DataRegionTable("query", getDriver());
assertEquals("Subfolder save should shadow the inherited view with a local copy",
List.of(LIST_KEY_COLUMN, FIRST_NAME_COLUMN), grid.getColumnNames());

// The subfolder save used to relocate the project's view rather than shadow it, so the project lost the view entirely
assertEquals("Project's view should still be inheritable", "true",
getCustomViewValue(PROJECT_NAME, INHERITED_VIEW_NAME, "Inheritable"));
assertEquals("Shadowing view should be local to the subfolder", "false",
getCustomViewValue(subfolderPath, INHERITED_VIEW_NAME, "Inheritable"));

GridPage.beginAt(this, PROJECT_NAME, INHERIT_LIST_NAME);
grid = new DataRegionTable("query", getDriver());
grid.goToView(INHERITED_VIEW_NAME);
assertEquals("Project's view should be untouched by the subfolder save",
INHERITED_VIEW_COLUMNS, grid.getColumnNames());
}

// query.CustomViews only shows views owned by the container, so a row here proves where the view lives
private String getCustomViewValue(String containerPath, String viewName, String column)
{
beginAt(WebTestHelper.buildURL("query", containerPath, "executeQuery", Map.of("schemaName", "query", "queryName", "CustomViews")));
DataRegionTable customViews = new DataRegionTable("query", getDriver());
int rowIndex = customViews.getRowIndex("Name", viewName);
assertNotEquals(String.format("View '%s' should be owned by folder '%s'", viewName, containerPath), -1, rowIndex);
return customViews.getDataAsText(rowIndex, column);
}

private void createInheritViewList() throws Exception
{
new IntListDefinition(INHERIT_LIST_NAME, LIST_KEY_COLUMN)
.setFields(List.of(
new FieldDefinition(FIRST_NAME_COLUMN, ColumnType.String),
new FieldDefinition(AGE_COLUMN, ColumnType.Integer)))
.create(createDefaultConnection(), PROJECT_NAME);
}

private void createList() throws Exception
{
ListDefinition listDefinition = new IntListDefinition(LIST_NAME, LIST_KEY_COLUMN).setFields(LIST_COLUMNS);
Expand Down
9 changes: 9 additions & 0 deletions src/org/labkey/test/tests/component/GridPanelViewTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,7 @@ public void testSaveViewTrickyName() throws Exception
log(String.format("Save the view name as '%s'.", trickyViewName));

saveViewDialog.setViewName(trickyViewName)
.setMakeShared(false)
.saveView();

// Add this view name to the list of views.
Expand Down Expand Up @@ -1234,7 +1235,12 @@ public void testManageViews() throws Exception
String viewName1 = String.format("No %s", fieldRemoved1);
log(String.format("Use the 'Save' button on the grid to save the view as '%s'.", viewName1));
SaveViewDialog saveViewDialog = grid.clickSaveButton(true);

log("GitHub Issue #899: Expect shared checkbox for session view to match original / shadowed view");
checker().verifyTrue("View should default to shared", saveViewDialog.isMakeSharedChecked());

saveViewDialog.setViewName(viewName1)
.setMakeShared(false)
.saveView();

log(String.format("Go back to '%s' and create a new view.", VIEW_DEFAULT));
Expand All @@ -1252,6 +1258,7 @@ public void testManageViews() throws Exception
log(String.format("Use the menu to save the view as '%s'. This is now the current view.", viewName2));
saveViewDialog = grid.saveView();
saveViewDialog.setViewName(viewName2)
.setMakeShared(false)
.saveView();

ManageViewsDialog manageViewsDialog = grid.manageViews();
Expand Down Expand Up @@ -1347,6 +1354,7 @@ public void testManageViews() throws Exception
String viewName3 = "No Bool";
log(String.format("Change view name to something new, '%s' and validate save works as expected.", viewName3));
saveViewDialog.setViewName(viewName3);
saveViewDialog.setMakeShared(false);
saveViewDialog.saveView();

checker().withScreenshot("New_View_Name_Error")
Expand All @@ -1373,6 +1381,7 @@ public void testManageViews() throws Exception
grid.hideColumn(fieldRemoved1);
saveViewDialog = grid.saveView();
saveViewDialog.setViewName(viewName1);
saveViewDialog.setMakeShared(false);
saveViewDialog.saveView();

manageViewsDialog = grid.manageViews();
Expand Down