From 5ff7052270322b94e92283eaff45039ca77581be Mon Sep 17 00:00:00 2001 From: David Waroquiers Date: Mon, 21 Sep 2026 14:26:28 +0200 Subject: [PATCH] Hide collections in webapp. --- .docker/app/Dockerfile | 1 + .docker/app_entrypoint.sh | 2 ++ docker-compose.yml | 2 ++ pydatalab/docs/config.md | 3 +++ webapp/cypress/component/ItemGraphTest.cy.jsx | 9 +++++++ webapp/cypress/component/NavbarTest.cy.jsx | 16 +++++++++++++ .../cypress/component/SampleTableTest.cy.jsx | 24 +++++++++++++++++++ webapp/src/components/CreateItemModal.vue | 12 +++++++--- webapp/src/components/DynamicDataTable.vue | 10 +++++--- .../components/DynamicDataTableButtons.vue | 7 ++++-- .../src/components/EquipmentInformation.vue | 4 +++- webapp/src/components/ItemGraph.vue | 21 +++++++++------- webapp/src/components/Navbar.vue | 7 ++++-- .../src/components/SampleGraphExportModal.vue | 4 +++- .../ToggleableCollectionFormGroup.vue | 3 +++ webapp/src/resources.js | 3 +++ webapp/src/router/index.js | 4 ++-- 17 files changed, 109 insertions(+), 23 deletions(-) diff --git a/.docker/app/Dockerfile b/.docker/app/Dockerfile index 8d34c40d3e..5eb5e229ef 100644 --- a/.docker/app/Dockerfile +++ b/.docker/app/Dockerfile @@ -47,6 +47,7 @@ ARG VUE_APP_API_URL=magic-api-url ARG VUE_APP_LOGO_URL=magic-logo-url ARG VUE_APP_HOMEPAGE_URL=magic-homepage-url ARG VUE_APP_EDITABLE_INVENTORY=magic-setting +ARG VUE_APP_HIDE_COLLECTIONS=magic-hide-collections-setting ARG VUE_APP_WEBSITE_TITLE=magic-title ARG VUE_APP_QR_CODE_RESOLVER_URL=magic-qr-code-resolver-url ARG VUE_APP_AUTOMATICALLY_GENERATE_ID_DEFAULT=magic-generate-id-setting diff --git a/.docker/app_entrypoint.sh b/.docker/app_entrypoint.sh index ad40c33512..eb2459835f 100755 --- a/.docker/app_entrypoint.sh +++ b/.docker/app_entrypoint.sh @@ -34,6 +34,7 @@ echo " LOGO_URL: ${VUE_APP_LOGO_URL}" echo " LOGO_WIDTH: ${VUE_APP_LOGO_WIDTH}" echo " HOMEPAGE_URL: ${VUE_APP_HOMPAGE_URL}" echo " EDITABLE_INVENTORY: ${VUE_APP_EDITABLE_INVENTORY}" +echo " HIDE_COLLECTIONS: ${VUE_APP_HIDE_COLLECTIONS}" echo " WEBSITE_TITLE: ${VUE_APP_WEBSITE_TITLE}" echo " QR_CODE_RESOLVER_URL: ${VUE_APP_QR_CODE_RESOLVER_URL}" echo " AUTOMATICALLY_GENERATE_ID_DEFAULT: ${VUE_APP_AUTOMATICALLY_GENERATE_ID_DEFAULT}" @@ -48,6 +49,7 @@ for file in $ROOT_DIR/js/app.*.js* $ROOT_DIR/*html; do sed -i "s|magic-logo-width|${VUE_APP_LOGO_WIDTH}|g" $file sed -i "s|magic-homepage-url|${VUE_APP_HOMEPAGE_URL}|g" $file sed -i "s|magic-setting|${VUE_APP_EDITABLE_INVENTORY}|g" $file + sed -i "s|magic-hide-collections-setting|${VUE_APP_HIDE_COLLECTIONS}|g" $file sed -i "s|magic-title|${VUE_APP_WEBSITE_TITLE}|g" $file sed -i "s|magic-qr-code-resolver-url|${VUE_APP_QR_CODE_RESOLVER_URL}|g" $file sed -i "s|magic-generate-id-setting|${VUE_APP_AUTOMATICALLY_GENERATE_ID_DEFAULT}|g" $file diff --git a/docker-compose.yml b/docker-compose.yml index ed5bcec730..6af62b042a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,7 @@ services: - VUE_APP_LOGO_URL - VUE_APP_HOMEPAGE_URL - VUE_APP_EDITABLE_INVENTORY + - VUE_APP_HIDE_COLLECTIONS - VUE_APP_WEBSITE_TITLE - VUE_APP_QR_CODE_RESOLVER_URL - VUE_APP_AUTOMATICALLY_GENERATE_ID_DEFAULT @@ -118,6 +119,7 @@ services: environment: - NODE_ENV=development - VUE_APP_API_URL=${VUE_APP_API_URL:-http://localhost:5001} + - VUE_APP_HIDE_COLLECTIONS - CHOKIDAR_USEPOLLING=true networks: diff --git a/pydatalab/docs/config.md b/pydatalab/docs/config.md index 44beee455f..e2222be9b6 100644 --- a/pydatalab/docs/config.md +++ b/pydatalab/docs/config.md @@ -23,6 +23,7 @@ These can be provided as either: - `VUE_APP_LOGO_URL`: the URL of an image to use as the logo header in the web app. - `VUE_APP_HOMEPAGE_URL`: a URL to provide as a link from the web app header. - `VUE_APP_EDITABLE_INVENTORY`: whether the inventory can be edited by non-admin users in the web app. + - `VUE_APP_HIDE_COLLECTIONS`: whether to hide collection pages and controls in the web app. This defaults to `false` and does not disable collection API routes or alter stored collection data. - `VUE_APP_WEBSITE_TITLE`: the title of the web app, which is displayed in the browser tab and header. - `VUE_APP_QR_CODE_RESOLVER_URL`: the URL of a service that can resolve QR codes to *datalab* entries, which is used by the web app to display QR codes for entries (see [datalab-org/datalab-purl](https://github.com/datalab-org/datalab-purl) for more information). - `VUE_APP_AUTOMATICALLY_GENERATE_ID_DEFAULT`: whether to automatically generate IDs for new entries in the web app by default, or require a checkbox to be ticked at item creation. @@ -30,6 +31,8 @@ These can be provided as either: > [!NOTE] > The possible ways to set configuration options can be inconsistent with each other, e.g., values required to be `None` in Python should be set to `null` in the JSON config file and as .env values. Similarly, boolean values may be set to `true` or `false` in the JSON config file, but can be set to {`1`, `yes`, `true`} or {`0`, `no`, `false`} in a `.env` file. +Changes to `VUE_APP_HIDE_COLLECTIONS` require restarting the web app. This setting only hides collections from the web interface and is not an access-control mechanism. + ## Mandatory settings There is only one mandatory setting when creating a deployment. diff --git a/webapp/cypress/component/ItemGraphTest.cy.jsx b/webapp/cypress/component/ItemGraphTest.cy.jsx index 0c2a950684..9298740fb3 100644 --- a/webapp/cypress/component/ItemGraphTest.cy.jsx +++ b/webapp/cypress/component/ItemGraphTest.cy.jsx @@ -165,6 +165,15 @@ describe("ItemGraph", () => { cy.contains("configure").should("be.visible"); }); + it("hides collection options when configured", () => { + mountGraph(twoConnectedNodes, { showOptions: true }); + cy.then(() => { + Cypress.vueWrapper.vm.hideCollections = true; + return Cypress.vueWrapper.vm.$nextTick(); + }); + cy.contains("Ignore connections to collections:").should("not.exist"); + }); + it("hides options panel when showOptions is false", () => { mountGraph(twoConnectedNodes, { showOptions: false }); cy.contains("configure").should("not.exist"); diff --git a/webapp/cypress/component/NavbarTest.cy.jsx b/webapp/cypress/component/NavbarTest.cy.jsx index d5a3ed832a..ac848b569a 100644 --- a/webapp/cypress/component/NavbarTest.cy.jsx +++ b/webapp/cypress/component/NavbarTest.cy.jsx @@ -96,6 +96,22 @@ describe("Navbar", () => { }); }); + it("hides the collections navigation link when configured", () => { + cy.mount(Navbar, { + global: { + plugins: [store, router], + }, + data() { + return { hideCollections: true }; + }, + }); + + cy.get("#nav").within(() => { + cy.contains("Collections").should("not.exist"); + cy.contains("Inventory").should("have.attr", "href", "/starting-materials"); + }); + }); + it("navigates to the correct route on link click", () => { cy.mount(Navbar, { global: { diff --git a/webapp/cypress/component/SampleTableTest.cy.jsx b/webapp/cypress/component/SampleTableTest.cy.jsx index 138f59980b..6666fc1bfb 100644 --- a/webapp/cypress/component/SampleTableTest.cy.jsx +++ b/webapp/cypress/component/SampleTableTest.cy.jsx @@ -1,5 +1,6 @@ import "bootstrap/dist/css/bootstrap.css"; import DynamicDataTable from "@/components/DynamicDataTable.vue"; +import DynamicDataTableButtons from "@/components/DynamicDataTableButtons.vue"; import SampleTable from "@/components/SampleTable.vue"; import UserBubble from "@/components/UserBubble.vue"; import StyledTooltip from "@/components/StyledTooltip.vue"; @@ -199,6 +200,29 @@ describe("SampleTable Component Tests", () => { cy.get('[data-testid="search-input"]').should("exist"); }); + it("hides collection table controls when configured", () => { + cy.then(() => { + const dataTable = wrapper.findComponent(DynamicDataTable); + dataTable.vm.hideCollections = true; + dataTable.vm.selectedColumns = dataTable.vm.availableColumns.filter((col) => !col.hidden); + dataTable.vm.itemsSelected = [store.state.sample_list[0]]; + + const buttons = dataTable.findComponent(DynamicDataTableButtons); + buttons.vm.hideCollections = true; + + expect(dataTable.vm.availableColumns.some((col) => col.field === "collections")).to.be.false; + return dataTable.vm.$nextTick().then(() => { + expect(buttons.props("availableColumns").some((col) => col.field === "collections")).to.be + .false; + }); + }); + + cy.get(".p-datatable-column-title").should("not.contain.text", "Collections"); + cy.get('[data-testid="add-to-collection-button"]').should("not.exist"); + cy.get('[data-testid="add-to-collection-form"]').should("not.exist"); + cy.contains("Create new collection").should("not.exist"); + }); + it("renders the table with correct headers", () => { const headers = [ "", //checkbox diff --git a/webapp/src/components/CreateItemModal.vue b/webapp/src/components/CreateItemModal.vue index 863c14b682..d91169353a 100644 --- a/webapp/src/components/CreateItemModal.vue +++ b/webapp/src/components/CreateItemModal.vue @@ -64,8 +64,8 @@ - -
+ +
- + ({ ...col })); + return this.columns + .filter((col) => !this.hideCollections || col.field !== "collections") + .map((col) => ({ ...col })); }, }, created() { diff --git a/webapp/src/components/DynamicDataTableButtons.vue b/webapp/src/components/DynamicDataTableButtons.vue index d034e15a0c..1897d399ac 100644 --- a/webapp/src/components/DynamicDataTableButtons.vue +++ b/webapp/src/components/DynamicDataTableButtons.vue @@ -61,7 +61,7 @@
-
+
@@ -92,6 +92,7 @@ import FormattedRefcode from "@/components/FormattedRefcode"; import ToggleableCreatorsFormGroup from "@/components/ToggleableCreatorsFormGroup"; import ToggleableItemStatusFormGroup from "@/components/ToggleableItemStatusFormGroup"; import ToggleableGroupsFormGroup from "@/components/ToggleableGroupsFormGroup"; +import { HIDE_COLLECTIONS } from "@/resources.js"; export default { components: { @@ -110,6 +111,7 @@ export default { }, data() { return { + hideCollections: HIDE_COLLECTIONS, tableOfContentsSections: [ { title: "Equipment Information", targetID: "equipment-information" }, { title: "Table of Contents", targetID: "table-of-contents" }, diff --git a/webapp/src/components/ItemGraph.vue b/webapp/src/components/ItemGraph.vue index 92100953d7..84c4477e21 100644 --- a/webapp/src/components/ItemGraph.vue +++ b/webapp/src/components/ItemGraph.vue @@ -77,14 +77,16 @@ @option:deselected="readdItemToGraph" /> - - +
import ItemSelect from "@/components/ItemSelect.vue"; import CollectionSelect from "@/components/CollectionSelect.vue"; -import { itemTypes } from "@/resources.js"; +import { itemTypes, HIDE_COLLECTIONS } from "@/resources.js"; import cytoscape from "cytoscape"; import cola from "cytoscape-cola"; import elk from "cytoscape-elk"; @@ -215,6 +217,7 @@ export default { ignoreItems: [], removedNodeData: {}, ignoreCollections: [], + hideCollections: HIDE_COLLECTIONS, labelStartingMaterialsByName: true, labelItemsByName: false, showBlocks: this.defaultShowBlocks, diff --git a/webapp/src/components/Navbar.vue b/webapp/src/components/Navbar.vue index 776dd46894..ca1210fc0b 100644 --- a/webapp/src/components/Navbar.vue +++ b/webapp/src/components/Navbar.vue @@ -25,7 +25,9 @@