diff --git a/openspec/changes/fix-tracker-ou-params-d2-api/.openspec.yaml b/openspec/changes/fix-tracker-ou-params-d2-api/.openspec.yaml new file mode 100644 index 00000000..4e618347 --- /dev/null +++ b/openspec/changes/fix-tracker-ou-params-d2-api/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-03-19 diff --git a/openspec/changes/fix-tracker-ou-params-d2-api/design.md b/openspec/changes/fix-tracker-ou-params-d2-api/design.md new file mode 100644 index 00000000..4a0f7d22 --- /dev/null +++ b/openspec/changes/fix-tracker-ou-params-d2-api/design.md @@ -0,0 +1,52 @@ +## Context + +The Bulk Load app fetches tracked entity instances (TEIs) from DHIS2 via the `/tracker/trackedEntities` endpoint during template population and relationship metadata retrieval. Currently, it bypasses d2-api's typed tracker methods and uses raw `api.get()` calls with a custom `TrackedEntityGetRequest` interface that manually assembles query parameters. DHIS2 v42 renamed `ouMode` → `orgUnitMode` and `orgUnit` (semicolon-delimited) → `orgUnits` (comma-delimited), and also changed the response shape from `{ instances, pageCount }` to `{ pager: { pageCount }, trackedEntities }`. + +The d2-api library is being updated (PR #184) to handle these v42 changes in its typed `api.tracker.trackedEntities.get()` method. Rather than duplicating the parameter fix in Bulk Load, we should leverage d2-api's typed API. + +## Goals / Non-Goals + +**Goals:** +- Use d2-api's typed `api.tracker.trackedEntities.get()` for all tracked entity queries +- Remove manual parameter construction and response normalization code +- Ensure type safety via d2-api's parameter types (compile-time validation of param names) +- Maintain identical runtime behavior (same HTTP requests, same data flow) + +**Non-Goals:** +- Refactoring the events API calls (`/tracker/events`) — those remain as raw `api.get()` for now +- Adding backward compatibility with DHIS2 < v41 — the tracker API (`/tracker/trackedEntities`) is v38+ and the codebase already targets v41+ +- Changing the domain layer entities or use cases + +## Decisions + +### 1. Use d2-api's typed tracker API instead of raw `api.get()` + +**Decision**: Replace `api.get("/tracker/trackedEntities", filterQuery)` with `api.tracker.trackedEntities.get({ ... })`. + +**Rationale**: d2-api's typed method handles parameter serialization (field selectors, order params) and response mapping internally. This means Bulk Load doesn't need to maintain its own parameter types or response compat layer. When d2-api updates for future DHIS2 versions, the fix propagates automatically. + +**Alternative considered**: Keep raw `api.get()` but update param names (PR #386 approach). Simpler short-term but leaves the app coupled to specific DHIS2 API parameter naming. + +### 2. Derive parameter types from d2-api instead of maintaining custom interfaces + +**Decision**: Use `Parameters[0]` as the type for tracker query params. Remove `TrackedEntityGetRequest`, `TrackerParams`, and related types. + +**Rationale**: Custom types drift from d2-api's actual API. Deriving types from the library ensures they stay in sync. + +### 3. Use typed field selectors instead of comma-separated strings + +**Decision**: Replace `fields: "trackedEntity,inactive,orgUnit,attributes,enrollments,relationships,geometry"` with `fields: { trackedEntity: true, inactive: true, ... }`. + +**Rationale**: d2-api validates field selectors at compile time. String-based fields provide no type safety and can silently include invalid field names. + +### 4. Cast d2-api response to domain type via `as unknown as TrackedEntitiesApiRequest[]` + +**Decision**: In `getTeisFromApi`, the d2-api response is cast to the domain's `TrackedEntitiesApiRequest` type. + +**Rationale**: The d2-api response type uses `SelectedPick` which produces structurally compatible but nominally different types. A cast is the pragmatic choice since the shapes are identical at runtime. Longer term, the domain type could be replaced with d2-api's type directly. + +## Risks / Trade-offs + +- **[Dependency on unreleased d2-api]** → The d2-api PR #184 must be merged and published before this can be released. `package.json` temporarily points to the git branch. Mitigation: track d2-api PR status; update to published version once available. +- **[Type cast in getTeisFromApi]** → The `as unknown as TrackedEntitiesApiRequest[]` cast bypasses type checking at one boundary. Mitigation: the types are structurally identical; this can be removed if domain types are aligned with d2-api types in a future refactor. +- **[Events API not updated]** → The `/tracker/events` calls in `Dhis2RelationshipTypes.ts` still use raw `api.get()`. These may need similar treatment when d2-api's event types are updated. Mitigation: tracked as a separate follow-up. diff --git a/openspec/changes/fix-tracker-ou-params-d2-api/proposal.md b/openspec/changes/fix-tracker-ou-params-d2-api/proposal.md new file mode 100644 index 00000000..8a29b5da --- /dev/null +++ b/openspec/changes/fix-tracker-ou-params-d2-api/proposal.md @@ -0,0 +1,31 @@ +## Why + +DHIS2 v42 deprecated the `ouMode` and `orgUnit` (semicolon-delimited) query parameters on the `/tracker/trackedEntities` endpoint, replacing them with `orgUnitMode` and `orgUnits` (comma-delimited). The Bulk Load app currently bypasses d2-api's typed tracker methods and constructs raw `api.get()` calls with manually assembled parameters. This causes TEI population to silently return all accessible TEIs instead of only those in selected org units on v42 instances. The fix in d2-api PR #184 already addresses the parameter naming at the library level — this change refactors Bulk Load to use that typed API instead of duplicating the fix locally. + +## What Changes + +- Upgrade `@eyeseetea/d2-api` to the version that includes v42 tracker parameter support (PR #184) +- Update d2-api import path from `2.41` to `2.42` +- Replace all raw `api.get("/tracker/trackedEntities", ...)` calls with d2-api's typed `api.tracker.trackedEntities.get()` method +- Remove custom `TrackedEntityGetRequest` interface and `TrackerParams` type — d2-api's types handle parameter validation +- Remove `getTrackedEntities()` raw API wrapper function and `TrackedEntitiesD2ApiResponse` compat type +- Update response handling from `instances` / `pageCount` to `pager.pageCount` / `trackedEntities` (v42 response shape) +- Replace string-based field selectors (`"trackedEntity,inactive,orgUnit,..."`) with typed object selectors (`{ trackedEntity: true, ... }`) +- Rename `buildOrgUnitMode` → `buildOrgUnitParams` to reflect the v42 parameter naming + +## Capabilities + +### New Capabilities + +_(none — this is a refactor of existing functionality)_ + +### Modified Capabilities + +- `tracker-ou-filtering`: Implementation changes from raw API calls to d2-api typed methods. The behavioral requirements remain the same (orgUnitMode, comma-delimited orgUnits), but the mechanism shifts from manual parameter construction to leveraging d2-api's typed tracker API. + +## Impact + +- **Dependencies**: `@eyeseetea/d2-api` upgraded from `1.20.0` to the version including PR #184 (v42 tracker support). This is a **prerequisite** — the d2-api PR must be merged and released first. +- **Code**: `src/data/Dhis2TrackedEntityInstances.ts`, `src/data/Dhis2RelationshipTypes.ts`, `src/domain/entities/TrackedEntity.ts`, `src/types/d2-api.ts` +- **Removed exports**: `getTrackedEntities`, `TrackedEntityGetRequest`, `TrackedEntitiesD2ApiResponse` from `Dhis2TrackedEntityInstances.ts`; `buildOrgUnitMode` renamed to `buildOrgUnitParams` in `Dhis2RelationshipTypes.ts`; `TrackedEntitiesResponse` and `TrackedEntitiesAPIResponse` removed from `TrackedEntity.ts` +- **No behavioral changes**: The API calls produce identical HTTP requests; only the mechanism for constructing them changes. diff --git a/openspec/changes/fix-tracker-ou-params-d2-api/specs/tracker-ou-filtering.md b/openspec/changes/fix-tracker-ou-params-d2-api/specs/tracker-ou-filtering.md new file mode 100644 index 00000000..84162063 --- /dev/null +++ b/openspec/changes/fix-tracker-ou-params-d2-api/specs/tracker-ou-filtering.md @@ -0,0 +1,20 @@ +## MODIFIED Requirements + +### Requirement: Tracker API queries use d2-api typed methods + +All calls to the DHIS2 `/tracker/trackedEntities` endpoint SHALL use d2-api's typed `api.tracker.trackedEntities.get()` method instead of raw `api.get()` calls with manually constructed parameters. The d2-api library handles the v42 parameter naming (`orgUnitMode`, `orgUnits` comma-delimited) internally. + +#### Scenario: TEI population uses typed tracker API +- **WHEN** `getTeisFromApi` fetches tracked entities for template population +- **THEN** it SHALL call `api.tracker.trackedEntities.get()` with typed field selectors and `orgUnitMode`/`orgUnits` parameters via `buildOrgUnitParams()` +- **THEN** it SHALL NOT use raw `api.get("/tracker/trackedEntities", ...)` with string-based parameters + +#### Scenario: Existing TEI lookup uses typed tracker API +- **WHEN** `getExistingTeis` fetches all existing TEIs for relationship splitting +- **THEN** it SHALL call `api.tracker.trackedEntities.get()` with `orgUnitMode: "CAPTURE"` and typed field selectors +- **THEN** it SHALL NOT use the deprecated `ouMode` parameter name + +#### Scenario: Relationship constraint TEI lookup uses typed tracker API +- **WHEN** `getAllTrackedEntities` fetches TEIs for relationship constraint resolution +- **THEN** it SHALL call `api.tracker.trackedEntities.get()` with typed parameters +- **THEN** it SHALL handle the v42 response shape (`{ pager, trackedEntities }`) directly from d2-api diff --git a/openspec/changes/fix-tracker-ou-params-d2-api/tasks.md b/openspec/changes/fix-tracker-ou-params-d2-api/tasks.md new file mode 100644 index 00000000..672fad64 --- /dev/null +++ b/openspec/changes/fix-tracker-ou-params-d2-api/tasks.md @@ -0,0 +1,23 @@ +## 1. Upgrade d2-api dependency + +- [x] 1.1 [BE] Upgrade `@eyeseetea/d2-api` to the version including PR #184 (v42 tracker params) +- [x] 1.2 [BE] Update `src/types/d2-api.ts` imports from `@eyeseetea/d2-api/2.41` to `@eyeseetea/d2-api/2.42` + +## 2. Refactor tracker API calls to use d2-api typed methods + +- [x] 2.1 [BE] Replace `buildOrgUnitMode` with `buildOrgUnitParams` in `Dhis2RelationshipTypes.ts` — use `OrgUnitMode` from d2-api instead of the removed `TeiOuRequest` +- [x] 2.2 [BE] Refactor `getTeisFromApi` in `Dhis2TrackedEntityInstances.ts` to use `api.tracker.trackedEntities.get()` with typed field selectors instead of raw `api.get()` +- [x] 2.3 [BE] Refactor `getExistingTeis` in `Dhis2TrackedEntityInstances.ts` to use `api.tracker.trackedEntities.get()` with `orgUnitMode: "CAPTURE"` +- [x] 2.4 [BE] Refactor `getAllTrackedEntities` in `Dhis2RelationshipTypes.ts` to use `api.tracker.trackedEntities.get()` and handle the v42 response shape + +## 3. Remove obsolete types and compat code + +- [x] 3.1 [BE] Remove `TrackedEntityGetRequest` interface, `TrackerParams` type, and `getTrackedEntities` raw API wrapper from `Dhis2TrackedEntityInstances.ts` +- [x] 3.2 [BE] Remove `TrackedEntitiesD2ApiResponse` compat type from `Dhis2TrackedEntityInstances.ts` +- [x] 3.3 [BE] Remove `TrackedEntitiesResponse` and `TrackedEntitiesAPIResponse` backward-compat types from `TrackedEntity.ts` +- [x] 3.4 [BE] Define `TrackedEntityGeometryAttributes` locally (replaces import from removed `trackedEntityInstances` module) + +## 4. Verification + +- [x] 4.1 [BE] Verify TypeScript compilation passes (`npx tsc --noEmit`) with no new errors in modified files +- [x] 4.2 [BE] Verify all unit tests pass (`yarn test`) diff --git a/openspec/specs/tracker-ou-filtering/spec.md b/openspec/specs/tracker-ou-filtering/spec.md new file mode 100644 index 00000000..58e0cf7f --- /dev/null +++ b/openspec/specs/tracker-ou-filtering/spec.md @@ -0,0 +1,16 @@ +### Requirement: Tracker API queries use orgUnitMode parameter + +All calls to the DHIS2 `/tracker/trackedEntities` endpoint SHALL use the `orgUnitMode` query parameter instead of the deprecated `ouMode` parameter. This applies to TEI fetching for template population and relationship metadata retrieval. + +#### Scenario: TEI population with "Only selected organisation units" +- **WHEN** user downloads a tracker template with populate enabled and "TEI and relationships enrollment by organisation unit" set to "Only selected organisation units" +- **THEN** the API call to `/tracker/trackedEntities` SHALL include `orgUnitMode=SELECTED` and `orgUnits=` as query parameters +- **THEN** the returned TEIs SHALL only include those enrolled in the selected organisation units + +#### Scenario: TEI population with "Current user organisation units" +- **WHEN** user downloads a tracker template with populate enabled and OU filter set to "Current user organisation units (data capture)" +- **THEN** the API call to `/tracker/trackedEntities` SHALL include `orgUnitMode=CAPTURE` as query parameter + +#### Scenario: Relationship metadata fetching respects OU filter +- **WHEN** relationship metadata is fetched for TEI constraints during template population +- **THEN** the API call to `/tracker/trackedEntities` SHALL use `orgUnitMode` (not `ouMode`) with the user's selected OU filter mode diff --git a/package.json b/package.json index 0faada21..e5441b9b 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "@dhis2/d2-ui-core": "7.3.3", "@dhis2/ui-core": "6.24.0", "@dhis2/ui-widgets": "6.24.0", - "@eyeseetea/d2-api": "1.20.0", + "@eyeseetea/d2-api": "EyeSeeTea/d2-api#feature/tracker-orgunitparams-42-869beeny2", "@eyeseetea/d2-ui-components": "2.12.0", "@eyeseetea/feedback-component": "0.1.3-beta.3", "@eyeseetea/xlsx-populate": "4.3.2-beta.1", diff --git a/src/data/Dhis2RelationshipTypes.ts b/src/data/Dhis2RelationshipTypes.ts index 02f4ef16..a8dbe22a 100644 --- a/src/data/Dhis2RelationshipTypes.ts +++ b/src/data/Dhis2RelationshipTypes.ts @@ -1,4 +1,4 @@ -import { TeiOuRequest as TrackedEntityOURequestApi } from "@eyeseetea/d2-api/api/trackedEntityInstances"; +import { OrgUnitMode } from "@eyeseetea/d2-api/api/trackerTrackedEntities"; import _ from "lodash"; import moment from "moment"; import { NamedRef } from "../domain/entities/ReferenceObject"; @@ -9,25 +9,27 @@ import { D2Api, D2RelationshipConstraint, D2RelationshipType, Id, Ref } from ".. import { memoizeAsync } from "../utils/cache"; import { promiseMap } from "../utils/promises"; import { getUid } from "./dhis2-uid"; -import { getTrackedEntities, TrackedEntityGetRequest } from "./Dhis2TrackedEntityInstances"; import { TrackerRelationship, RelationshipItem, TrackedEntitiesApiRequest } from "../domain/entities/TrackedEntity"; import { buildOrgUnitsParameter } from "../domain/entities/OrgUnit"; import { EventsAPIResponse } from "../domain/entities/DhisDataPackage"; type RelationshipTypesById = Record>; -export type RelationshipOrgUnitFilter = TrackedEntityOURequestApi["ouMode"]; +export type RelationshipOrgUnitFilter = OrgUnitMode; -export function buildOrgUnitMode(ouMode: RelationshipOrgUnitFilter, orgUnits?: Ref[]) { - const isOuReq = ouMode === "SELECTED" || ouMode === "CHILDREN" || ouMode === "DESCENDANTS"; - //issue: v41 - orgUnitMode/ouMode; v38-40 ouMode; ouMode to be deprecated - //can't use both orgUnitMode and ouMode in v41 - if (!isOuReq) { - return { ouMode }; +export function buildOrgUnitParams( + orgUnitMode: RelationshipOrgUnitFilter, + orgUnits?: Ref[] +): { orgUnitMode: OrgUnitMode; orgUnits?: string } { + const requiresOrgUnits = + orgUnitMode === "SELECTED" || orgUnitMode === "CHILDREN" || orgUnitMode === "DESCENDANTS"; + + if (!requiresOrgUnits) { + return { orgUnitMode }; } else if (orgUnits && orgUnits.length > 0) { - return { ouMode, orgUnit: buildOrgUnitsParameter(orgUnits) }; + return { orgUnitMode, orgUnits: buildOrgUnitsParameter(orgUnits) }; } else { - throw new Error(`No orgUnits selected for ouMode ${ouMode}`); + throw new Error(`No orgUnits selected for orgUnitMode ${orgUnitMode}`); } } @@ -258,22 +260,21 @@ async function getConstraintForTypeTei( const { ouMode = "CAPTURE", organisationUnits = [] } = filters || {}; const trackedEntityTypesById = _.keyBy(trackedEntityTypes, obj => obj.id); - const ouModeQuery = buildOrgUnitMode(ouMode, organisationUnits); + const orgUnitParams = buildOrgUnitParams(ouMode, organisationUnits); const query = { - ...ouModeQuery, - order: "createdAt:asc", + ...orgUnitParams, + order: [{ type: "field" as const, field: "createdAt" as const, direction: "asc" as const }], program: constraint.program?.id, // Program and tracked entity cannot be specified simultaneously trackedEntityType: constraint.program ? undefined : constraint.trackedEntityType.id, pageSize: 1000, totalPages: true, - fields: "trackedEntity", - } as const; + fields: { trackedEntity: true as const }, + }; const results = await getAllTrackedEntities(api, query); - const trackedEntityInstances = results.map(({ trackedEntity, ...rest }) => ({ - ...rest, + const trackedEntityInstances = results.map(({ trackedEntity }) => ({ id: trackedEntity, })); @@ -283,12 +284,18 @@ async function getConstraintForTypeTei( return { type: "tei", name, program: constraint.program, teis }; } -async function getAllTrackedEntities(api: D2Api, query: TrackedEntityGetRequest): Promise { - const { instances: firstPage, pageCount } = await getTrackedEntities(api, query); +type TrackerGetParams = Parameters[0]; + +async function getAllTrackedEntities( + api: D2Api, + query: TrackerGetParams +): Promise<{ trackedEntity: string }[]> { + const { pager, trackedEntities: firstPage } = await api.tracker.trackedEntities.get(query).getData(); + const pageCount = pager.pageCount ?? 0; const pages = _.range(2, pageCount + 1); const otherPages = await promiseMap(pages, async page => { - const { instances } = await getTrackedEntities(api, { ...query, page }); - return instances; + const { trackedEntities } = await api.tracker.trackedEntities.get({ ...query, page }).getData(); + return trackedEntities; }); return [...firstPage, ..._.flatten(otherPages)]; diff --git a/src/data/Dhis2TrackedEntityInstances.ts b/src/data/Dhis2TrackedEntityInstances.ts index 441bbaa2..129a808d 100644 --- a/src/data/Dhis2TrackedEntityInstances.ts +++ b/src/data/Dhis2TrackedEntityInstances.ts @@ -1,4 +1,3 @@ -import { TeiGetRequest, TrackedEntityInstanceGeometryAttributes } from "@eyeseetea/d2-api/api/trackedEntityInstances"; import { generateUid } from "d2/uid"; import _ from "lodash"; import { Moment } from "moment"; @@ -12,11 +11,11 @@ import { AttributeValue, Enrollment, Program, TrackedEntityInstance } from "../d import { parseDate } from "../domain/helpers/ExcelReader"; import i18n from "../utils/i18n"; import { D2Api, D2RelationshipType, Id, Ref } from "../types/d2-api"; -import { KeysOfUnion } from "../types/utils"; +import { D2Geometry } from "@eyeseetea/d2-api/schemas"; import { promiseMap } from "../utils/promises"; import { getUid } from "./dhis2-uid"; import { - buildOrgUnitMode, + buildOrgUnitParams, fromApiRelationships, getApiRelationships, getRelationshipMetadata, @@ -24,8 +23,7 @@ import { RelationshipOrgUnitFilter, } from "./Dhis2RelationshipTypes"; import { ImportPostResponse, postImport } from "./Dhis2Import"; -import { TrackedEntitiesApiRequest, TrackedEntitiesResponse, TrackedEntity } from "../domain/entities/TrackedEntity"; -import { Params } from "@eyeseetea/d2-api/api/common"; +import { TrackedEntitiesApiRequest, TrackedEntity } from "../domain/entities/TrackedEntity"; import { ImportDataPackageOptions } from "../domain/repositories/InstanceRepository"; import { MULTI_TEXT_OPTION_DELIMITER } from "../domain/helpers/ExcelBuilder"; @@ -39,15 +37,10 @@ export interface GetOptions { relationshipsOuFilter?: RelationshipOrgUnitFilter; } -type TrackerParams = Params & Omit; - -export interface TrackedEntityGetRequest extends TrackerParams { - orgUnit?: string; - orgUnitMode?: TeiGetRequest["ouMode"]; - trackedEntity?: string; - enrollmentEnrolledAfter?: string; - enrollmentEnrolledBefore?: string; -} +type TrackedEntityGeometryAttributes = + | { featureType: "NONE" } + | { featureType: "POINT"; geometry: Extract } + | { featureType: "POLYGON"; geometry: Extract }; export async function getTrackedEntityInstances(options: GetOptions): Promise { const { @@ -68,26 +61,24 @@ export async function getTrackedEntityInstances(options: GetOptions): Promise { - const query = { - ouMode: "CAPTURE", - pageSize: 1000, - totalPages: true, - fields: "trackedEntity", - } as const; - // DHIS 2.37 added a new requirement: "Either Program or Tracked entity type should be specified" - // Requests to /api/trackedEntityInstances for these two params are singled-value, so we must - // perform multiple requests. Use Tracked Entity Types as tipically there will be more programs. + // Requests to /api/tracker/trackedEntities for these two params are single-value, so we must + // perform multiple requests. Use Tracked Entity Types as typically there will be more programs. const metadata = await api.metadata.get({ trackedEntityTypes: { fields: { id: true } } }).getData(); const teisGroups = await promiseMap(metadata.trackedEntityTypes, async entityType => { - const queryWithEntityType: TrackedEntityGetRequest = { ...query, trackedEntityType: entityType.id }; - - const { instances: firstPage, pageCount } = await getTrackedEntities(api, queryWithEntityType); + const query = { + orgUnitMode: "CAPTURE" as const, + trackedEntityType: entityType.id, + pageSize: 1000, + totalPages: true, + fields: { trackedEntity: true as const }, + }; + const { pager, trackedEntities: firstPage } = await api.tracker.trackedEntities.get(query).getData(); + const pageCount = pager.pageCount ?? 0; const pages = _.range(2, pageCount + 1); const otherPages = await promiseMap(pages, async page => { - const { instances: trackedEntityInstances } = await getTrackedEntities(api, { - ...queryWithEntityType, - page, - }); - - return trackedEntityInstances; + const { trackedEntities } = await api.tracker.trackedEntities.get({ ...query, page }).getData(); + return trackedEntities; }); - return [...firstPage, ..._.flatten(otherPages)].map(({ trackedEntity, ...rest }) => ({ - ...rest, + return [...firstPage, ..._.flatten(otherPages)].map(({ trackedEntity }) => ({ id: trackedEntity, })); }); @@ -500,7 +485,15 @@ async function getExistingTeis(api: D2Api): Promise { return _.flatten(teisGroups); } -type TeiKey = KeysOfUnion; +const teiFields = { + trackedEntity: true, + inactive: true, + orgUnit: true, + attributes: true, + enrollments: true, + relationships: true, + geometry: true, +} as const; async function getTeisFromApi(options: { api: D2Api; @@ -510,47 +503,30 @@ async function getTeisFromApi(options: { pageSize: number; enrollmentStartDate?: Moment; enrollmentEndDate?: Moment; - ouMode: RelationshipOrgUnitFilter; -}): Promise { - const { api, program, orgUnits, page, pageSize, enrollmentStartDate, enrollmentEndDate, ouMode } = options; - - const fields: TeiKey[] = [ - "trackedEntity", - "inactive", - "orgUnit", - "attributes", - "enrollments", - "relationships", - "geometry", - ]; - - const ouModeQuery = buildOrgUnitMode(ouMode, orgUnits); - - const filters: TrackedEntityGetRequest = { - ...ouModeQuery, - order: "createdAt:asc", - program: program.id, - pageSize: pageSize, - page: page, - totalPages: true as const, - fields: fields.join(","), - enrollmentEnrolledAfter: enrollmentStartDate?.format("YYYY-MM-DD[T]HH:mm"), - enrollmentEnrolledBefore: enrollmentEndDate?.format("YYYY-MM-DD[T]HH:mm"), - }; - const { instances, pageCount } = await getTrackedEntities(api, filters); + orgUnitMode: RelationshipOrgUnitFilter; +}): Promise<{ trackedEntities: TrackedEntitiesApiRequest[]; pageCount: number }> { + const { api, program, orgUnits, page, pageSize, enrollmentStartDate, enrollmentEndDate, orgUnitMode } = options; - return { instances, pageCount }; -} + const orgUnitParams = buildOrgUnitParams(orgUnitMode, orgUnits); -export async function getTrackedEntities( - api: D2Api, - filterQuery: TrackedEntityGetRequest -): Promise { - const { instances, trackedEntities, pager, pageCount } = await api - .get("/tracker/trackedEntities", filterQuery) + const { pager, trackedEntities } = await api.tracker.trackedEntities + .get({ + ...orgUnitParams, + order: [{ type: "field", field: "createdAt", direction: "asc" }], + program: program.id, + pageSize, + page, + totalPages: true, + fields: teiFields, + enrollmentEnrolledAfter: enrollmentStartDate?.format("YYYY-MM-DD[T]HH:mm"), + enrollmentEnrolledBefore: enrollmentEndDate?.format("YYYY-MM-DD[T]HH:mm"), + }) .getData(); - return { instances: instances || trackedEntities || [], pageCount: pageCount ?? pager?.pageCount ?? 0 }; + return { + trackedEntities: trackedEntities as unknown as TrackedEntitiesApiRequest[], + pageCount: pager.pageCount ?? 0, + }; } function buildTei( @@ -597,7 +573,7 @@ function buildTei( }; } -function getD2TeiGeometryAttributes(tei: TrackedEntityInstance): TrackedEntityInstanceGeometryAttributes { +function getD2TeiGeometryAttributes(tei: TrackedEntityInstance): TrackedEntityGeometryAttributes { const { geometry } = tei; switch (geometry.type) { @@ -663,15 +639,6 @@ export function generateUidForTei(teiId: Id, orgUnitId: Id, programId: Id): stri return getUid([teiId, orgUnitId, programId].join("-")); } -export type TrackedEntitiesD2ApiResponse = { - instances?: TrackedEntitiesApiRequest[]; - trackedEntities?: TrackedEntitiesApiRequest[]; - pager?: { - pageCount: number; - }; - pageCount?: number; -}; - type TEIId = string; type EventMap = Record; diff --git a/src/domain/entities/TrackedEntity.ts b/src/domain/entities/TrackedEntity.ts index feaf4fc3..9f31483b 100644 --- a/src/domain/entities/TrackedEntity.ts +++ b/src/domain/entities/TrackedEntity.ts @@ -44,12 +44,3 @@ export type TrackedEntitiesApiRequest = { trackedEntity: Id; }; -export type TrackedEntitiesResponse = { - instances: TrackedEntitiesApiRequest[]; - pageCount: number; -}; - -export type TrackedEntitiesAPIResponse = Omit & { - instances?: TrackedEntitiesApiRequest[]; - trackedEntities?: TrackedEntitiesApiRequest[]; -}; diff --git a/src/types/d2-api.ts b/src/types/d2-api.ts index d4047ba3..39ef65cf 100644 --- a/src/types/d2-api.ts +++ b/src/types/d2-api.ts @@ -1,6 +1,6 @@ -import { D2Api } from "@eyeseetea/d2-api/2.41"; +import { D2Api } from "@eyeseetea/d2-api/2.42"; -export * from "@eyeseetea/d2-api/2.41"; +export * from "@eyeseetea/d2-api/2.42"; export const D2ApiDefault = D2Api; export const getMockApi = () => { diff --git a/yarn.lock b/yarn.lock index 11d8faa8..3abf8aa8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4626,10 +4626,9 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@eyeseetea/d2-api@1.20.0": - version "1.20.0" - resolved "https://registry.yarnpkg.com/@eyeseetea/d2-api/-/d2-api-1.20.0.tgz#2a0dd75bf922c0734f32d249d7df457756087d3f" - integrity sha512-/Toy/1sWB3eqlC+YGLk+KUIe1IJ+ws40aqk+SKBnBCtyNr6TK+AaQF+S+aXQPFJF7Nxnkki08tWvQTQjCdaOVw== +"@eyeseetea/d2-api@EyeSeeTea/d2-api#feature/tracker-orgunitparams-42-869beeny2": + version "1.21.0-dhis2.42" + resolved "https://codeload.github.com/EyeSeeTea/d2-api/tar.gz/ae3cc7e62c474de1282618ee162047bc985dfe0e" dependencies: abort-controller "3.0.0" axios "1.6.4" @@ -4640,7 +4639,6 @@ iconv-lite "0.6.2" lodash "4.17.21" qs "6.9.7" - react "^16.12.0" "@eyeseetea/d2-ui-components@2.12.0": version "2.12.0" @@ -17012,15 +17010,6 @@ react@17.0.2: loose-envify "^1.1.0" object-assign "^4.1.1" -react@^16.12.0: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" - integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - reactcss@^1.2.0: version "1.2.3" resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd"