From f22bd8927ecae71ab4f696843745e50802bfcb2a Mon Sep 17 00:00:00 2001 From: Adam Wright Date: Tue, 24 Feb 2026 14:20:47 -0500 Subject: [PATCH 1/2] feat: Add REST endpoints for icon library (facet, query, detail) Expose 3 new endpoints under /search/icon/ to support the Angular icon gallery page, matching the existing data-content IconLibraryController. Co-Authored-By: Claude Opus 4.6 --- .../controller/search/SearchController.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/main/java/org/reactome/server/service/controller/search/SearchController.java b/src/main/java/org/reactome/server/service/controller/search/SearchController.java index d214a3a..0e96d45 100644 --- a/src/main/java/org/reactome/server/service/controller/search/SearchController.java +++ b/src/main/java/org/reactome/server/service/controller/search/SearchController.java @@ -336,6 +336,58 @@ public DiagramSearchSummary diagramSearchSummary(@Parameter(description = "Searc return searchService.getDiagramSearchSummary(queryObject); } + @Operation(summary = "Icon category facets", description = "Returns icon category names and counts for the icon library") + @ApiResponses({ + @ApiResponse(responseCode = "406", description = "Not acceptable according to the accept headers sent in the request"), + @ApiResponse(responseCode = "500", description = "Internal Error in SolR") + }) + @RequestMapping(value = "/icon/facet", method = RequestMethod.GET, produces = "application/json") + @ResponseBody + public FacetMapping iconFacet() throws SolrSearcherException { + infoLogger.info("Request for icon faceting information"); + return searchService.getIconFacetingInformation(); + } + + @Operation(summary = "Paginated icon search results", description = "Returns icons optionally filtered by category or search text, with pagination") + @ApiResponses({ + @ApiResponse(responseCode = "406", description = "Not acceptable according to the accept headers sent in the request"), + @ApiResponse(responseCode = "500", description = "Internal Error in SolR") + }) + @RequestMapping(value = "/icon/query", method = RequestMethod.GET, produces = "application/json") + @ResponseBody + public Result iconQuery(@Parameter(description = "Search text", example = "mitochondria") @RequestParam(required = false) String query, + @Parameter(description = "Icon category filter", example = "cell_type") @RequestParam(required = false) String category, + @Parameter(description = "Page number (1-indexed)", example = "1") @RequestParam(required = false, defaultValue = "1") Integer page, + @Parameter(description = "Results per page", example = "28") @RequestParam(required = false, defaultValue = "28") Integer pageSize) throws SolrSearcherException { + infoLogger.info("Icon query request: query={}, category={}, page={}", query, category, page); + String q = ""; + if (StringUtils.isNotEmpty(category)) { + q = "iconCategories_facet:" + category.toLowerCase().replaceAll("\\s+", "_"); + } + if (StringUtils.isNotEmpty(query)) { + q = q.isEmpty() ? query : q + " " + query; + } + if (q.isEmpty()) q = "*"; + Query queryObject = new Query.Builder(q).build(); + return searchService.getIconsResult(queryObject, pageSize, page); + } + + @Operation(summary = "Icon detail by identifier", description = "Returns a single icon entry by its stable identifier") + @ApiResponses({ + @ApiResponse(responseCode = "404", description = "Icon not found"), + @ApiResponse(responseCode = "406", description = "Not acceptable according to the accept headers sent in the request"), + @ApiResponse(responseCode = "500", description = "Internal Error in SolR") + }) + @RequestMapping(value = "/icon/{identifier}", method = RequestMethod.GET, produces = "application/json") + @ResponseBody + public Entry iconDetail(@Parameter(description = "Icon stable identifier", example = "R-ICO-013428", required = true) @PathVariable String identifier) throws SolrSearcherException { + infoLogger.info("Icon detail request for: {}", identifier); + Query queryObject = new Query.Builder(identifier).build(); + Entry entry = searchService.getIcon(queryObject); + if (entry == null) throw new NotFoundException("Icon not found: " + identifier); + return entry; + } + @Autowired public void setSearchService(SearchService searchService) { this.searchService = searchService; From 3a5c6518650d8d5404f49b2d84886d8415486a0f Mon Sep 17 00:00:00 2001 From: Adam Wright Date: Thu, 26 Feb 2026 16:05:06 -0500 Subject: [PATCH 2/2] test: Add tests for icon library REST endpoints Co-Authored-By: Claude Opus 4.6 --- .../search/SearchControllerTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/test/java/org/reactome/server/service/controller/search/SearchControllerTest.java b/src/test/java/org/reactome/server/service/controller/search/SearchControllerTest.java index f742a82..e9bf26c 100644 --- a/src/test/java/org/reactome/server/service/controller/search/SearchControllerTest.java +++ b/src/test/java/org/reactome/server/service/controller/search/SearchControllerTest.java @@ -113,4 +113,37 @@ public void diagramSearchSummary() throws Exception { mockMvcGetResult("/search/diagram/summary", "application/json;Charset=UTF-8", params); } + + //##################### Icon Endpoints #####################// + + @Test + public void iconFacet() throws Exception { + mockMvcGetResult("/search/icon/facet", "application/json;charset=UTF-8"); + } + + @Test + public void iconQuery() throws Exception { + mockMvcGetResult("/search/icon/query"); + } + + @Test + public void iconQueryWithParams() throws Exception { + Map params = new HashMap<>(); + params.put("query", "mitochondria"); + params.put("category", "cell_type"); + params.put("page", "1"); + params.put("pageSize", "28"); + + mockMvcGetResult("/search/icon/query", "application/json;charset=UTF-8", params); + } + + @Test + public void iconDetail() throws Exception { + mockMvcGetResult("/search/icon/R-ICO-013428", "application/json;charset=UTF-8"); + } + + @Test + public void iconDetailNotFound() throws Exception { + mockMvcGetResultNotFound("/search/icon/R-ICO-999999"); + } } \ No newline at end of file