From 6f2f7aeff5bd559d21564deb4fe8f7e38dadb882 Mon Sep 17 00:00:00 2001 From: learner97 Date: Tue, 11 Aug 2026 14:07:11 -0600 Subject: [PATCH] added a back button for returning to root collection, added some paths for api formatting --- .../controllers/PermissionController.java | 2 +- .../sbh3/controllers/SearchController.java | 37 +++++++++--- .../sbh3/controllers/SubmitController.java | 12 ---- frontend/components/Viewing/ViewHeader.js | 58 ++++++++++++++++++- 4 files changed, 87 insertions(+), 22 deletions(-) diff --git a/backend/src/main/java/com/synbiohub/sbh3/controllers/PermissionController.java b/backend/src/main/java/com/synbiohub/sbh3/controllers/PermissionController.java index 7250bf4e..fb4ef378 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/controllers/PermissionController.java +++ b/backend/src/main/java/com/synbiohub/sbh3/controllers/PermissionController.java @@ -26,7 +26,7 @@ public ResponseEntity addOwner(@Parameter(description = "Key/value pairs } @Operation(summary = "Remove owner (Unimplemented)", description = "Removes an owner from a registry object. Currently an empty stub.", deprecated = true) - @PostMapping(value = "/removeOwner/**") + @PostMapping(value = "/removeOwner") public ResponseEntity removeOwner(@Parameter(description = "Key/value pairs including uri and user email") @RequestParam Map allParams, @Parameter(description = "JWT Token") @RequestHeader("X-authorization") String xauth) { return new ResponseEntity<>(HttpStatus.OK); } diff --git a/backend/src/main/java/com/synbiohub/sbh3/controllers/SearchController.java b/backend/src/main/java/com/synbiohub/sbh3/controllers/SearchController.java index 62c27090..a594f153 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/controllers/SearchController.java +++ b/backend/src/main/java/com/synbiohub/sbh3/controllers/SearchController.java @@ -204,7 +204,10 @@ public JsonNode getSharedObjects() throws IOException, JsonProcessingException { @ApiResponse(responseCode = "200", description = "JSON array of sub-collection metadata"), @ApiResponse(responseCode = "500", description = "SPARQL query or database error") }) - @GetMapping("/{visibility:.+}/{collectionID:.+}/{displayID:.+}/{version:.+}/**/subCollections") + @GetMapping({ + "/{visibility:.+}/{collectionID:.+}/{displayID:.+}/{version:.+}/**/subCollections", + "/{visibility}/{collectionID}/{displayID}/{version}/subCollections" + }) public String getSubCollections( @Parameter(description = "Visibility scope (e.g. 'public')") @PathVariable("visibility") String visibility, @Parameter(description = "Collection database ID") @PathVariable("collectionID") String collectionID, @@ -229,7 +232,10 @@ public String getSubCollections( @ApiResponse(responseCode = "200", description = "JSON array of twin component metadata"), @ApiResponse(responseCode = "500", description = "SPARQL query or database error") }) - @GetMapping("/{visibility:.+}/{collectionID:.+}/{displayID:.+}/{version:.+}/**/twins") + @GetMapping({ + "/{visibility:.+}/{collectionID:.+}/{displayID:.+}/{version:.+}/**/twins", + "/{visibility}/{collectionID}/{displayID}/{version}/twins" + }) public String getTwins( @Parameter(description = "Visibility scope (e.g. 'public')") @PathVariable("visibility") String visibility, @Parameter(description = "Collection database ID") @PathVariable("collectionID") String collectionID, @@ -255,7 +261,10 @@ public String getTwins( @ApiResponse(responseCode = "200", description = "Plaintext count of twin components"), @ApiResponse(responseCode = "500", description = "SPARQL query or database error") }) - @GetMapping("/{visibility:.+}/{collectionID:.+}/{displayID:.+}/{version:.+}/**/twinsCount") + @GetMapping({ + "/{visibility:.+}/{collectionID:.+}/{displayID:.+}/{version:.+}/**/twinsCount", + "/{visibility}/{collectionID}/{displayID}/{version}/twinsCount" + }) public String getTwinsCount( @Parameter(description = "Visibility scope (e.g. 'public')") @PathVariable("visibility") String visibility, @Parameter(description = "Collection database ID") @PathVariable("collectionID") String collectionID, @@ -282,7 +291,10 @@ public String getTwinsCount( @ApiResponse(responseCode = "200", description = "JSON array of similar component metadata"), @ApiResponse(responseCode = "500", description = "SPARQL query or database error, or SBOLExplorer not enabled") }) - @GetMapping("/{visibility:.+}/{collectionID:.+}/{displayID:.+}/{version:.+}/**/similar") + @GetMapping({ + "/{visibility:.+}/{collectionID:.+}/{displayID:.+}/{version:.+}/**/similar", + "/{visibility}/{collectionID}/{displayID}/{version}/similar" + }) public String getSimilar( @Parameter(description = "Visibility scope (e.g. 'public')") @PathVariable("visibility") String visibility, @Parameter(description = "Collection database ID") @PathVariable("collectionID") String collectionID, @@ -309,7 +321,10 @@ public String getSimilar( @ApiResponse(responseCode = "200", description = "Plaintext count of similar components"), @ApiResponse(responseCode = "500", description = "SPARQL query or database error, or SBOLExplorer not enabled") }) - @GetMapping("/{visibility:.+}/{collectionID:.+}/{displayID:.+}/{version:.+}/**/similarCount") + @GetMapping({ + "/{visibility:.+}/{collectionID:.+}/{displayID:.+}/{version:.+}/**/similarCount", + "/{visibility}/{collectionID}/{displayID}/{version}/similarCount" + }) public String getSimilarCount( @Parameter(description = "Visibility scope (e.g. 'public')") @PathVariable("visibility") String visibility, @Parameter(description = "Collection database ID") @PathVariable("collectionID") String collectionID, @@ -337,7 +352,10 @@ public String getSimilarCount( @ApiResponse(responseCode = "200", description = "JSON array of referencing object metadata"), @ApiResponse(responseCode = "500", description = "SPARQL query or database error") }) - @GetMapping("/{visibility:.+}/{collectionID:.+}/{displayID:.+}/{version:.+}/**/uses") + @GetMapping({ + "/{visibility:.+}/{collectionID:.+}/{displayID:.+}/{version:.+}/**/uses", + "/{visibility}/{collectionID}/{displayID}/{version}/uses" + }) public String getUses( @Parameter(description = "Visibility scope (e.g. 'public')") @PathVariable("visibility") String visibility, @Parameter(description = "Collection database ID") @PathVariable("collectionID") String collectionID, @@ -365,7 +383,10 @@ public String getUses( @ApiResponse(responseCode = "200", description = "Plaintext count of referencing objects"), @ApiResponse(responseCode = "500", description = "SPARQL query or database error") }) - @GetMapping("/{visibility:.+}/{collectionID:.+}/{displayID:.+}/{version:.+}/**/usesCount") + @GetMapping({ + "/{visibility:.+}/{collectionID:.+}/{displayID:.+}/{version:.+}/**/usesCount", + "/{visibility}/{collectionID}/{displayID}/{version}/usesCount" + }) public String getUsesCount( @Parameter(description = "Visibility scope (e.g. 'public')") @PathVariable("visibility") String visibility, @Parameter(description = "Collection database ID") @PathVariable("collectionID") String collectionID, @@ -445,7 +466,7 @@ public String getTypeCount(@Parameter(description = "SBOL object type (e.g. Comp @ApiResponse(responseCode = "200", description = "SPARQL query results as JSON"), @ApiResponse(responseCode = "500", description = "SPARQL syntax error or database error") }) - @RequestMapping(value = "/sparql", headers = "Accept=application/json") + @GetMapping(value = "/sparql", headers = "Accept=application/json") @ResponseBody public String getSPARQL(@Parameter(description = "Query parameters: 'query' (SPARQL query string) and optional 'default-graph-uri'") @RequestParam Map params) throws IOException { return sparqlService.read(sparqlService.getExplorerUrl(), params.get("default-graph-uri"), params.get("query")); diff --git a/backend/src/main/java/com/synbiohub/sbh3/controllers/SubmitController.java b/backend/src/main/java/com/synbiohub/sbh3/controllers/SubmitController.java index 17ac9f9c..e3d14992 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/controllers/SubmitController.java +++ b/backend/src/main/java/com/synbiohub/sbh3/controllers/SubmitController.java @@ -40,12 +40,6 @@ public ResponseEntity submit( return submitService.submit(allParams, file); } - @Operation(summary = "Create new collection (Unimplemented)", description = "Currently an empty stub.", deprecated = true) - @PostMapping(value = "/newCollection") - public void createNewCollection(@RequestParam(required = false) Map submissionData) { - - } - @Operation(summary = "Make object public (Unimplemented)", description = "Currently an empty stub.", deprecated = true) @PreAuthorize("hasAuthority('CURATOR')") @PostMapping(value = "/makePublic") @@ -71,12 +65,6 @@ public void replaceObject(@RequestParam Map allParams) { // should just call remove object then add object } - @Operation(summary = "Add object (Unimplemented)", description = "Currently an empty stub.", deprecated = true) - @PostMapping(value = "/add") - public void addObject(@RequestBody(required = false) SBOLDocument sbolDocument) { - - } - @Operation(summary = "Update icon (Unimplemented)", description = "Currently an empty stub.", deprecated = true) @PostMapping(value = "/icon") public ResponseEntity updateIcon(@RequestParam Map allParams) { diff --git a/frontend/components/Viewing/ViewHeader.js b/frontend/components/Viewing/ViewHeader.js index 2eaea0e1..eb603c93 100644 --- a/frontend/components/Viewing/ViewHeader.js +++ b/frontend/components/Viewing/ViewHeader.js @@ -4,7 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import styles from '../../styles/view.module.css'; import React, { useRef, useEffect } from 'react'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useState } from 'react'; import { useRouter } from 'next/router'; @@ -14,6 +14,9 @@ import Link from 'next/link'; import { isUriOwner } from './Shell'; import getConfig from 'next/config'; +import { processUrl } from '../Admin/Registries'; +import getMemberOfCollections from '../../sparql/getMemberOfCollections'; +import getQueryResponse from '../../sparql/tools/getQueryResponse'; const { publicRuntimeConfig } = getConfig(); function renderSplitTitle(title) { @@ -46,8 +49,11 @@ export default function ViewHeader(properties) { const [isEditingDescription, setIsEditingDescription] = useState(false); const [editedDescription, setEditedDescription] = useState(properties.description); const [counts, setCounts] = useState({ twins: 0, uses: 0, similar: 0 }); + const [parentCollectionUrl, setParentCollectionUrl] = useState(null); const token = useSelector(state => state.user.token); + const dispatch = useDispatch(); + const registries = JSON.parse(localStorage.getItem('registries')) || {}; useEffect(() => { const base = `${publicRuntimeConfig.backend}/${getAfterThirdSlash(properties.uri)}`; @@ -71,6 +77,43 @@ export default function ViewHeader(properties) { .catch(err => console.error("Error fetching linked counts:", err)); }, [properties.uri, token]); + // Resolve parent collection for "Back to Collection"; hide when none (e.g. root collection) + useEffect(() => { + let cancelled = false; + setParentCollectionUrl(null); + + if (!properties.uri) return undefined; + + getQueryResponse( + dispatch, + getMemberOfCollections, + { uri: properties.uri }, + token + ) + .then(async collections => { + if (cancelled) return; + if (!collections || collections.length === 0) { + setParentCollectionUrl(null); + return; + } + const processed = await processUrl(collections[0].subject, registries); + if (cancelled) return; + setParentCollectionUrl( + processed.urlRemovedForLink || processed.original || null + ); + }) + .catch(err => { + if (!cancelled) { + console.error('Error fetching parent collection:', err); + setParentCollectionUrl(null); + } + }); + + return () => { + cancelled = true; + }; + }, [properties.uri, token, dispatch]); + const theme = JSON.parse(localStorage.getItem('theme')) || {}; var displayTitle = properties.type; @@ -379,6 +422,19 @@ export default function ViewHeader(properties) { )} */} + {parentCollectionUrl && ( + + )} {(search.twins || search.uses || search.similar) && ( Search For: )}