From d94807ae2bd5e28afa5e650de4d12b90db48c3d0 Mon Sep 17 00:00:00 2001 From: cnathe Date: Mon, 24 Aug 2026 10:21:10 -0500 Subject: [PATCH 1/7] GitHub Issue #899: Save grid view fix for saving/resolving inherited view when target folder provided --- .../query/controllers/QueryController.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/query/src/org/labkey/query/controllers/QueryController.java b/query/src/org/labkey/query/controllers/QueryController.java index 8603332e28b..3c86993a1e9 100644 --- a/query/src/org/labkey/query/controllers/QueryController.java +++ b/query/src/org/labkey/query/controllers/QueryController.java @@ -2558,6 +2558,12 @@ protected JSONObject saveCustomView(Container container, QueryDefinition queryDe else view = queryDef.getCustomView(owner, getViewContext().getRequest(), name); + // GitHub Issue #899: the lookups above also resolve views inherited from ancestor folders. containerPath is only + // honored when inherit is set, so otherwise shadow that view with a new local one rather than editing (and + // relocating) the ancestor's. + if (view != null && !inherit && view.getContainer() != null && !container.equals(view.getContainer())) + view = null; + if (view != null && !replaceExisting && !StringUtils.isEmpty(name)) errors.reject(ERROR_MSG, "A saved view by the name \"" + viewName + "\" already exists. "); @@ -6220,8 +6226,9 @@ public ApiResponse execute(SaveSessionViewForm form, BindException errors) // Users may save views to a location other than the current container String containerPath = form.getContainerPath(); + boolean explicitTargetContainer = form.isInherit() && containerPath != null; Container container; - if (form.isInherit() && containerPath != null) + if (explicitTargetContainer) { // Only respect this request if it's a view that is inheritable in subfolders container = ContainerManager.getForPath(containerPath); @@ -6259,6 +6266,14 @@ public ApiResponse execute(SaveSessionViewForm form, BindException errors) existingView = form.getQueryDef().getCustomView(getUser(), null, form.getNewName()); } + // GitHub Issue #899: getCustomView() also resolves views inherited from ancestor folders. Absent an explicit + // target folder, shadow that view with a new local one instead of rewriting (and un-inheriting) the ancestor's. + if (existingView != null && !explicitTargetContainer && existingView.getContainer() != null + && !container.equals(existingView.getContainer())) + { + existingView = null; + } + // save a new private view if shared is false but existing view is shared if (existingView != null && !form.isShared() && existingView.getOwner() == null) { @@ -6278,8 +6293,7 @@ public ApiResponse execute(SaveSessionViewForm form, BindException errors) viewCopy.setFilterAndSort(view.getFilterAndSort()); viewCopy.setColumnProperties(view.getColumnProperties()); viewCopy.setIsHidden(form.isHidden()); - if (form.isInherit()) - viewCopy.setContainer(container); + viewCopy.setContainer(container); viewCopy.save(getUser(), getViewContext().getRequest()); } From b8cde4a9125b54b21303fbbbed4f249b1f40b1af Mon Sep 17 00:00:00 2001 From: cnathe Date: Mon, 24 Aug 2026 13:08:54 -0500 Subject: [PATCH 2/7] GitHub Issue #899: Save grid view fix for saving/resolving inherited view when target folder provided --- .../query/controllers/QueryController.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/query/src/org/labkey/query/controllers/QueryController.java b/query/src/org/labkey/query/controllers/QueryController.java index 3c86993a1e9..f5892585d5a 100644 --- a/query/src/org/labkey/query/controllers/QueryController.java +++ b/query/src/org/labkey/query/controllers/QueryController.java @@ -2534,7 +2534,7 @@ public void addNavTrail(NavTree root) // Uck. Supports the old and new view designer. protected JSONObject saveCustomView(Container container, QueryDefinition queryDef, String regionName, String viewName, boolean replaceExisting, - boolean share, boolean inherit, + boolean share, boolean inherit, boolean explicitTargetContainer, boolean session, boolean saveFilter, boolean hidden, JSONObject jsonView, ActionURL returnUrl, @@ -2558,10 +2558,9 @@ protected JSONObject saveCustomView(Container container, QueryDefinition queryDe else view = queryDef.getCustomView(owner, getViewContext().getRequest(), name); - // GitHub Issue #899: the lookups above also resolve views inherited from ancestor folders. containerPath is only - // honored when inherit is set, so otherwise shadow that view with a new local one rather than editing (and - // relocating) the ancestor's. - if (view != null && !inherit && view.getContainer() != null && !container.equals(view.getContainer())) + // GitHub Issue #899: the lookups above also resolve views inherited from ancestor folders. Absent an explicit + // target folder, shadow that view with a new local one rather than editing (and relocating) the ancestor's. + if (view != null && !explicitTargetContainer && view.getContainer() != null && !container.equals(view.getContainer())) view = null; if (view != null && !replaceExisting && !StringUtils.isEmpty(name)) @@ -2631,7 +2630,7 @@ else if (session != view.isSession()) try { view.delete(getUser(), getViewContext().getRequest()); - JSONObject ret = saveCustomView(container, queryDef, regionName, viewName, replaceExisting, share, inherit, session, saveFilter, hidden, jsonView, returnUrl, errors); + JSONObject ret = saveCustomView(container, queryDef, regionName, viewName, replaceExisting, share, inherit, explicitTargetContainer, session, saveFilter, hidden, jsonView, returnUrl, errors); success = !errors.hasErrors() && ret != null; return success ? ret : null; } @@ -2776,9 +2775,10 @@ public ApiResponse execute(SimpleApiJsonForm form, BindException errors) boolean session = jsonView.optBoolean("session", false); boolean hidden = jsonView.optBoolean("hidden", false); // Users may save views to a location other than the current container - String containerPath = jsonView.optString("containerPath", getContainer().getPath()); + String containerPath = jsonView.optString("containerPath", null); + boolean explicitTargetContainer = inherit && containerPath != null; Container container; - if (inherit) + if (explicitTargetContainer) { // Only respect this request if it's a view that is inheritable in subfolders container = ContainerManager.getForPath(containerPath); @@ -2796,7 +2796,7 @@ public ApiResponse execute(SimpleApiJsonForm form, BindException errors) JSONObject savedView = saveCustomView( container, queryDef, QueryView.DATAREGIONNAME_DEFAULT, viewName, replace, - shared, inherit, session, true, hidden, jsonView, null, errors); + shared, inherit, explicitTargetContainer, session, true, hidden, jsonView, null, errors); if (savedView != null) { From 5d1f7848e8d523fece85f35e42ae4a6225913e07 Mon Sep 17 00:00:00 2001 From: cnathe Date: Mon, 24 Aug 2026 15:10:28 -0500 Subject: [PATCH 3/7] Add back duplicate view name check for inherited case --- .../org/labkey/query/controllers/QueryController.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/query/src/org/labkey/query/controllers/QueryController.java b/query/src/org/labkey/query/controllers/QueryController.java index f5892585d5a..f1f6529e3e2 100644 --- a/query/src/org/labkey/query/controllers/QueryController.java +++ b/query/src/org/labkey/query/controllers/QueryController.java @@ -2560,11 +2560,17 @@ protected JSONObject saveCustomView(Container container, QueryDefinition queryDe // GitHub Issue #899: the lookups above also resolve views inherited from ancestor folders. Absent an explicit // target folder, shadow that view with a new local one rather than editing (and relocating) the ancestor's. + CustomView inheritedView = null; if (view != null && !explicitTargetContainer && view.getContainer() != null && !container.equals(view.getContainer())) + { + inheritedView = view; view = null; + } if (view != null && !replaceExisting && !StringUtils.isEmpty(name)) errors.reject(ERROR_MSG, "A saved view by the name \"" + viewName + "\" already exists. "); + else if (inheritedView != null && !replaceExisting && !StringUtils.isEmpty(name)) + errors.reject(ERROR_MSG, "A saved view by the name \"" + viewName + "\" is already inherited from folder \"" + inheritedView.getContainer().getPath() + "\". "); // 11179: Allow editing the view if we're saving to session. // NOTE: Check for session flag first otherwise the call to canEdit() will add errors to the errors collection. @@ -6268,12 +6274,17 @@ public ApiResponse execute(SaveSessionViewForm form, BindException errors) // GitHub Issue #899: getCustomView() also resolves views inherited from ancestor folders. Absent an explicit // target folder, shadow that view with a new local one instead of rewriting (and un-inheriting) the ancestor's. + CustomView inheritedView = null; if (existingView != null && !explicitTargetContainer && existingView.getContainer() != null && !container.equals(existingView.getContainer())) { + inheritedView = existingView; existingView = null; } + if (inheritedView != null && !form.isReplace() && !StringUtils.isEmpty(form.getNewName())) + throw new IllegalArgumentException("A saved view by the name \"" + form.getNewName() + "\" is already inherited from folder \"" + inheritedView.getContainer().getPath() + "\". "); + // save a new private view if shared is false but existing view is shared if (existingView != null && !form.isShared() && existingView.getOwner() == null) { From fa0b8ede9280a65d77bc4b84986ffae90383b4c6 Mon Sep 17 00:00:00 2001 From: cnathe Date: Mon, 24 Aug 2026 15:17:20 -0500 Subject: [PATCH 4/7] update @labkey/components package version --- assay/package-lock.json | 8 ++++---- assay/package.json | 2 +- core/package-lock.json | 8 ++++---- core/package.json | 2 +- experiment/package-lock.json | 8 ++++---- experiment/package.json | 2 +- pipeline/package-lock.json | 8 ++++---- pipeline/package.json | 2 +- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/assay/package-lock.json b/assay/package-lock.json index a64072f2048..75ee93f5a7a 100644 --- a/assay/package-lock.json +++ b/assay/package-lock.json @@ -8,7 +8,7 @@ "name": "assay", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.58.1" + "@labkey/components": "7.58.6-fb-saveView899.1" }, "devDependencies": { "@labkey/build": "10.1.2", @@ -2366,9 +2366,9 @@ } }, "node_modules/@labkey/components": { - "version": "7.58.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.58.1.tgz", - "integrity": "sha512-bgsETI9c7mOAY8/1sxoEWK8py40+YF4pUOxcWU2PdSObPHS/Ncv66nZCvB7l7GNDfghQnD4E8x01Ze6lvpoMmQ==", + "version": "7.58.6-fb-saveView899.1", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.58.6-fb-saveView899.1.tgz", + "integrity": "sha512-IyBLwHY7Z6X0QgWokop5dO/zLMe0xA86TZA5s+X5NTQf/zWjmZWvD3R/oq/9d3C74MxkIzv1Z0sSK0P9/JjsLQ==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/assay/package.json b/assay/package.json index 1a5c30a95c5..ddc707c0d7d 100644 --- a/assay/package.json +++ b/assay/package.json @@ -15,7 +15,7 @@ "lint-fix": "eslint --fix" }, "dependencies": { - "@labkey/components": "7.58.1" + "@labkey/components": "7.58.6-fb-saveView899.1" }, "devDependencies": { "@labkey/build": "10.1.2", diff --git a/core/package-lock.json b/core/package-lock.json index aabeeddc3d2..a768c70ff49 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -8,7 +8,7 @@ "name": "labkey-core", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.58.1", + "@labkey/components": "7.58.6-fb-saveView899.1", "@labkey/themes": "1.9.5" }, "devDependencies": { @@ -2369,9 +2369,9 @@ } }, "node_modules/@labkey/components": { - "version": "7.58.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.58.1.tgz", - "integrity": "sha512-bgsETI9c7mOAY8/1sxoEWK8py40+YF4pUOxcWU2PdSObPHS/Ncv66nZCvB7l7GNDfghQnD4E8x01Ze6lvpoMmQ==", + "version": "7.58.6-fb-saveView899.1", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.58.6-fb-saveView899.1.tgz", + "integrity": "sha512-IyBLwHY7Z6X0QgWokop5dO/zLMe0xA86TZA5s+X5NTQf/zWjmZWvD3R/oq/9d3C74MxkIzv1Z0sSK0P9/JjsLQ==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/core/package.json b/core/package.json index 717a60c080e..5655cdf52e7 100644 --- a/core/package.json +++ b/core/package.json @@ -20,7 +20,7 @@ "lint-branch-fix": "node lint.diff.mjs --currentBranch --fix" }, "dependencies": { - "@labkey/components": "7.58.1", + "@labkey/components": "7.58.6-fb-saveView899.1", "@labkey/themes": "1.9.5" }, "devDependencies": { diff --git a/experiment/package-lock.json b/experiment/package-lock.json index 35a8dd69da5..439cb0aae34 100644 --- a/experiment/package-lock.json +++ b/experiment/package-lock.json @@ -8,7 +8,7 @@ "name": "experiment", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.58.1" + "@labkey/components": "7.58.6-fb-saveView899.1" }, "devDependencies": { "@labkey/build": "10.1.2", @@ -2378,9 +2378,9 @@ } }, "node_modules/@labkey/components": { - "version": "7.58.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.58.1.tgz", - "integrity": "sha512-bgsETI9c7mOAY8/1sxoEWK8py40+YF4pUOxcWU2PdSObPHS/Ncv66nZCvB7l7GNDfghQnD4E8x01Ze6lvpoMmQ==", + "version": "7.58.6-fb-saveView899.1", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.58.6-fb-saveView899.1.tgz", + "integrity": "sha512-IyBLwHY7Z6X0QgWokop5dO/zLMe0xA86TZA5s+X5NTQf/zWjmZWvD3R/oq/9d3C74MxkIzv1Z0sSK0P9/JjsLQ==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/experiment/package.json b/experiment/package.json index be0823caceb..3c5444bd27d 100644 --- a/experiment/package.json +++ b/experiment/package.json @@ -13,7 +13,7 @@ "test-integration": "cross-env NODE_ENV=test jest --ci --runInBand -c test/js/jest.config.integration.js" }, "dependencies": { - "@labkey/components": "7.58.1" + "@labkey/components": "7.58.6-fb-saveView899.1" }, "devDependencies": { "@labkey/build": "10.1.2", diff --git a/pipeline/package-lock.json b/pipeline/package-lock.json index 062c30b8df0..9450a5f207e 100644 --- a/pipeline/package-lock.json +++ b/pipeline/package-lock.json @@ -8,7 +8,7 @@ "name": "pipeline", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.58.1" + "@labkey/components": "7.58.6-fb-saveView899.1" }, "devDependencies": { "@labkey/build": "10.1.2", @@ -1469,9 +1469,9 @@ } }, "node_modules/@labkey/components": { - "version": "7.58.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.58.1.tgz", - "integrity": "sha512-bgsETI9c7mOAY8/1sxoEWK8py40+YF4pUOxcWU2PdSObPHS/Ncv66nZCvB7l7GNDfghQnD4E8x01Ze6lvpoMmQ==", + "version": "7.58.6-fb-saveView899.1", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.58.6-fb-saveView899.1.tgz", + "integrity": "sha512-IyBLwHY7Z6X0QgWokop5dO/zLMe0xA86TZA5s+X5NTQf/zWjmZWvD3R/oq/9d3C74MxkIzv1Z0sSK0P9/JjsLQ==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/pipeline/package.json b/pipeline/package.json index f2b575a4bf8..2cc10e52464 100644 --- a/pipeline/package.json +++ b/pipeline/package.json @@ -14,7 +14,7 @@ "build-prod": "npm run clean && cross-env NODE_ENV=production rspack build --config node_modules/@labkey/build/configs/prod.config.js" }, "dependencies": { - "@labkey/components": "7.58.1" + "@labkey/components": "7.58.6-fb-saveView899.1" }, "devDependencies": { "@labkey/build": "10.1.2", From ce135cea7f40f829b3f6715ea8097d2ffabad46d Mon Sep 17 00:00:00 2001 From: cnathe Date: Tue, 25 Aug 2026 09:15:57 -0500 Subject: [PATCH 5/7] Claude CR - rename explicitTargetContainer to inheritToTargetContainer, add EditSharedViewPermission container check, move "save a new private view" check up --- .../query/controllers/QueryController.java | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/query/src/org/labkey/query/controllers/QueryController.java b/query/src/org/labkey/query/controllers/QueryController.java index f1f6529e3e2..88c9261ef4c 100644 --- a/query/src/org/labkey/query/controllers/QueryController.java +++ b/query/src/org/labkey/query/controllers/QueryController.java @@ -2534,7 +2534,7 @@ public void addNavTrail(NavTree root) // Uck. Supports the old and new view designer. protected JSONObject saveCustomView(Container container, QueryDefinition queryDef, String regionName, String viewName, boolean replaceExisting, - boolean share, boolean inherit, boolean explicitTargetContainer, + boolean share, boolean inherit, boolean inheritToTargetContainer, boolean session, boolean saveFilter, boolean hidden, JSONObject jsonView, ActionURL returnUrl, @@ -2561,7 +2561,7 @@ protected JSONObject saveCustomView(Container container, QueryDefinition queryDe // GitHub Issue #899: the lookups above also resolve views inherited from ancestor folders. Absent an explicit // target folder, shadow that view with a new local one rather than editing (and relocating) the ancestor's. CustomView inheritedView = null; - if (view != null && !explicitTargetContainer && view.getContainer() != null && !container.equals(view.getContainer())) + if (view != null && !inheritToTargetContainer && view.getContainer() != null && !container.equals(view.getContainer())) { inheritedView = view; view = null; @@ -2569,7 +2569,7 @@ protected JSONObject saveCustomView(Container container, QueryDefinition queryDe if (view != null && !replaceExisting && !StringUtils.isEmpty(name)) errors.reject(ERROR_MSG, "A saved view by the name \"" + viewName + "\" already exists. "); - else if (inheritedView != null && !replaceExisting && !StringUtils.isEmpty(name)) + if (inheritedView != null && !replaceExisting && !StringUtils.isEmpty(name)) errors.reject(ERROR_MSG, "A saved view by the name \"" + viewName + "\" is already inherited from folder \"" + inheritedView.getContainer().getPath() + "\". "); // 11179: Allow editing the view if we're saving to session. @@ -2636,7 +2636,7 @@ else if (session != view.isSession()) try { view.delete(getUser(), getViewContext().getRequest()); - JSONObject ret = saveCustomView(container, queryDef, regionName, viewName, replaceExisting, share, inherit, explicitTargetContainer, session, saveFilter, hidden, jsonView, returnUrl, errors); + JSONObject ret = saveCustomView(container, queryDef, regionName, viewName, replaceExisting, share, inherit, inheritToTargetContainer, session, saveFilter, hidden, jsonView, returnUrl, errors); success = !errors.hasErrors() && ret != null; return success ? ret : null; } @@ -2782,9 +2782,9 @@ public ApiResponse execute(SimpleApiJsonForm form, BindException errors) boolean hidden = jsonView.optBoolean("hidden", false); // Users may save views to a location other than the current container String containerPath = jsonView.optString("containerPath", null); - boolean explicitTargetContainer = inherit && containerPath != null; + boolean inheritToTargetContainer = inherit && containerPath != null; Container container; - if (explicitTargetContainer) + if (inheritToTargetContainer) { // Only respect this request if it's a view that is inheritable in subfolders container = ContainerManager.getForPath(containerPath); @@ -2800,9 +2800,12 @@ public ApiResponse execute(SimpleApiJsonForm form, BindException errors) throw new NotFoundException("No such container: " + containerPath); } + if (inheritToTargetContainer && !container.hasPermission(getUser(), EditSharedViewPermission.class)) + throw new UnauthorizedException(); + JSONObject savedView = saveCustomView( container, queryDef, QueryView.DATAREGIONNAME_DEFAULT, viewName, replace, - shared, inherit, explicitTargetContainer, session, true, hidden, jsonView, null, errors); + shared, inherit, inheritToTargetContainer, session, true, hidden, jsonView, null, errors); if (savedView != null) { @@ -6232,9 +6235,9 @@ public ApiResponse execute(SaveSessionViewForm form, BindException errors) // Users may save views to a location other than the current container String containerPath = form.getContainerPath(); - boolean explicitTargetContainer = form.isInherit() && containerPath != null; + boolean inheritToTargetContainer = form.isInherit() && containerPath != null; Container container; - if (explicitTargetContainer) + if (inheritToTargetContainer) { // Only respect this request if it's a view that is inheritable in subfolders container = ContainerManager.getForPath(containerPath); @@ -6272,10 +6275,16 @@ public ApiResponse execute(SaveSessionViewForm form, BindException errors) existingView = form.getQueryDef().getCustomView(getUser(), null, form.getNewName()); } + // save a new private view if shared is false but existing view is shared + if (existingView != null && !form.isShared() && existingView.getOwner() == null) + { + existingView = null; + } + // GitHub Issue #899: getCustomView() also resolves views inherited from ancestor folders. Absent an explicit // target folder, shadow that view with a new local one instead of rewriting (and un-inheriting) the ancestor's. CustomView inheritedView = null; - if (existingView != null && !explicitTargetContainer && existingView.getContainer() != null + if (existingView != null && !inheritToTargetContainer && existingView.getContainer() != null && !container.equals(existingView.getContainer())) { inheritedView = existingView; @@ -6285,12 +6294,6 @@ public ApiResponse execute(SaveSessionViewForm form, BindException errors) if (inheritedView != null && !form.isReplace() && !StringUtils.isEmpty(form.getNewName())) throw new IllegalArgumentException("A saved view by the name \"" + form.getNewName() + "\" is already inherited from folder \"" + inheritedView.getContainer().getPath() + "\". "); - // save a new private view if shared is false but existing view is shared - if (existingView != null && !form.isShared() && existingView.getOwner() == null) - { - existingView = null; - } - if (existingView != null && !form.isReplace() && !StringUtils.isEmpty(form.getNewName())) throw new IllegalArgumentException("A saved view by the name \"" + form.getNewName() + "\" already exists. "); From 565d97527148e71ca635758b403ab720466695e6 Mon Sep 17 00:00:00 2001 From: cnathe Date: Wed, 26 Aug 2026 08:33:03 -0500 Subject: [PATCH 6/7] QueryController consolidation of resolveViewName() --- .../query/controllers/QueryController.java | 68 +++++++++++-------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/query/src/org/labkey/query/controllers/QueryController.java b/query/src/org/labkey/query/controllers/QueryController.java index 88c9261ef4c..f118d3862bb 100644 --- a/query/src/org/labkey/query/controllers/QueryController.java +++ b/query/src/org/labkey/query/controllers/QueryController.java @@ -2531,6 +2531,38 @@ public void addNavTrail(NavTree root) } } + /** + * GitHub Issue #899: custom view lookups also resolve views inherited from ancestor folders. Absent an explicit target + * folder, such a view must be shadowed by a new local one instead of rewritten (and un-inherited), so a name collision + * with an ancestor's view reports differently from one with a local view. + * + * @param localView the resolved view, null once it turns out to belong to an ancestor + * @param message a name-collision error, or null if the save may proceed + */ + private record ResolvedViewName(CustomView localView, String message) {} + + private static ResolvedViewName resolveViewName(CustomView existingView, String name, Container container, + boolean inheritToTargetContainer, boolean replaceExisting) + { + CustomView inheritedView = null; + if (existingView != null && !inheritToTargetContainer && existingView.getContainer() != null + && !container.equals(existingView.getContainer())) + { + inheritedView = existingView; + existingView = null; + } + + String message = null; + if (!replaceExisting && !StringUtils.isEmpty(name)) + { + if (inheritedView != null) + message = "A saved view by the name \"" + name + "\" is already inherited from folder \"" + inheritedView.getContainer().getPath() + "\". "; + else if (existingView != null) + message = "A saved view by the name \"" + name + "\" already exists. "; + } + return new ResolvedViewName(existingView, message); + } + // Uck. Supports the old and new view designer. protected JSONObject saveCustomView(Container container, QueryDefinition queryDef, String regionName, String viewName, boolean replaceExisting, @@ -2558,19 +2590,10 @@ protected JSONObject saveCustomView(Container container, QueryDefinition queryDe else view = queryDef.getCustomView(owner, getViewContext().getRequest(), name); - // GitHub Issue #899: the lookups above also resolve views inherited from ancestor folders. Absent an explicit - // target folder, shadow that view with a new local one rather than editing (and relocating) the ancestor's. - CustomView inheritedView = null; - if (view != null && !inheritToTargetContainer && view.getContainer() != null && !container.equals(view.getContainer())) - { - inheritedView = view; - view = null; - } - - if (view != null && !replaceExisting && !StringUtils.isEmpty(name)) - errors.reject(ERROR_MSG, "A saved view by the name \"" + viewName + "\" already exists. "); - if (inheritedView != null && !replaceExisting && !StringUtils.isEmpty(name)) - errors.reject(ERROR_MSG, "A saved view by the name \"" + viewName + "\" is already inherited from folder \"" + inheritedView.getContainer().getPath() + "\". "); + ResolvedViewName resolved = resolveViewName(view, name, container, inheritToTargetContainer, replaceExisting); + view = resolved.localView(); + if (resolved.message() != null) + errors.reject(ERROR_MSG, resolved.message()); // 11179: Allow editing the view if we're saving to session. // NOTE: Check for session flag first otherwise the call to canEdit() will add errors to the errors collection. @@ -6281,21 +6304,10 @@ public ApiResponse execute(SaveSessionViewForm form, BindException errors) existingView = null; } - // GitHub Issue #899: getCustomView() also resolves views inherited from ancestor folders. Absent an explicit - // target folder, shadow that view with a new local one instead of rewriting (and un-inheriting) the ancestor's. - CustomView inheritedView = null; - if (existingView != null && !inheritToTargetContainer && existingView.getContainer() != null - && !container.equals(existingView.getContainer())) - { - inheritedView = existingView; - existingView = null; - } - - if (inheritedView != null && !form.isReplace() && !StringUtils.isEmpty(form.getNewName())) - throw new IllegalArgumentException("A saved view by the name \"" + form.getNewName() + "\" is already inherited from folder \"" + inheritedView.getContainer().getPath() + "\". "); - - if (existingView != null && !form.isReplace() && !StringUtils.isEmpty(form.getNewName())) - throw new IllegalArgumentException("A saved view by the name \"" + form.getNewName() + "\" already exists. "); + ResolvedViewName resolved = resolveViewName(existingView, form.getNewName(), container, inheritToTargetContainer, form.isReplace()); + existingView = resolved.localView(); + if (resolved.message() != null) + throw new IllegalArgumentException(resolved.message()); if (existingView == null || (existingView instanceof ModuleCustomView && existingView.isEditable())) { From 62c563e7f59844c5e9f734a7d5717c381a112177 Mon Sep 17 00:00:00 2001 From: cnathe Date: Wed, 26 Aug 2026 08:45:20 -0500 Subject: [PATCH 7/7] update @labkey/components package version --- assay/package-lock.json | 8 ++++---- assay/package.json | 2 +- core/package-lock.json | 8 ++++---- core/package.json | 2 +- experiment/package-lock.json | 8 ++++---- experiment/package.json | 2 +- pipeline/package-lock.json | 8 ++++---- pipeline/package.json | 2 +- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/assay/package-lock.json b/assay/package-lock.json index 75ee93f5a7a..69cc8b9c21b 100644 --- a/assay/package-lock.json +++ b/assay/package-lock.json @@ -8,7 +8,7 @@ "name": "assay", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.58.6-fb-saveView899.1" + "@labkey/components": "7.59.0" }, "devDependencies": { "@labkey/build": "10.1.2", @@ -2366,9 +2366,9 @@ } }, "node_modules/@labkey/components": { - "version": "7.58.6-fb-saveView899.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.58.6-fb-saveView899.1.tgz", - "integrity": "sha512-IyBLwHY7Z6X0QgWokop5dO/zLMe0xA86TZA5s+X5NTQf/zWjmZWvD3R/oq/9d3C74MxkIzv1Z0sSK0P9/JjsLQ==", + "version": "7.59.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.59.0.tgz", + "integrity": "sha512-wV5Wtqrz/dOzMWD6XpVuantMha8fetCYOXB7S/Jz6QMt2uE9Fg23lfxYArKWDylm8ufMgEzi9hqDmYdeYM6xYg==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/assay/package.json b/assay/package.json index ddc707c0d7d..0d49e947c9b 100644 --- a/assay/package.json +++ b/assay/package.json @@ -15,7 +15,7 @@ "lint-fix": "eslint --fix" }, "dependencies": { - "@labkey/components": "7.58.6-fb-saveView899.1" + "@labkey/components": "7.59.0" }, "devDependencies": { "@labkey/build": "10.1.2", diff --git a/core/package-lock.json b/core/package-lock.json index a768c70ff49..02c67966ae5 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -8,7 +8,7 @@ "name": "labkey-core", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.58.6-fb-saveView899.1", + "@labkey/components": "7.59.0", "@labkey/themes": "1.9.5" }, "devDependencies": { @@ -2369,9 +2369,9 @@ } }, "node_modules/@labkey/components": { - "version": "7.58.6-fb-saveView899.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.58.6-fb-saveView899.1.tgz", - "integrity": "sha512-IyBLwHY7Z6X0QgWokop5dO/zLMe0xA86TZA5s+X5NTQf/zWjmZWvD3R/oq/9d3C74MxkIzv1Z0sSK0P9/JjsLQ==", + "version": "7.59.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.59.0.tgz", + "integrity": "sha512-wV5Wtqrz/dOzMWD6XpVuantMha8fetCYOXB7S/Jz6QMt2uE9Fg23lfxYArKWDylm8ufMgEzi9hqDmYdeYM6xYg==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/core/package.json b/core/package.json index 5655cdf52e7..6020e70b9c0 100644 --- a/core/package.json +++ b/core/package.json @@ -20,7 +20,7 @@ "lint-branch-fix": "node lint.diff.mjs --currentBranch --fix" }, "dependencies": { - "@labkey/components": "7.58.6-fb-saveView899.1", + "@labkey/components": "7.59.0", "@labkey/themes": "1.9.5" }, "devDependencies": { diff --git a/experiment/package-lock.json b/experiment/package-lock.json index 439cb0aae34..c11eb5a0321 100644 --- a/experiment/package-lock.json +++ b/experiment/package-lock.json @@ -8,7 +8,7 @@ "name": "experiment", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.58.6-fb-saveView899.1" + "@labkey/components": "7.59.0" }, "devDependencies": { "@labkey/build": "10.1.2", @@ -2378,9 +2378,9 @@ } }, "node_modules/@labkey/components": { - "version": "7.58.6-fb-saveView899.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.58.6-fb-saveView899.1.tgz", - "integrity": "sha512-IyBLwHY7Z6X0QgWokop5dO/zLMe0xA86TZA5s+X5NTQf/zWjmZWvD3R/oq/9d3C74MxkIzv1Z0sSK0P9/JjsLQ==", + "version": "7.59.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.59.0.tgz", + "integrity": "sha512-wV5Wtqrz/dOzMWD6XpVuantMha8fetCYOXB7S/Jz6QMt2uE9Fg23lfxYArKWDylm8ufMgEzi9hqDmYdeYM6xYg==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/experiment/package.json b/experiment/package.json index 3c5444bd27d..45c776c7d4c 100644 --- a/experiment/package.json +++ b/experiment/package.json @@ -13,7 +13,7 @@ "test-integration": "cross-env NODE_ENV=test jest --ci --runInBand -c test/js/jest.config.integration.js" }, "dependencies": { - "@labkey/components": "7.58.6-fb-saveView899.1" + "@labkey/components": "7.59.0" }, "devDependencies": { "@labkey/build": "10.1.2", diff --git a/pipeline/package-lock.json b/pipeline/package-lock.json index 9450a5f207e..2e1ad5a03d9 100644 --- a/pipeline/package-lock.json +++ b/pipeline/package-lock.json @@ -8,7 +8,7 @@ "name": "pipeline", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.58.6-fb-saveView899.1" + "@labkey/components": "7.59.0" }, "devDependencies": { "@labkey/build": "10.1.2", @@ -1469,9 +1469,9 @@ } }, "node_modules/@labkey/components": { - "version": "7.58.6-fb-saveView899.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.58.6-fb-saveView899.1.tgz", - "integrity": "sha512-IyBLwHY7Z6X0QgWokop5dO/zLMe0xA86TZA5s+X5NTQf/zWjmZWvD3R/oq/9d3C74MxkIzv1Z0sSK0P9/JjsLQ==", + "version": "7.59.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.59.0.tgz", + "integrity": "sha512-wV5Wtqrz/dOzMWD6XpVuantMha8fetCYOXB7S/Jz6QMt2uE9Fg23lfxYArKWDylm8ufMgEzi9hqDmYdeYM6xYg==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/pipeline/package.json b/pipeline/package.json index 2cc10e52464..db925d09b3f 100644 --- a/pipeline/package.json +++ b/pipeline/package.json @@ -14,7 +14,7 @@ "build-prod": "npm run clean && cross-env NODE_ENV=production rspack build --config node_modules/@labkey/build/configs/prod.config.js" }, "dependencies": { - "@labkey/components": "7.58.6-fb-saveView899.1" + "@labkey/components": "7.59.0" }, "devDependencies": { "@labkey/build": "10.1.2",