Skip to content

Nested Documents UI — Phase 2: Virtual Catalogue Config, SearchWrapper & CatalogueSearch #3662

Description

@luis100

Overview

Add config-driven "virtual catalogue" entries to the search dropdown. A virtual catalogue is a named search context that queries Solr child documents using ChildOfFilterParameter, returning results as IndexedAIP (children carry their specific fields in indexedAip.getFields()). No new Java result type is needed — ConfigurableAsyncTableCell already reads object.getFields().get(c.getField()) for non-default columns.

Note

Key architectural insight: Child documents returned by ChildOfFilterParameter are deserialized as IndexedAIP objects. Mandatory AIP fields (permissions, ancestors, etc.) come from the parent or are empty/null. Child-specific dynamic Solr fields (e.g. subject_txt, sender_s) appear in indexedAip.getFields(). ConfigurableAsyncTableCell.configureDisplay() at line ~337 already calls object.getFields().get(c.getField()) for any non-default_ column — meaning the email-specific columns work through existing infrastructure without any new table class.

Warning

SearchWrapper.Components is currently keyed by Class<? extends IsIndexed>. Two virtual catalogues both using IndexedAIP would overwrite each other. The Components inner class must be extended to support a parallel string-keyed map for virtual catalogues (keyed by listId).

Part of: #3382
Depends on: #3660 (Phase 0 — content_type values referenced in config)


How Virtual Catalogues Work

The API call for a virtual catalogue uses ChildOfFilterParameter:

{
  "filter": {
    "parameters": [{
      "type": "ChildOfFilterParameter",
      "childrenOfFilter": {
        "type": "SimpleFilterParameter",
        "name": "content_type",
        "value": "emailarchive"
      }
    }]
  },
  "fieldsToReturn": ["uuid", "subject_txt", "sender_s", "sentDate_dt"]
}

Results are IndexedAIP objects where child-specific fields are in the fields map:

{
  "id": "5f28e162-.../emails#42",
  "fields": {
    "subject_txt": ["Re: Quarterly Report"],
    "sender_s": ["joao.silva@empresa.pt"],
    "sentDate_dt": ["2021-03-15T09:42:00Z"]
  }
}

Clicking a row navigates to browse/{parentUUID} — the parent UUID is extracted from the child UUID by splitting on / and taking the first segment.


Configuration Schema

File: roda-ui/roda-wui/src/main/resources/config/roda-wui.properties

Virtual catalogues are declared at a top-level key and configured under ui.lists.{listId}.* — the same mechanism as Search_AIPs:

# Declare virtual catalogue list IDs
ui.catalogue.virtual = Search_emails

# Dropdown metadata (new sub-keys under ui.lists.{listId})
ui.lists.Search_emails.catalogue.label.i18n     = ui.catalogue.Search_emails.label
ui.lists.Search_emails.catalogue.icon           = fa-envelope
ui.lists.Search_emails.catalogue.childOf.filter = content_type:emailarchive
ui.lists.Search_emails.catalogue.click_action   = browse

# Columns — same syntax as ui.lists.Search_AIPs
ui.lists.Search_emails.columns[] = subject_txt
ui.lists.Search_emails.columns[].subject_txt.field          = subject_txt
ui.lists.Search_emails.columns[].subject_txt.header         = ui.lists.Search_emails.columns.subject_txt
ui.lists.Search_emails.columns[].subject_txt.sortBy         = subject_txt

ui.lists.Search_emails.columns[] = sender_s
ui.lists.Search_emails.columns[].sender_s.field             = sender_s
ui.lists.Search_emails.columns[].sender_s.header            = ui.lists.Search_emails.columns.sender_s

ui.lists.Search_emails.columns[] = sentDate_dt
ui.lists.Search_emails.columns[].sentDate_dt.field          = sentDate_dt
ui.lists.Search_emails.columns[].sentDate_dt.header         = ui.lists.Search_emails.columns.sentDate_dt
ui.lists.Search_emails.columns[].sentDate_dt.renderingHint  = DATETIME_FORMAT_SIMPLE

ui.lists.Search_emails.defaultSortList.columnName = sentDate_dt
ui.lists.Search_emails.defaultSortList.ascending  = false

# Facets
ui.lists.Search_emails.facets.parameters = sender_s
ui.lists.Search_emails.facets.parameters.sender_s.type = SimpleFacetParameter

# Advanced search enabled
ui.lists.Search_emails.search.advanced.enabled = true

New RodaConstants entries

public static final String UI_CATALOGUE_VIRTUAL_PROPERTY          = "ui.catalogue.virtual";
public static final String UI_LISTS_CATALOGUE_LABEL_I18N_PROPERTY = "catalogue.label.i18n";
public static final String UI_LISTS_CATALOGUE_ICON_PROPERTY       = "catalogue.icon";
public static final String UI_LISTS_CATALOGUE_CHILDOF_FILTER      = "catalogue.childOf.filter";
public static final String UI_LISTS_CATALOGUE_CLICK_ACTION        = "catalogue.click_action";

SearchWrapper Changes

File: roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/search/SearchWrapper.java

Extend Components inner class

The current Components class uses Map<Class<? extends IsIndexed>, ...>. Add parallel string-keyed maps for virtual catalogues:

Components changes
private class Components {
  // Existing maps (unchanged)
  private final Map<Class<? extends IsIndexed>, SearchPanel<?>> searchPanels = new LinkedHashMap<>();
  private final Map<Class<? extends IsIndexed>, AsyncTableCell<?>> lists = new LinkedHashMap<>();

  // NEW: string-keyed maps for virtual catalogues (multiple IndexedAIP lists)
  private final Map<String, SearchPanel<?>> virtualSearchPanels = new LinkedHashMap<>();
  private final Map<String, AsyncTableCell<?>> virtualLists = new LinkedHashMap<>();

  public <T extends IsIndexed> void putVirtual(String listId, SearchPanel<T> panel, AsyncTableCell<T> list) {
    virtualSearchPanels.put(listId, panel);
    virtualLists.put(listId, list);
  }

  // getList(String key): check virtual map first, then fall back to class-name lookup
  <T extends IsIndexed> AsyncTableCell<T> getList(String key) {
    if (virtualLists.containsKey(key)) {
      return (AsyncTableCell<T>) virtualLists.get(key);
    }
    return (AsyncTableCell<T>) lists.get(classForName(key));
  }

  // forEachList: include virtual lists
  <T extends IsIndexed> void forEachList(Consumer<AsyncTableCell<T>> action) {
    for (AsyncTableCell<?> list : lists.values()) action.accept((AsyncTableCell<T>) list);
    for (AsyncTableCell<?> list : virtualLists.values()) action.accept((AsyncTableCell<T>) list);
  }
}

New createListAndSearchPanel overload for virtual catalogues

Uses listId as the dropdownValue (not classSimpleName) so each virtual catalogue gets a unique dropdown slot:

public SearchWrapper createVirtualListAndSearchPanel(ListBuilder<IndexedAIP> listBuilder, String listId, ...) {
  // dropdownValue = listId  (e.g. "Search_emails")
  // components.putVirtual(listId, searchPanel, list)
  // searchPanelSelectionDropdown.addItem(label, listId, icon)
}

CatalogueSearch Changes

File: roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/search/CatalogueSearch.java

Read virtual catalogue configs

List<String> virtualCatalogueIds = ConfigurationManager.getStringList(
  RodaConstants.UI_CATALOGUE_VIRTUAL_PROPERTY);

Build virtual list builders

For each virtual catalogue listId:

String childOfFilter = ConfigurationManager.getString(
  RodaConstants.UI_LISTS_PROPERTY, listId, RodaConstants.UI_LISTS_CATALOGUE_CHILDOF_FILTER);
// e.g. "content_type:emailarchive" → parsed as SimpleFilterParameter

Filter baseFilter = new Filter(new ChildOfFilterParameter(
  new SimpleFilterParameter(parseKey(childOfFilter), parseValue(childOfFilter))));

ListBuilder<IndexedAIP> lb = new ListBuilder<>(
  () -> new ConfigurableAsyncTableCell<>(),
  new AsyncTableCellOptions<>(IndexedAIP.class, listId)
    .withFilter(baseFilter)
    .withStartHidden(startHidden)
    // NO bindOpener() — custom click action below
);
searchWrapper.createVirtualListAndSearchPanel(lb, listId, true, true);

Row click — navigate to parent AIP

Child UUID format: {parentUUID}/{fieldName}#{index} (e.g. 5f28e162-.../emails#42).

// Registered as a click handler on each row in the virtual catalogue list:
String childUUID = object.getUUID();
String parentUUID = childUUID.contains("/") ? childUUID.split("/")[0] : childUUID;
History.newItem(HistoryUtils.getHistoryBrowse(parentUUID));

i18n Keys Required

ui.catalogue.Search_emails.label = Emails
ui.lists.Search_emails.columns.subject_txt = Subject
ui.lists.Search_emails.columns.sender_s    = From
ui.lists.Search_emails.columns.sentDate_dt = Sent Date

Files to Change

File Action
roda-core/roda-common-data/.../data/common/RodaConstants.java Edit — add new constants
roda-ui/.../resources/config/roda-wui.properties Edit — add virtual catalogue config
roda-ui/.../client/common/search/SearchWrapper.java Edit — extend Components, add virtual list support
roda-ui/.../client/common/search/CatalogueSearch.java Edit — read virtual configs, build list builders, row click handler
All locale .properties files Edit — add i18n keys

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions