From ec1fe68b4c36ff0b14a68543947799bfb29ca312 Mon Sep 17 00:00:00 2001 From: Diana Nanyanzi Date: Fri, 22 May 2026 02:17:43 +0300 Subject: [PATCH 1/5] feat: remove API versioning from all API calls BREAKING CHANGE: apiVersion is not accepted by Config, DataEngineConfig, or DataProvider --- engine/src/links/RestAPILink.ts | 6 ++--- engine/src/links/RestAPILink/fetchData.ts | 7 +----- .../links/RestAPILink/queryToResourcePath.ts | 25 +------------------ engine/src/types/DataEngineConfig.ts | 1 - services/config/src/ConfigContext.ts | 1 - services/config/src/types.ts | 1 - .../src/react/components/DataProvider.tsx | 1 - services/data/src/types.ts | 1 - 8 files changed, 4 insertions(+), 39 deletions(-) diff --git a/engine/src/links/RestAPILink.ts b/engine/src/links/RestAPILink.ts index c452ee327..0111c2915 100644 --- a/engine/src/links/RestAPILink.ts +++ b/engine/src/links/RestAPILink.ts @@ -15,8 +15,7 @@ import { queryToResourcePath } from './RestAPILink/queryToResourcePath' export class RestAPILink implements DataEngineLink { public readonly config: DataEngineConfig - public readonly versionedApiPath: string - public readonly unversionedApiPath: string + public readonly apiPath: string public readonly queryAliasCache: QueryAliasCache = new LRUCache< string, @@ -25,8 +24,7 @@ export class RestAPILink implements DataEngineLink { public constructor(config: DataEngineConfig) { this.config = config - this.versionedApiPath = joinPath('api', String(config.apiVersion)) - this.unversionedApiPath = joinPath('api') + this.apiPath = joinPath('api') } private fetch(path: string, options: RequestInit): Promise { diff --git a/engine/src/links/RestAPILink/fetchData.ts b/engine/src/links/RestAPILink/fetchData.ts index 5fc3481be..5529186b1 100644 --- a/engine/src/links/RestAPILink/fetchData.ts +++ b/engine/src/links/RestAPILink/fetchData.ts @@ -82,12 +82,7 @@ const createQueryAlias = async ( refs: FetchDataRefs ) => { const alias = await fetchData( - joinPath( - refs.config.baseUrl, - 'api', - String(refs.config.apiVersion), - 'query/alias' - ), + joinPath(refs.config.baseUrl, 'api', 'query/alias'), { signal: requestOptions.signal, body: JSON.stringify({ target: url }), diff --git a/engine/src/links/RestAPILink/queryToResourcePath.ts b/engine/src/links/RestAPILink/queryToResourcePath.ts index ce518932f..5e5e7c2c6 100644 --- a/engine/src/links/RestAPILink/queryToResourcePath.ts +++ b/engine/src/links/RestAPILink/queryToResourcePath.ts @@ -1,4 +1,3 @@ -import type { DataEngineConfig } from '../../types/DataEngineConfig' import type { FetchType } from '../../types/ExecuteOptions' import type { ResolvedResourceQuery } from '../../types/Query' import type { @@ -71,24 +70,6 @@ const makeActionPath = (resource: string) => `${resource.substring(actionPrefix.length)}.action` ) -const skipApiVersion = ( - resource: string, - config: DataEngineConfig -): boolean => { - if (resource === 'tracker' || resource.startsWith('tracker/')) { - if (!config.serverVersion?.minor || config.serverVersion?.minor < 38) { - return true - } - } - - // The `/api/ping` endpoint is unversioned - if (resource === 'ping') { - return true - } - - return false -} - export const queryToResourcePath = ( link: RestAPILink, query: ResolvedResourceQuery, @@ -96,13 +77,9 @@ export const queryToResourcePath = ( ): string => { const { resource, id, params = {} } = query - const apiBase = skipApiVersion(resource, link.config) - ? link.unversionedApiPath - : link.versionedApiPath - const base = isAction(resource) ? makeActionPath(resource) - : joinPath(apiBase, resource, id) + : joinPath(link.apiPath, resource, id) validateResourceQuery(query, type) diff --git a/engine/src/types/DataEngineConfig.ts b/engine/src/types/DataEngineConfig.ts index 92963390b..a83e8b23a 100644 --- a/engine/src/types/DataEngineConfig.ts +++ b/engine/src/types/DataEngineConfig.ts @@ -1,6 +1,5 @@ export interface DataEngineConfig { baseUrl: string - apiVersion: number serverVersion?: { major: number minor: number diff --git a/services/config/src/ConfigContext.ts b/services/config/src/ConfigContext.ts index 4407e9316..144a345f9 100644 --- a/services/config/src/ConfigContext.ts +++ b/services/config/src/ConfigContext.ts @@ -3,5 +3,4 @@ import { Config } from './types' export const ConfigContext = React.createContext({ baseUrl: '..', - apiVersion: 32, }) diff --git a/services/config/src/types.ts b/services/config/src/types.ts index ad052ca6c..f9d6480e1 100644 --- a/services/config/src/types.ts +++ b/services/config/src/types.ts @@ -16,7 +16,6 @@ interface SystemInfo { export interface Config { baseUrl: string - apiVersion: number appName?: string appVersion?: Version serverVersion?: Version diff --git a/services/data/src/react/components/DataProvider.tsx b/services/data/src/react/components/DataProvider.tsx index 2f0f3af6f..b71d9c068 100644 --- a/services/data/src/react/components/DataProvider.tsx +++ b/services/data/src/react/components/DataProvider.tsx @@ -11,7 +11,6 @@ import { DataContext } from '../context/DataContext' export interface ProviderInput { baseUrl?: string - apiVersion?: number children: React.ReactNode } diff --git a/services/data/src/types.ts b/services/data/src/types.ts index b314cec8a..ec3340caa 100644 --- a/services/data/src/types.ts +++ b/services/data/src/types.ts @@ -15,7 +15,6 @@ export interface ContextType { export interface ContextInput { baseUrl: string - apiVersion: number } export type ExecuteOptions = QueryExecuteOptions From 36953e3e4897d7abadf7493c9375ed5ce969e131 Mon Sep 17 00:00:00 2001 From: Diana Nanyanzi Date: Fri, 22 May 2026 02:32:24 +0300 Subject: [PATCH 2/5] test: update tests to remove api version numbers --- engine/src/links/RestAPILink.test.ts | 5 ++-- .../RestAPILink/fetchData.aliasing.test.ts | 4 +-- .../src/links/RestAPILink/fetchData.test.ts | 1 - .../RestAPILink/queryToResourcePath.test.ts | 29 ++++--------------- .../config/src/__tests__/integration.test.tsx | 7 ++--- .../__tests__/useTimeZoneConversion.test.tsx | 4 +-- .../react/components/DataProvider.test.tsx | 2 +- .../dhis2-connection-status.test.tsx | 4 --- 8 files changed, 13 insertions(+), 43 deletions(-) diff --git a/engine/src/links/RestAPILink.test.ts b/engine/src/links/RestAPILink.test.ts index b784e5f77..823eb9f20 100644 --- a/engine/src/links/RestAPILink.test.ts +++ b/engine/src/links/RestAPILink.test.ts @@ -7,17 +7,16 @@ jest.mock('./RestAPILink/fetchData', () => ({ describe('RestAPILink', () => { it('should call fetch with the expected URL', async () => { - const link = new RestAPILink({ baseUrl: 'http://url', apiVersion: 42 }) + const link = new RestAPILink({ baseUrl: 'http://url' }) await link.executeResourceQuery('read', { resource: 'something' }, {}) expect(fetchData).toHaveBeenCalledWith( - 'http://url/api/42/something', + 'http://url/api/something', { method: 'GET', }, expect.objectContaining({ config: { baseUrl: 'http://url', - apiVersion: 42, }, queryAliasCache: expect.anything(), }) diff --git a/engine/src/links/RestAPILink/fetchData.aliasing.test.ts b/engine/src/links/RestAPILink/fetchData.aliasing.test.ts index ccfaaebaf..cd350f618 100644 --- a/engine/src/links/RestAPILink/fetchData.aliasing.test.ts +++ b/engine/src/links/RestAPILink/fetchData.aliasing.test.ts @@ -33,9 +33,8 @@ const makeResponse = ({ describe('fetchData - query alias creation and caching', () => { const baseUrl = 'http://example.com' - const apiVersion = 34 const longUrl = `${baseUrl}/very/long/query?with=params&that=make&the=uri&long` - const aliasEndpoint = `${baseUrl}/api/${apiVersion}/query/alias` + const aliasEndpoint = `${baseUrl}/api/query/alias` let refs: FetchRefs let aliasCreateCalls = 0 @@ -50,7 +49,6 @@ describe('fetchData - query alias creation and caching', () => { queryAliasCache: new LRUCache(10), config: { baseUrl, - apiVersion, apiToken: undefined, }, } diff --git a/engine/src/links/RestAPILink/fetchData.test.ts b/engine/src/links/RestAPILink/fetchData.test.ts index 02128a54a..58d50f1b4 100644 --- a/engine/src/links/RestAPILink/fetchData.test.ts +++ b/engine/src/links/RestAPILink/fetchData.test.ts @@ -136,7 +136,6 @@ describe('networkFetch', () => { const mockRefs = { config: { baseUrl: '', - apiVersion: 42, } as DataEngineConfig, queryAliasCache: new LRUCache(100), } diff --git a/engine/src/links/RestAPILink/queryToResourcePath.test.ts b/engine/src/links/RestAPILink/queryToResourcePath.test.ts index da0d272a6..b944241c8 100644 --- a/engine/src/links/RestAPILink/queryToResourcePath.test.ts +++ b/engine/src/links/RestAPILink/queryToResourcePath.test.ts @@ -6,7 +6,6 @@ import { queryToResourcePath } from './queryToResourcePath' const createLink = (config: DataEngineConfig) => new RestAPILink(config) const defaultConfig: DataEngineConfig = { basePath: '', - apiVersion: '37', serverVersion: { major: 2, minor: 37, @@ -16,7 +15,7 @@ const defaultConfig: DataEngineConfig = { } as unknown as DataEngineConfig const link = createLink(defaultConfig) -const apiPath = link.versionedApiPath +const apiPath = link.apiPath const actionPrefix = `dhis-web-commons/` const actionPostfix = '.action' @@ -170,39 +169,21 @@ describe('queryToResourcePath', () => { expect(() => queryToResourcePath(link, query, 'read')).toThrow() }) - it('should return an unversioned endpoint for the new tracker importer (in version 2.37)', () => { + it('should return an endpoint for the tracker resource', () => { const query: ResolvedResourceQuery = { resource: 'tracker', } expect(queryToResourcePath(link, query, 'read')).toBe( - `${link.unversionedApiPath}/tracker` + `${apiPath}/tracker` ) }) - it('should return an unversioned endpoint sub-resources of the new tracker importer (in version 2.37)', () => { + it('should return an unversioned endpoint for the tracker importer', () => { const query: ResolvedResourceQuery = { resource: 'tracker/test', } expect(queryToResourcePath(link, query, 'read')).toBe( - `${link.unversionedApiPath}/tracker/test` - ) - }) - - it('should return a VERSIONED endpoint for the new tracker importer (in version 2.38)', () => { - const query: ResolvedResourceQuery = { - resource: 'tracker', - } - const v38config: DataEngineConfig = { - ...defaultConfig, - serverVersion: { - major: 2, - minor: 38, - patch: 0, - full: '2.38.0', - }, - } as DataEngineConfig - expect(queryToResourcePath(createLink(v38config), query, 'read')).toBe( - `${link.versionedApiPath}/tracker` + `${apiPath}/tracker/test` ) }) }) diff --git a/services/config/src/__tests__/integration.test.tsx b/services/config/src/__tests__/integration.test.tsx index 1d0ea43c9..e2d3ed849 100644 --- a/services/config/src/__tests__/integration.test.tsx +++ b/services/config/src/__tests__/integration.test.tsx @@ -6,7 +6,6 @@ import { Config } from '../types' const mockConfig: Config = { baseUrl: 'http://test.com', - apiVersion: 42, serverVersion: { full: '2.35-SNAPSHOT', major: 2, @@ -23,9 +22,7 @@ const mockConfig: Config = { describe('Testing custom config provider', () => { it('Should render without failing', async () => { - const consumerFunction = jest.fn( - (config) => `${config.baseUrl}:${config.apiVersion}` - ) + const consumerFunction = jest.fn((config) => `${config.baseUrl}`) const { getByText } = render( @@ -34,7 +31,7 @@ describe('Testing custom config provider', () => { ) - expect(getByText(/http:\/\/test.com:42/i)).not.toBeUndefined() + expect(getByText(/http:\/\/test.com/i)).not.toBeUndefined() expect(consumerFunction).toHaveBeenCalledTimes(1) expect(consumerFunction).toHaveBeenLastCalledWith(mockConfig) }) diff --git a/services/config/src/__tests__/useTimeZoneConversion.test.tsx b/services/config/src/__tests__/useTimeZoneConversion.test.tsx index 933e8c0a1..fa626279e 100644 --- a/services/config/src/__tests__/useTimeZoneConversion.test.tsx +++ b/services/config/src/__tests__/useTimeZoneConversion.test.tsx @@ -2,7 +2,7 @@ import { renderHook } from '@testing-library/react' import React, { ReactNode } from 'react' import { ConfigProvider, useTimeZoneConversion } from '../index' -const defaultConfig = { baseUrl: '/', apiVersion: 40 } +const defaultConfig = { baseUrl: '/' } const defaultSystemInfo = { version: '40', contextPath: '', @@ -13,7 +13,7 @@ const defaultSystemInfo = { describe('useTimeZoneConversion', () => { it('Hook returns a fromClientDate and fromServerDate function', () => { - const config = { baseUrl: '/', apiVersion: 30 } + const config = { baseUrl: '/' } const wrapper = ({ children }: { children?: ReactNode }) => ( {children} ) diff --git a/services/data/src/react/components/DataProvider.test.tsx b/services/data/src/react/components/DataProvider.test.tsx index f60c0d3a5..36a3bf3cb 100644 --- a/services/data/src/react/components/DataProvider.test.tsx +++ b/services/data/src/react/components/DataProvider.test.tsx @@ -8,7 +8,7 @@ describe('DataProvider', () => { it('Should pass a new engine and RestAPILink to consumers', () => { const renderFunction = jest.fn() render( - + {renderFunction} ) diff --git a/services/offline/src/lib/dhis2-connection-status/dhis2-connection-status.test.tsx b/services/offline/src/lib/dhis2-connection-status/dhis2-connection-status.test.tsx index 1f63c4a16..0bef4b9fe 100644 --- a/services/offline/src/lib/dhis2-connection-status/dhis2-connection-status.test.tsx +++ b/services/offline/src/lib/dhis2-connection-status/dhis2-connection-status.test.tsx @@ -49,7 +49,6 @@ const wrapper: React.FC<{ children?: React.ReactNode }> = ({ children }) => ( { { { Date: Fri, 22 May 2026 03:23:50 +0300 Subject: [PATCH 3/5] chore: fix commit-lints job dependency version to v13 --- .github/workflows/dhis2-verify-commits.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dhis2-verify-commits.yml b/.github/workflows/dhis2-verify-commits.yml index d726c1722..22abfbebb 100644 --- a/.github/workflows/dhis2-verify-commits.yml +++ b/.github/workflows/dhis2-verify-commits.yml @@ -14,7 +14,7 @@ jobs: with: node-version: 20.x cache: 'yarn' - - run: yarn add -W @commitlint/config-conventional + - run: yarn add -W @commitlint/config-conventional@13 - id: commitlint run: | curl https://raw.githubusercontent.com/dhis2/cli-style/refs/heads/master/config/commitlint.config.js -o commitlint.config.js From abc3a5442933bc4fc6ffd344277b1fd51dab1cec Mon Sep 17 00:00:00 2001 From: Diana Nanyanzi Date: Fri, 22 May 2026 04:28:07 +0300 Subject: [PATCH 4/5] feat: remove api version instances from example app --- examples/cra/src/App.js | 1 - examples/cra/src/components/ConfigConsumer.js | 2 -- examples/cra/src/components/SwitchableProvider.js | 9 +-------- examples/standalone-engine-test.js | 8 ++------ 4 files changed, 3 insertions(+), 17 deletions(-) diff --git a/examples/cra/src/App.js b/examples/cra/src/App.js index baef8640f..a732fd877 100644 --- a/examples/cra/src/App.js +++ b/examples/cra/src/App.js @@ -8,7 +8,6 @@ import { SwitchableProvider } from './components/SwitchableProvider' const config = { baseUrl: process.env.REACT_APP_D2_BASE_URL || 'http://localhost:8080', - apiVersion: process.env.REACT_APP_D2_API_VERSION || 33, } const providerType = ( diff --git a/examples/cra/src/components/ConfigConsumer.js b/examples/cra/src/components/ConfigConsumer.js index 2f7fa2957..8315940dc 100644 --- a/examples/cra/src/components/ConfigConsumer.js +++ b/examples/cra/src/components/ConfigConsumer.js @@ -6,8 +6,6 @@ export const ConfigConsumer = () => { return ( Base url: {config.baseUrl} -
- API version: {config.apiVersion}
) } diff --git a/examples/cra/src/components/SwitchableProvider.js b/examples/cra/src/components/SwitchableProvider.js index 4a69402b6..b9931b174 100644 --- a/examples/cra/src/components/SwitchableProvider.js +++ b/examples/cra/src/components/SwitchableProvider.js @@ -4,14 +4,7 @@ import React from 'react' export const SwitchableProvider = ({ type, config, children }) => { if (type === 'data') { - return ( - - {children} - - ) + return {children} } else if (type === 'runtime') { return {children} } diff --git a/examples/standalone-engine-test.js b/examples/standalone-engine-test.js index ae6ebcb97..a8ebf1371 100644 --- a/examples/standalone-engine-test.js +++ b/examples/standalone-engine-test.js @@ -2,7 +2,7 @@ 'use strict' // Simple test script for @dhis2/data-engine -// Usage: node node-engine-test.js --url https://play.dhis2.org/2.37.8 --apiKey [--apiVersion 41] +// Usage: node node-engine-test.js --url https://play.dhis2.org/2.37.8 --apiKey const { DataEngine, RestAPILink } = require('@dhis2/data-engine') @@ -19,17 +19,14 @@ const parseArg = (name, short) => { const baseUrl = parseArg('url', 'u') || parseArg('baseUrl', 'b') const apiKey = parseArg('apiKey', 'k') || parseArg('api-key', 'k') -const apiVersionArg = parseArg('apiVersion', 'v') if (!baseUrl || !apiKey) { console.error( - 'Usage: node node-engine-test.js --url --apiKey [--apiVersion ]' + 'Usage: node node-engine-test.js --url --apiKey ' ) process.exit(2) } -const apiVersion = apiVersionArg ? Number(apiVersionArg) : 41 - // Ensure global fetch exists (Node 18+). If not present, instruct the user. if (typeof fetch === 'undefined') { console.error( @@ -42,7 +39,6 @@ async function main() { try { const config = { baseUrl, - apiVersion, apiToken: apiKey, } From adaaf953f62896e12a4c4a59e041f08a59cb5c13 Mon Sep 17 00:00:00 2001 From: Diana Nanyanzi Date: Fri, 22 May 2026 14:15:22 +0300 Subject: [PATCH 5/5] docs: update docs with api versioning removal --- docs/advanced/DataEngine.md | 2 -- docs/hooks/useConfig.md | 20 +++++++++----------- docs/provider.md | 1 - docs/types/Config.md | 29 +++++++++++++++++++++++++---- 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/docs/advanced/DataEngine.md b/docs/advanced/DataEngine.md index b41a308c2..8e7650924 100644 --- a/docs/advanced/DataEngine.md +++ b/docs/advanced/DataEngine.md @@ -32,7 +32,6 @@ import { // Specify the configuration object and construct a RestAPILink const config: DataEngineConfig = { baseUrl: 'https://my-dhis2-server.com', - apiVersion: 42, apiToken: 'MY_PERSONAL_ACCESS_TOKEN', } const link = new RestAPILink(config) @@ -66,7 +65,6 @@ The following configuration properties are supported: | property | required | description | | --------------- | -------: | ------------------------------------------------------------------------------------------------------------------ | | `baseUrl` | yes | Base URL of the DHIS2 server (for example: https://play.dhis2.org) | -| `apiVersion` | yes | Numeric API version to target (for example: `38`) | | `serverVersion` | no | Optional server version object with fields `major`, `minor`, optional `patch`, and `full` string (see table below) | | `apiToken` | no | Optional API token (personal access token) used for authentication when using token-based auth | diff --git a/docs/hooks/useConfig.md b/docs/hooks/useConfig.md index 0c8aefc62..d0676d6a8 100644 --- a/docs/hooks/useConfig.md +++ b/docs/hooks/useConfig.md @@ -8,7 +8,7 @@ Access the application configuration passed to the top-level [Provider](../provi import { useConfig } from '@dhis2/app-runtime' // Within a functional component body -const { baseUrl, apiVersion } = useConfig() +const { baseUrl } = useConfig() ``` ## Input @@ -26,15 +26,13 @@ import React from 'react' import { useConfig } from '@dhis2/app-runtime' export const MyComponent = () => { - const { baseUrl, apiVersion } = useConfig() - return - ;
- - Base URL : {baseUrl} - - - API Version : {apiVersion} - -
+ const { baseUrl } = useConfig() + return ( +
+ + Base URL : {baseUrl} + +
+ ) } ``` diff --git a/docs/provider.md b/docs/provider.md index 516167da2..e839f9e10 100644 --- a/docs/provider.md +++ b/docs/provider.md @@ -13,7 +13,6 @@ import { Provider } from '@dhis2/app-runtime' const appConfig = { baseUrl: 'play.dhis2.org/dev', - apiVersion: 33, } export const App = () => ( diff --git a/docs/types/Config.md b/docs/types/Config.md index 85c0b42ec..7ecbc597d 100644 --- a/docs/types/Config.md +++ b/docs/types/Config.md @@ -4,7 +4,28 @@ The Application Config type is a required input to the top-level [Provider](../provider.md), and can be accessed from within an application with the [useConfig](../hooks/useConfig.md) hook. -| Property | Type | Description | -| :------------: | :------: | --------------------------------------------------------------------------------------------------------------- | -| **baseUrl** | _string_ | The base URL of the DHIS2 Core server, i.e. `https://play.dhis2.org/dev` | -| **apiVersion** | _number_ | The default API version to use. API request resource paths will be expanded to `{baseUrl}/api/{version}/{path}` | +| Property | Type | Required | Description | +| :---------------: | :----------: | :------: | ------------------------------------------------------------------------ | +| **baseUrl** | _string_ | yes | The base URL of the DHIS2 Core server, i.e. `https://play.dhis2.org/dev` | +| **appName** | _string_ | no | The name of the application | +| **appVersion** | _Version_ | no | The version of the application | +| **serverVersion** | _Version_ | no | The DHIS2 server version object | +| **systemInfo** | _SystemInfo_ | no | Information about the DHIS2 server | + +### Version + +| Property | Type | Required | Description | +| :-------: | :------: | :------: | ------------------------------------------ | +| **full** | _string_ | yes | The full version string, e.g. `2.40.0` | +| **major** | _number_ | yes | The major version number | +| **minor** | _number_ | yes | The minor version number | +| **patch** | _number_ | no | The patch version number | +| **tag** | _string_ | no | An additional version tag, e.g. `SNAPSHOT` | + +### SystemInfo + +| Property | Type | Required | Description | +| :------------------: | :------: | :------: | ------------------------------- | +| **version** | _string_ | yes | The DHIS2 server version string | +| **contextPath** | _string_ | yes | The root path to application | +| **serverTimeZoneId** | _string_ | yes | The time zone ID of the server |