From 8713e2fa3ef463623d36488697b32a50289ea1e4 Mon Sep 17 00:00:00 2001
From: Hamayoon Khan <hamkha@ceh.ac.uk>
Date: Wed, 18 Jun 2025 12:46:02 +0100
Subject: [PATCH] Add support for selecting source folder (eidc/dropbox) in
 keyword suggestions

---
 docker-compose.yml                                    |  2 ++
 .../c88921ba-f871-44c3-9339-51c5bee4024a/support.txt  |  0
 .../controllers/KeywordSuggestionsController.java     |  5 +++--
 .../catalogue/services/KeywordSuggestionsService.java |  8 ++++----
 .../services/KeywordSuggestionsServiceTest.java       |  8 ++++----
 web/src/editor/src/templates/Parent.js                | 11 ++++++++++-
 web/src/editor/src/views/LegiloFetcher.js             |  6 +++---
 web/src/editor/src/views/LegiloView.js                |  5 ++++-
 web/src/editor/src/views/ParentView.js                |  3 +++
 web/src/editor/test/LegiloKeywordsTest.js             |  2 +-
 10 files changed, 34 insertions(+), 16 deletions(-)
 rename fixtures/upload/{supporting-documents => dropbox}/c88921ba-f871-44c3-9339-51c5bee4024a/support.txt (100%)

diff --git a/docker-compose.yml b/docker-compose.yml
index 1a1273e76..a39ce1ad4 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -47,11 +47,13 @@ services:
     environment:
       DATA_DIR: /opt/data
       SUPPORTING_DOCS_DIR: /opt/supporting-docs
+      DROPBOX_DIR: /opt/dropbox
     ports:
       - "8000:8000"
     volumes:
       - ./fixtures/upload/eidchub:/opt/data
       - ./fixtures/upload/supporting-documents:/opt/supporting-docs
+      - ./fixtures/upload/dropbox:/opt/dropbox
     profiles: [legilo]
   fuseki:
     image: registry.gitlab.ceh.ac.uk/eip/fuseki:4.9.0-ceh2
diff --git a/fixtures/upload/supporting-documents/c88921ba-f871-44c3-9339-51c5bee4024a/support.txt b/fixtures/upload/dropbox/c88921ba-f871-44c3-9339-51c5bee4024a/support.txt
similarity index 100%
rename from fixtures/upload/supporting-documents/c88921ba-f871-44c3-9339-51c5bee4024a/support.txt
rename to fixtures/upload/dropbox/c88921ba-f871-44c3-9339-51c5bee4024a/support.txt
diff --git a/java/src/main/java/uk/ac/ceh/gateway/catalogue/controllers/KeywordSuggestionsController.java b/java/src/main/java/uk/ac/ceh/gateway/catalogue/controllers/KeywordSuggestionsController.java
index ca5411973..5d438164c 100644
--- a/java/src/main/java/uk/ac/ceh/gateway/catalogue/controllers/KeywordSuggestionsController.java
+++ b/java/src/main/java/uk/ac/ceh/gateway/catalogue/controllers/KeywordSuggestionsController.java
@@ -5,6 +5,7 @@ import org.springframework.context.annotation.Profile;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import uk.ac.ceh.gateway.catalogue.services.KeywordSuggestionsService;
@@ -24,8 +25,8 @@ public class KeywordSuggestionsController {
 
     @GetMapping("/documents/{file}/suggestKeywords")
     @PreAuthorize("@permission.userCanEdit(#file)")
-    public List<KeywordSuggestionsService.KeywordsSuggestion> getKeywordsSuggestions(@PathVariable String file) {
-        return service.getKeywordsSuggestions(file);
+    public List<KeywordSuggestionsService.KeywordsSuggestion> getKeywordsSuggestions(@PathVariable String file, @RequestParam(name = "location", defaultValue = "eidc") String location) {
+        return service.getKeywordsSuggestions(file, location);
     }
 
     @GetMapping("/documents/{file}/suggestVariables")
diff --git a/java/src/main/java/uk/ac/ceh/gateway/catalogue/services/KeywordSuggestionsService.java b/java/src/main/java/uk/ac/ceh/gateway/catalogue/services/KeywordSuggestionsService.java
index e3d943131..d188e471d 100644
--- a/java/src/main/java/uk/ac/ceh/gateway/catalogue/services/KeywordSuggestionsService.java
+++ b/java/src/main/java/uk/ac/ceh/gateway/catalogue/services/KeywordSuggestionsService.java
@@ -46,10 +46,10 @@ public class KeywordSuggestionsService {
         log.info("Creating");
     }
 
-    public List<KeywordsSuggestion> getKeywordsSuggestions(String file) {
+    public List<KeywordsSuggestion> getKeywordsSuggestions(String file, String location) {
         List<Map<String, Object>> errorList = new ArrayList<>();
 
-        List<KeywordsSuggestion> keywords = getKeywords(restClient, file, errorList)
+        List<KeywordsSuggestion> keywords = getKeywords(restClient, file, location, errorList)
             .flatMap(kw -> Optional.ofNullable(kw.summary()))
             .orElseGet(Collections::emptyList);
 
@@ -95,11 +95,11 @@ public class KeywordSuggestionsService {
             .orElseGet(Collections::emptyList);
     }
 
-    private Optional<KeywordsResponse> getKeywords(RestClient restClient, String file, List<Map<String, Object>> errorList) {
+    private Optional<KeywordsResponse> getKeywords(RestClient restClient, String file, String location, List<Map<String, Object>> errorList) {
         return Optional.ofNullable(
             restClient
                 .get()
-                .uri("/{file}/keywords", file)
+                .uri("/{file}/keywords?location={location}", file, location)
                 .retrieve()
                 .onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
                     int code = response.getStatusCode().value();
diff --git a/java/src/test/java/uk/ac/ceh/gateway/catalogue/services/KeywordSuggestionsServiceTest.java b/java/src/test/java/uk/ac/ceh/gateway/catalogue/services/KeywordSuggestionsServiceTest.java
index ebd5a4af0..b7faa76f3 100644
--- a/java/src/test/java/uk/ac/ceh/gateway/catalogue/services/KeywordSuggestionsServiceTest.java
+++ b/java/src/test/java/uk/ac/ceh/gateway/catalogue/services/KeywordSuggestionsServiceTest.java
@@ -53,13 +53,13 @@ public class KeywordSuggestionsServiceTest {
         //given
         String keywordsResponse = IOUtils.toString(getClass().getResource("legilo-keywords-response.json"), UTF_8);
         mockServer
-            .expect(requestTo(equalTo(LEGILO_URL + FILE_ID + "/keywords")))
+            .expect(requestTo(equalTo(LEGILO_URL + FILE_ID + "/keywords?location=eidc")))
             .andExpect(method(HttpMethod.GET))
             .andExpect(header(HttpHeaders.AUTHORIZATION, "Basic dXNlcm5hbWU6cGFzc3dvcmQ="))
             .andRespond(withSuccess(keywordsResponse, MediaType.APPLICATION_JSON));
 
         //when
-        List<KeywordSuggestionsService.KeywordsSuggestion> keywordsSuggestions = service.getKeywordsSuggestions(FILE_ID);
+        List<KeywordSuggestionsService.KeywordsSuggestion> keywordsSuggestions = service.getKeywordsSuggestions(FILE_ID, "eidc");
 
         //then
         mockServer.verify();
@@ -75,13 +75,13 @@ public class KeywordSuggestionsServiceTest {
     void getSuggestionsWithException() {
         //given
         mockServer
-            .expect(requestTo(equalTo(LEGILO_URL + FILE_ID + "/keywords")))
+            .expect(requestTo(equalTo(LEGILO_URL + FILE_ID + "/keywords?location=eidc")))
             .andExpect(method(HttpMethod.GET))
             .andExpect(header(HttpHeaders.AUTHORIZATION, "Basic dXNlcm5hbWU6cGFzc3dvcmQ="))
             .andRespond(withStatus(HttpStatus.UNPROCESSABLE_ENTITY));
 
         //when
-        ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> service.getKeywordsSuggestions(FILE_ID));
+        ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> service.getKeywordsSuggestions(FILE_ID, "eidc"));
 
         //then
         assertEquals("422 UNPROCESSABLE_ENTITY \"Unprocessable Entity\"", exception.getMessage());
diff --git a/web/src/editor/src/templates/Parent.js b/web/src/editor/src/templates/Parent.js
index 04f86c74b..872fd96f0 100644
--- a/web/src/editor/src/templates/Parent.js
+++ b/web/src/editor/src/templates/Parent.js
@@ -16,8 +16,17 @@ export default _.template(`
           <% } %>
       </label>
       <button class="editor-button add" <%= data.disabled%>>Add <span class="fa-solid fa-plus" aria-hidden="true"></span></button>
+      <div class="clearfix"></div>
       <% if (data.fetchKeywordsButton) { %>
-        <button class="legilo-keywords-btn editor-button mx-lg-1 px-3"><i class="fa-solid fa-wand-magic-sparkles"></i> Suggest</button>
+        <div class="btn-group align-items-end mt-1 float-end" role="group">
+          <select id="location-select" class="form-select form-select-sm w-auto">
+            <option value="eidc" selected>EIDC</option>
+            <option value="dropbox">Dropbox</option>
+          </select>
+          <button class="legilo-keywords-btn editor-button mx-lg-1 px-3">
+            <i class="fa-solid fa-wand-magic-sparkles"></i> Suggest
+          </button>
+        </div>
       <% } %>
       <div id="help-<%= data.modelAttribute %>" class="editor-help w-100">
           <%= data.helpText %>
diff --git a/web/src/editor/src/views/LegiloFetcher.js b/web/src/editor/src/views/LegiloFetcher.js
index 27fdce9a2..cfb96fc4e 100644
--- a/web/src/editor/src/views/LegiloFetcher.js
+++ b/web/src/editor/src/views/LegiloFetcher.js
@@ -13,13 +13,13 @@ export function fetchVariablesFromLegilo (id) {
       confidence: variableData.confidence
     })))
     .catch(error => {
-      console.error('Error fetching variaqbles:', error)
+      console.error('Error fetching variables:', error)
       throw error
     })
 }
 
-export function fetchKeywordsFromLegilo (id) {
-  const apiUrl = `/documents/${id}/suggestKeywords`
+export function fetchKeywordsFromLegilo (id, location = 'eidc') {
+  const apiUrl = `/documents/${id}/suggestKeywords?location=${location}`
 
   return $.getJSON(apiUrl)
     .then(data => data.map(keywordData => new LegiloKeyword({
diff --git a/web/src/editor/src/views/LegiloView.js b/web/src/editor/src/views/LegiloView.js
index 579c08b70..df3777ea1 100644
--- a/web/src/editor/src/views/LegiloView.js
+++ b/web/src/editor/src/views/LegiloView.js
@@ -12,6 +12,7 @@ export default Backbone.View.extend({
     this.onSelect = options.onSelect
     this.suggestionsToShow = options.suggestionsToShow || 10
     this.result = options.result
+    this.locationSelectEl = options.locationSelectEl
     this.selectedSuggestions = []
     this.suggestions = []
     this.suggestionsToDisplay = []
@@ -47,7 +48,9 @@ export default Backbone.View.extend({
       .removeClass('text-primary')
       .text('It may take a while.')
 
-    this.fetcher(this.model.id)
+    const location = this.locationSelectEl?.val() || 'eidc'
+
+    this.fetcher(this.model.id, location)
       .then(suggestions => {
         this.model.set(this.fetcher.name, suggestions)
         this.render()
diff --git a/web/src/editor/src/views/ParentView.js b/web/src/editor/src/views/ParentView.js
index f2a269f97..c9b214233 100644
--- a/web/src/editor/src/views/ParentView.js
+++ b/web/src/editor/src/views/ParentView.js
@@ -38,6 +38,8 @@ export default SingleView.extend({
     const that = this
     $(document).ready(function () {
       if ((that.data.fetchKeywordsButton || that.data.renderLegiloKeywords) && that.model.get('id')) {
+        const locationSelect = that.$('#location-select')
+
         that.legiloKeywords = new LegiloView({
           collection: that.collection,
           model: that.model,
@@ -46,6 +48,7 @@ export default SingleView.extend({
           fetcher: fetchKeywordsFromLegilo,
           fetchButton: that.$('.legilo-keywords-btn'),
           result: that.$('.legilo-keywords-view'),
+          locationSelectEl: locationSelect,
           onSelect: keywordOnSelect
         })
         if (that.data.renderLegiloKeywords) {
diff --git a/web/src/editor/test/LegiloKeywordsTest.js b/web/src/editor/test/LegiloKeywordsTest.js
index 2199c64b3..0bd830211 100644
--- a/web/src/editor/test/LegiloKeywordsTest.js
+++ b/web/src/editor/test/LegiloKeywordsTest.js
@@ -108,7 +108,7 @@ describe('Test LegiloKeywords View', () => {
     legiloButton.trigger('click')
     await new Promise(resolve => setTimeout(resolve, 0))
 
-    expect($.getJSON).toHaveBeenCalledWith('/documents/123/suggestKeywords')
+    expect($.getJSON).toHaveBeenCalledWith('/documents/123/suggestKeywords?location=eidc')
     expect(view.suggestions.length).toBe(2)
     expect(view.suggestions[0].get('name')).toBe('temperature')
     expect(view.suggestions[0].get('confidence')).toBe(0.9)
-- 
GitLab