Hi devs,
FYI, I’m trying to tackle Loading... with the goal of not doing a 100% full scale generic solution on the first go but enough (and a little beyond) to be usable for implementing Loading....
I want to see how good (or bad) claude code can be for this design work.
FYI the first pass design doc (generated by Claude Code):
Design: Solr-based Live Data Source (XWIKI-23940)
Context
- Issue: XWIKI-23940 — provide a Live Data source backed by Solr to find and list documents in a Live Data table.
- Primary driver: XWIKI-22733 — the Backlinks / “Information” section doesn’t scale, can’t filter/sort. Existing Live Data sources cannot display backlinks, so we need a new source first.
- Scope agreed in XWIKI-22733 (Michael Hamann, confirmed by Vincent): a generic Solr search Live Data source. A first version only needs an arbitrary Solr query as input plus
titleandlinkcolumns. More fields added later as needed. - Open question from the issue (now resolved for v1): “use Solr only for query, or also fetch display data from Solr?” → Fetch display data directly from Solr (faster; avoids loading documents). Loading documents to reuse LiveTable column metadata is explicitly a later option.
How Live Data sources work (recap of the contract)
A source is three cooperating components, all looked up by the same hint and all PER_LOOKUP, extending WithParameters (so source parameters from the macro flow into them):
LiveDataSource(@Named("solr")) → exposesgetEntries()andgetProperties().LiveDataEntryStore.get(LiveDataQuery)→ returns aLiveData(acount+ a list of entries, each entry aMap<String,Object>).LiveDataPropertyDescriptorStore.get()→ returns the column/property descriptors (id, name, type, sortable, filterable, displayer…).
LiveDataQuery is essentially an SQL-shaped object:
properties= select (which columns to return)source=Source(id, parameters)(the from clause — oursolrsource + its params)filters= list ofFilter{property, matchAll, constraints[{operator, value}]}(the where clause)sort= list ofSortEntry{property, descending}offset(Long),limit(Integer)
Reference implementation to mirror: xwiki-platform-livedata-livetable (LiveTableLiveDataSource / LiveTableLiveDataEntryStore / LiveTableLiveDataPropertyStore).
Existing Solr plumbing we reuse
QueryManager.createQuery(queryString, SolrQueryExecutor.SOLR)builds a SolrQuery.((SecureQuery) query).checkCurrentUser(true)→SolrQueryExecutorfilters out documents the current user cannot view. We get view-rights enforcement for free.query.bindValue("fq", List<String>),bindValue("sort", "field asc|desc"),setLimit(n),setOffset(start)— extra Solr params are passed throughgetNamedParameters().execute()returns a single-elementList<QueryResponse>; fromQueryResponsewe readgetResults()→SolrDocumentList(getNumFound()+ iterateSolrDocument).FieldUtilsholds the indexed field names:TITLE,FULLNAME,REFERENCE,NAME,SPACE/SPACES,WIKI,TYPE,LINKS(outgoing links),HIDDEN,DATE,AUTHOR_DISPLAY,SCORE,*_sortvariants for sorting, etc. Localized fields (e.g.title) are auto-expanded across supported locales by the executor.
Prior art for building/running a Solr query string and reading results: SOLRSearchSource (xwiki-platform-search-solr-rest).
Proposed module
New sibling module under xwiki-platform-livedata:
xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-solr/
pom.xml (artifactId: xwiki-platform-livedata-solr)
src/main/java/org/xwiki/livedata/internal/solr/
SolrLiveDataSource.java @Named("solr")
SolrLiveDataEntryStore.java @Named("solr")
SolrLiveDataPropertyStore.java @Named("solr")
SolrLiveDataConfigurationResolver.java (optional, see below)
src/main/resources/META-INF/components.txt
src/test/java/...
Dependencies: xwiki-platform-livedata-api, xwiki-platform-query-manager (Query/QueryManager/SecureQuery), xwiki-platform-search-solr-api (for FieldUtils and Solr field constants), SolrJ (transitively), xwiki-platform-model-api. Register the module in xwiki-platform-livedata/pom.xml <modules> and add it to the WAR/flavor as the LiveTable connector is.
@since should be the current SNAPSHOT version from the root pom (currently 18.5.0-SNAPSHOT → @since 18.5.0RC1), not the value in CLAUDE.md.
Source parameters (macro-facing)
| Parameter | Meaning | v1 |
|---|---|---|
query |
Arbitrary Solr query string (the “search query”), e.g. links:"xwiki:Sandbox.WebHome" for backlinks |
yes |
fq |
Optional extra Solr filter queries | nice-to-have |
wiki |
Restrict to a wiki (else current wiki) | nice-to-have |
(future) cql |
CQL query support | later |
Filtering to type:DOCUMENT and hidden:false (for users not showing hidden docs) is applied internally as fq, mirroring SOLRSearchSource.
Entry store: get(LiveDataQuery) algorithm
- Read
queryparam from merged source parameters; if absent default to*:*. Query q = queryManager.createQuery(queryParam, SolrQueryExecutor.SOLR).((SecureQuery) q).checkCurrentUser(true)→ enforce view rights.- Build
fq:type:("DOCUMENT"); addhidden:(false)unless the user has programming rights; add any callerfq/wiki. - Translate
LiveDataQuery.filters→ Solrfqclauses. EachFiltermaps to onefq; constraints within a filter are joined by OR/AND permatchAll. Operator mapping (v1 minimal):equals→field:"value"contains→field:*value*startsWith→field:value*- Values are escaped with Solr
ClientUtils.escapeQueryCharsto prevent query injection.
- Translate
LiveDataQuery.sort→ Solrsort, mapping the LD property id to the sortable Solr field (e.g.title→title_sort). Default sort configurable; for backlinks, by title. q.setOffset(query.getOffset()),q.setLimit(query.getLimit()).- Execute; take
response.getResults(). - For each
SolrDocument, build an entryMap<String,Object>containing only the requestedquery.getProperties(), e.g.:doc.title←title(fallback toname/fullnameif blank)doc.url/ link ← fromREFERENCEresolved to aDocumentReference(reuse thesolrDocumentReferenceResolver<SolrDocument>) → built into a view URL- additional fields as configured
LiveData ld = new LiveData(); ld.setCount(results.getNumFound()); ld.getEntries().addAll(entries);
Known caveat — count vs. security filtering
SolrQueryExecutor filters results after Solr returns them, but getNumFound() reflects the pre-filter total. So count can be slightly higher than the number of viewable rows (same limitation the REST search has). Acceptable for v1; document it. A proper fix is a Solr PostFilter (out of scope, noted in SOLRSearchSource too).
Property store: get() (columns) — configurable via source params
Decision: columns are configurable through source parameters, defaulting to title + link.
- Default descriptors when no column config is given:
doc.title— typeString, displayerlink(links to the page), sortable (title_sort), filterable (contains).
- A source parameter (e.g.
fields/columns) lets callers declare additional columns by Solr field name. For each declared field the property store emits a descriptor (id = the LD property, mapped to its Solr field +*_sortvariant for sorting where one exists), and the entry store includes that field in the result mapping. - This keeps the generic source minimal while letting specializations (e.g. backlinks, or any future table) tailor the visible columns without new Java. Later iterations can derive descriptors dynamically from the Solr schema /
FieldUtils(types, sortability) instead of relying on the caller.
Backlinks specialization (the XWIKI-22733 payoff)
No new Java needed beyond the generic source. The backlinks UI (e.g. a panel or the page “Information” area) declares a Live Data using:
source = solrquery = links:"<serialized reference of the current document>"- columns = title (as link)
This replaces the non-scaling backlinks query with a paginated, sortable, filterable, rights-aware Solr-backed table. Optionally wrap it in a small macro/template that injects the current doc reference into the query parameter.
Testing
- Unit tests for the entry store (mock
QueryManager/Query/QueryResponse, assert the generated query string,fq,sort, offset/limit, and entry mapping) — mirrorLiveTableLiveDataEntryStoreTestandSolrKeywordSearchSourceTest. - Unit test the filter/operator → Solr translation and escaping.
- A functional
@UITest(later) verifying a backlinks Live Data renders, paginates and sorts against a seeded Solr index.
Phasing
- v1: generic
solrsource — arbitraryqueryparam, title + link columns, filter/sort translation, view-rights viaSecureQuery, count fromnumFound. Wire up backlinks for XWIKI-22733. - v2: richer/configurable columns, dynamic descriptors from the Solr schema, more operators, multi-wiki, CQL input.
- v3 (if needed): PostFilter-based accurate counts; option to load documents for column metadata reuse.
Decisions (confirmed)
- Module:
xwiki-platform-livedata-solr, placed underxwiki-platform-livedata(sibling of-livetable). - v1 columns: configurable via source parameters, defaulting to title + link.
- Backlinks: this issue (XWIKI-23940) delivers the generic Solr source only; the backlinks UI wiring is done separately under XWIKI-22733.
Let me know if you see something wrong. Main issue I see ATM is the count (see above).
I’ll soon have a PR to show.
Thanks