Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/components/map-projects/MapProject.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ import ProjectLogs from './ProjectLogs';
import { useAlgos, ensureConceptIdentity } from './algorithms'
import AutoMatchDialog from './AutoMatchDialog'
import { DEFAULT_ENCODER_MODEL } from './rerankerModels'
import { normalizeAlgorithmInvocation, lookupStatusRank, buildRecommendableConceptEntry, stripConstantClassAndDatatype } from './normalizers'
import { normalizeAlgorithmInvocation, lookupStatusRank, buildRecommendableConceptEntry, stripConstantClassAndDatatype, buildLookupConceptUrl } from './normalizers'
import { parseConceptKey } from './conceptKey'
import { getDefaultTargetRepoVersion, getProjectTargetRepoVersion, getTargetRepoVersionFromUrl, getTargetRepoVersionId } from './projectTargetRepo'
import { buildQualityRowViews, conceptBelongsToTargetRepo, conceptForMapping, resolveAICandidateID } from './viewBuilders.js'
Expand Down Expand Up @@ -3303,7 +3303,19 @@ const MapProject = () => {
pending.push(promise)

if(def?.ocl_url) {
directFetches.push({key, oclUrl: def.ocl_url})
// The target repo may be a definition-only canonical repo whose content
// is served from a configured custom lookup source (e.g. the public WHO
// ICD-11 repo, kept content-empty under WHO's license). Concepts that
// resolve to the target repo — notably ocl-bridge cascade targets, whose
// ocl_url points INTO the (empty) target repo — 404 on a direct GET.
// Divert those to the lookup source by code. Branch 2 ($resolveReference,
// below) already honors lookupConfig.url; this brings the direct-fetch
// branch in line. See ocl_issues#2558.
const tr = ctx?.target_repo
const divertedUrl = (lookupConfig?.url && tr && conceptBelongsToTargetRepo(def, tr.canonical_url, tr.relative_url))
? buildLookupConceptUrl(lookupConfig.url, def.reference?.code)
: null
directFetches.push({key, oclUrl: divertedUrl || def.ocl_url})
} else {
try {
const reference = parseConceptKey(key)
Expand Down
45 changes: 44 additions & 1 deletion src/components/map-projects/__tests__/normalizers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import {
filterAndTrimNames,
filterAndTrimDescriptions,
primarySubtag,
uniqByPrimarySubtag
uniqByPrimarySubtag,
buildLookupConceptUrl
} from '../normalizers.js'
import { makeConceptKey, parseConceptKey, referencesEqual } from '../conceptKey.js'

Expand Down Expand Up @@ -1569,3 +1570,45 @@ test('buildRecommendableConceptEntry: union effectiveLocales=["pt","en"] surface
const langs = entry.names.map(n => primarySubtag(n.locale)).sort()
assert.deepEqual(langs, ['en', 'en', 'pt'])
})

// ---------- buildLookupConceptUrl ----------
// URL builder used by MapProject.ensureLoaded to divert target-repo concept
// fetches to the configured custom lookup source. ocl_issues#2558.
// (The "should this divert?" predicate is conceptBelongsToTargetRepo in
// viewBuilders.js; this helper only builds the lookup-source URL.)

const LOOKUP_URL = '/orgs/OpenMRS-OCL-Squad/sources/ICD-11-WHO-Mapper/'

test('buildLookupConceptUrl: builds a lookup-source concept URL by code', () => {
assert.equal(
buildLookupConceptUrl(LOOKUP_URL, 'BD11.Z'),
'/orgs/OpenMRS-OCL-Squad/sources/ICD-11-WHO-Mapper/concepts/BD11.Z/'
)
})

test('buildLookupConceptUrl: double-encodes cluster (&) and compound (/) codes', () => {
// Matches the $resolveReference (Branch 2) double-encode so both fetch paths
// address the same concept (verified against successful run fetches).
assert.equal(
buildLookupConceptUrl(LOOKUP_URL, 'ND92.1&XA55T2'),
'/orgs/OpenMRS-OCL-Squad/sources/ICD-11-WHO-Mapper/concepts/ND92.1%2526XA55T2/'
)
assert.equal(
buildLookupConceptUrl(LOOKUP_URL, 'GB4Y/MF8Y'),
'/orgs/OpenMRS-OCL-Squad/sources/ICD-11-WHO-Mapper/concepts/GB4Y%252FMF8Y/'
)
})

test('buildLookupConceptUrl: appends a trailing slash to the base when missing', () => {
assert.equal(
buildLookupConceptUrl('/orgs/X/sources/Y', 'QC00.5'),
'/orgs/X/sources/Y/concepts/QC00.5/'
)
})

test('buildLookupConceptUrl: returns null on missing input', () => {
assert.equal(buildLookupConceptUrl(undefined, 'BD11.Z'), null)
assert.equal(buildLookupConceptUrl('', 'BD11.Z'), null)
assert.equal(buildLookupConceptUrl(LOOKUP_URL, undefined), null)
assert.equal(buildLookupConceptUrl(LOOKUP_URL, ''), null)
})
19 changes: 19 additions & 0 deletions src/components/map-projects/normalizers.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,25 @@ const resolveReference = (result, identityConfig, projectContext) => {
return version ? { url, code, version } : { url, code }
}

/**
* Build the concept-detail URL for a custom lookup source, addressed by code.
*
* Mirrors the `$resolveReference` (Branch 2) fetch path in
* MapProject.ensureLoaded — same base + double-encode — so a code resolves to
* the identical URL whether it arrives with an `ocl_url` (direct branch) or via
* `$resolveReference`. The double-encode keeps cluster (`&`) and compound (`/`)
* codes addressable.
*
* @param {string} [lookupUrl] lookupConfig.url — the custom lookup source
* @param {string} [code] the concept code
* @returns {string|null} the lookup-source concept URL, or null on missing input
*/
export const buildLookupConceptUrl = (lookupUrl, code) => {
if (!lookupUrl || !code) return null
const base = lookupUrl.endsWith('/') ? lookupUrl : `${lookupUrl}/`
return `${base}concepts/${encodeURIComponent(encodeURIComponent(code))}/`
}

/**
* Build a ConceptDefinition from a concept-shaped result + a resolved reference.
*/
Expand Down