Skip to content

Conversation

@ODORA0
Copy link
Member

@ODORA0 ODORA0 commented Sep 29, 2025

Requirements

  • This PR has a title that briefly describes the work done including the ticket number. Ensure your PR title includes a conventional commit label (such as feat, fix, or chore, among others). See existing PR titles for inspiration.

If applicable

  • My work is based on designs, which are linked or shown either in the Jira ticket or the description below.
  • My work includes tests or is validated by existing tests.
  • I have updated the esm-framework mock to reflect any API changes I have made.

Summary

  • Updated fetchInstalledBackendModules to follow links[rel="next"] until exhausted
  • Normalized relative next URLs to prevent broken fetches
  • Switched to v=default representation to avoid 400 Bad Request errors on some servers
  • Mapped results to uuid for proper dependency matching
  • Ensures modules like Reporting are correctly recognized and avoids false "unresolved backend dependencies" errors

Screenshots

Related Issue

Other

@alaboso alaboso requested review from denniskigen and ibacher October 2, 2025 14:15
@alaboso alaboso marked this pull request as ready for review October 2, 2025 14:15
Copy link
Member

@ibacher ibacher left a comment

Choose a reason for hiding this comment

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

Thanks @ODORA0! Just a couple of nits and it should be ok.

nextUrl = resolveNext(nextLink ? nextLink.uri || nextLink.href || null : null);
safetyCounter += 1;
} catch (e) {
console.error('Failed to fetch installed backend modules page', e);
Copy link
Member

Choose a reason for hiding this comment

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

Would be good to include more details like what page we were trying to fetch, etc.

const collected: Array<BackendModule> = [];
let nextUrl: string | null = `${restBaseUrl}/module?v=default`;
let safetyCounter = 0;
const MAX_PAGES = 50;
Copy link
Member

Choose a reason for hiding this comment

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

If this is actually intended as a const it should be defined outside the function. Otherwise, just call this maxPages.


// Use moduleid/moduleId for name matching against required dependency keys (e.g., 'reporting')
const pageResults: Array<BackendModule> = rawResults.map((r) => ({
uuid: r.moduleId ?? r.moduleid ?? r.uuid,
Copy link
Member

Choose a reason for hiding this comment

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

While the API can return moduleId (but never moduleid), we're always requesting the default view where the property will always be uuid

Suggested change
uuid: r.moduleId ?? r.moduleid ?? r.uuid,
uuid: r.uuid,

const links: Array<{ rel?: string; uri?: string; href?: string }> = Array.isArray(data?.links) ? data.links : [];
const nextLink = links.find((l) => (l.rel || '').toLowerCase() === 'next');

nextUrl = resolveNext(nextLink ? nextLink.uri || nextLink.href || null : null);
Copy link
Member

Choose a reason for hiding this comment

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

For the REST API, we always return the property uri if anything.

Suggested change
nextUrl = resolveNext(nextLink ? nextLink.uri || nextLink.href || null : null);
nextUrl = resolveNext(nextLink?.uri ?? null);

});
async function fetchInstalledBackendModules(): Promise<Array<BackendModule>> {
const collected: Array<BackendModule> = [];
let nextUrl: string | null = `${restBaseUrl}/module?v=default`;
Copy link
Member

@denniskigen denniskigen Oct 7, 2025

Choose a reason for hiding this comment

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

Suggested change
let nextUrl: string | null = `${restBaseUrl}/module?v=default`;
let nextUrl: string | null = `${restBaseUrl}/module?v=custom:(uuid,version)`;

Can we whittle the custom representation down to just fetching the uuid and version properties? That substantially reduces the payload size from ~10 fields per module down to just 2. The response would look like this:

{
  "results": [
    {
      "uuid": "serialization.xstream",
      "version": "0.2.16"
    },
    {
      "uuid": "authentication",
      "version": "2.0.0-SNAPSHOT"
    },
    {
      "uuid": "fhir2",
      "version": "2.8.0-SNAPSHOT"
    }
  ]
}

versus this in the default response:

{
  "results": [
    {
      "uuid": "serialization.xstream",
      "display": "Serialization Xstream",
      "name": "Serialization Xstream",
      "description": "Core (de)serialize API and services supported by xstream library",
      "version": "0.2.16",
      "started": true,
      "startupErrorMessage": null,
      "links": [
        {
          "rel": "full",
          "uri": "https://dev3.openmrs.org/openmrs/ws/rest/v1/module/serialization.xstream?v=full",
          "resourceAlias": "module"
        },
        {
          "rel": "ref",
          "uri": "https://dev3.openmrs.org/openmrs/ws/rest/v1/module/serialization.xstream?v=ref",
          "resourceAlias": "module"
        },
        {
          "rel": "action",
          "uri": "https://dev3.openmrs.org/openmrs/ws/rest/v1/moduleaction",
          "resourceAlias": "module"
        },
        {
          "rel": "self",
          "uri": "https://dev3.openmrs.org/openmrs/ws/rest/v1/module/serialization.xstream",
          "resourceAlias": "module"
        }
      ],
      "resourceVersion": "1.8"
    },
    {
      "uuid": "authentication",
      "display": "Authentication",
      "name": "Authentication",
      "description": "Authentication support for OpenMRS",
      "version": "2.0.0-SNAPSHOT",
      "started": true,
      "startupErrorMessage": null,
      "links": [
        {
          "rel": "full",
          "uri": "https://dev3.openmrs.org/openmrs/ws/rest/v1/module/authentication?v=full",
          "resourceAlias": "module"
        },
        {
          "rel": "ref",
          "uri": "https://dev3.openmrs.org/openmrs/ws/rest/v1/module/authentication?v=ref",
          "resourceAlias": "module"
        },
        {
          "rel": "action",
          "uri": "https://dev3.openmrs.org/openmrs/ws/rest/v1/moduleaction",
          "resourceAlias": "module"
        },
        {
          "rel": "self",
          "uri": "https://dev3.openmrs.org/openmrs/ws/rest/v1/module/authentication",
          "resourceAlias": "module"
        }
      ],
      "resourceVersion": "1.8"
    },
    {
      "uuid": "fhir2",
      "display": "FHIR2",
      "name": "FHIR2",
      "description": "Implementation of FHIR R4 for OpenMRS",
      "version": "2.8.0-SNAPSHOT",
      "started": true,
      "startupErrorMessage": null,
      "links": [
        {
          "rel": "full",
          "uri": "https://dev3.openmrs.org/openmrs/ws/rest/v1/module/fhir2?v=full",
          "resourceAlias": "module"
        },
        {
          "rel": "ref",
          "uri": "https://dev3.openmrs.org/openmrs/ws/rest/v1/module/fhir2?v=ref",
          "resourceAlias": "module"
        },
        {
          "rel": "action",
          "uri": "https://dev3.openmrs.org/openmrs/ws/rest/v1/moduleaction",
          "resourceAlias": "module"
        },
        {
          "rel": "self",
          "uri": "https://dev3.openmrs.org/openmrs/ws/rest/v1/module/fhir2",
          "resourceAlias": "module"
        }
      ],
      "resourceVersion": "1.8"
    }
  ]
}

const { data } = await openmrsFetch(nextUrl, { method: 'GET' });
const rawResults: Array<any> = Array.isArray(data?.results) ? data.results : [];

// Use moduleid/moduleId for name matching against required dependency keys (e.g., 'reporting')
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Use moduleid/moduleId for name matching against required dependency keys (e.g., 'reporting')

This is misleading because the API's uuid field already contains module IDs like "webservices.rest". The moduleId/moduleid fields don't exist in the API response.

while (nextUrl && safetyCounter < MAX_PAGES) {
try {
const { data } = await openmrsFetch(nextUrl, { method: 'GET' });
const rawResults: Array<any> = Array.isArray(data?.results) ? data.results : [];
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
const rawResults: Array<any> = Array.isArray(data?.results) ? data.results : [];
const rawResults: Array<BackendModule> = Array.isArray(data?.results) ? data.results : [];

@denniskigen denniskigen changed the title fix(implementer-tools): handle paginated backend module fetch and correct moduleId mapping (fix) Fetch all backend modules in implementer tools when count exceeds page limit Oct 7, 2025
@denniskigen denniskigen requested a review from ibacher October 9, 2025 11:25
Copy link
Member

@denniskigen denniskigen left a comment

Choose a reason for hiding this comment

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

Thanks, @ODORA0!

@denniskigen denniskigen enabled auto-merge (squash) October 9, 2025 19:34
@denniskigen denniskigen merged commit d6d451f into openmrs:main Oct 9, 2025
14 checks passed
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.

3 participants