Skip to content

Feature/graphql sources 20260906 - #897

Draft
filiperochalopes wants to merge 3 commits into
OpenConceptLab:masterfrom
filiperochalopes:feature/graphql-sources-20260906
Draft

Feature/graphql sources 20260906#897
filiperochalopes wants to merge 3 commits into
OpenConceptLab:masterfrom
filiperochalopes:feature/graphql-sources-20260906

Conversation

@filiperochalopes

@filiperochalopes filiperochalopes commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Solves: OpenConceptLab/ocl_issues#2757

Why? CIEL Lab needs improvements when loading worklists to load fast using ES-only retrieve for minimal payloads
Also: OpenConceptLab/ocl_issues#2577

GraphQL: permission-aware source and concept projections

Context

OCL's REST search latency is a bottleneck for several existing flows and blocks new ones from
being viable. Many searches only need a small, fixed payload (a display name, a description, a
few metadata fields) — hydrating a full ORM object and its relations for that is unnecessary
cost. This PR introduces a GraphQL path that serves these minimal payloads straight from
Elasticsearch, without touching the database, while keeping full-object retrieval available
for callers that need it.

Design principles

  • Minimal payload → no hydration. A request for a minimal payload should be answerable from
    indexed filters alone, without a database round-trip. Whether to hydrate, and how much, is a
    per-request decision driven by which fields the caller (source or concept payload) actually
    selects — not a fixed retrieval mode for the whole query.
  • Share search classes with REST. Reuse REST's existing Elasticsearch query/filter
    building blocks instead of duplicating them for GraphQL.
  • Share authorization classes with REST. Visibility and permission rules stay centralized
    in one place and apply identically to both REST and GraphQL, wherever that's possible.
  • Retrieval path may still differ. Because the rules that apply depend on the dynamic
    payload requested, GraphQL's retrieval path (index-only vs. hydrated) can legitimately differ
    from REST's, even while reusing REST's search and authorization building blocks.

Documentation is free and detailed

image image image

⚠️ Breaking change — reindex before deploy

Four fields are new on the search documents: ConceptDocument.is_active, .is_head,
.display_name, and SourceDocument.is_active. They're additive to the mapping, but existing
documents don't have them populated, and Elasticsearch can't backfill them on its own.

If you deploy without reindexing first, concepts searches silently return zero results.
The index projection filters on is_active/is_head; old documents don't match, and a
zero-hit Elasticsearch response is not an error — it's {"totalCount": 0, "results": []},
indistinguishable from a legitimate empty search. (source degrades safely instead: missing
is_active there falls back to the database automatically.)

Reindex before routing traffic:

docker exec <api-container> python manage.py search_index --populate --models sources.Source concepts.Concept -f --parallel

Treat this as a pre-deploy gate, not cleanup — there's no error to notice afterward.

Known limitation

display_name is derived from the source's default_locale/supported_locales. Changing a
source's locale configuration does not trigger a concept reindex (persist_changes only
reindexes concepts on released or match-algorithm changes), so previously indexed
display_name values go stale until a manual reindex. Not fixed in this PR; scope kept to the
permission/projection work.

New GraphQL API surface

query Dictionary($org: String!, $source: String!, $version: String) {
  source(org: $org, source: $source, version: $version) {
    name description canonicalUrl uri
    classes datatypes mapTypes
    externalSources { name url }
    summary { activeConcepts mappings }
  }
}

query FindConcepts($org: String, $source: String, $query: String!, $page: Int, $limit: Int) {
  concepts(org: $org, source: $source, query: $query, page: $page, limit: $limit) {
    totalCount hasNextPage versionResolved
    results { conceptId display description conceptClass datatype { name } }
  }
}
  • conceptIds does exact, case-sensitive mnemonic matching, dedupes, preserves order, and
    takes precedence over query. Omit both org and source for global search.
  • page/limit go together; result window is 10,000 (totalCount still reflects the true total).
  • Omitted version resolves to HEAD, falling back to the latest release only if HEAD is
    absent; an explicit missing version does not fall back.
  • Same auth as REST (OCL token, OIDC bearer, session); anonymous callers see public data only.

Permissions

Reuses the existing REST visibility rule directly. Concept visibility relies on the indexed
public_can_view flag, already kept in sync from the parent repository by
core/sources/signals.py whenever the repository's access changes.

Not indexed, by design

  • description — locale-resolved per concept; selecting it routes the whole request
    through the ORM.
  • uri — rebuilt at query time via Django's reverse() from indexed owner,
    owner_type, mnemonic, version, reproducing the ORM's percent-encoding exactly (verified
    against all sources in the database, including versions with reserved characters).

Compatibility

No database migrations, no new environment variables. Existing REST search relevance and
behavior are unchanged.

Testing

68 GraphQL tests + 249 concepts/sources regression tests passing. Integration suite runs
against real Elasticsearch with temporary indexes, covering zero-SQL projections, owner
isolation, HEAD/release selection, inactive/retired filtering, private-repository visibility,
the rebuilt source URI, and the database fallback for description. Pylint clean (10.00/10).

* Update ConceptDocument to use `display_name` field instead of `name` for GraphQL projections

* Ensure GraphQL resolvers and tests correctly utilize the `display_name` field in the Elasticsearch index

* This change allows clients to retrieve the human-readable display name instead of the internal name when fetching concepts via GraphQL
@filiperochalopes
filiperochalopes marked this pull request as draft September 7, 2026 20:40
@filiperochalopes filiperochalopes self-assigned this Sep 7, 2026
@filiperochalopes

Copy link
Copy Markdown
Contributor Author

Still draft, but updated, please consider check @snyaggarwal

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 091bfb0377

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread core/graphql/indexed.py Outdated
Comment on lines +25 to +26
'display': ('display_name',),
'description': ('preferred_description',),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reindex locale-derived fields after source locale changes

When a source's default_locale or supported_locales changes, these projections continue serving the previously indexed display_name and preferred_description, even though both values are derived from the parent source's locale configuration. Source persistence only reindexes concepts for release or match-algorithm changes, and the new signal propagation handles only activity and visibility, so a lean query requesting display or description remains stale indefinitely while the ORM path returns the new locale selection. Reindex the source's concepts when the relevant locale settings change, or avoid directly projecting these derived fields.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think we have use case for it. Seems harmless, but I will create a ticket for it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* Add `display_name` field to `ConceptDocument` projections

* Refactor source projection URI to be rebuilt instead of stored to ensure data integrity and accurate URL generation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant