Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/dhis2-verify-commits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions docs/advanced/DataEngine.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 |

Expand Down
20 changes: 9 additions & 11 deletions docs/hooks/useConfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -26,15 +26,13 @@ import React from 'react'
import { useConfig } from '@dhis2/app-runtime'

export const MyComponent = () => {
const { baseUrl, apiVersion } = useConfig()
return
;<div>
<span>
<strong>Base URL</strong> : {baseUrl}
</span>
<span>
<strong>API Version</strong> : {apiVersion}
</span>
</div>
const { baseUrl } = useConfig()
return (
<div>
<span>
<strong>Base URL</strong> : {baseUrl}
</span>
</div>
)
}
```
1 change: 0 additions & 1 deletion docs/provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { Provider } from '@dhis2/app-runtime'

const appConfig = {
baseUrl: 'play.dhis2.org/dev',
apiVersion: 33,
}

export const App = () => (
Expand Down
29 changes: 25 additions & 4 deletions docs/types/Config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
5 changes: 2 additions & 3 deletions engine/src/links/RestAPILink.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
})
Expand Down
6 changes: 2 additions & 4 deletions engine/src/links/RestAPILink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<JsonValue> {
Expand Down
4 changes: 1 addition & 3 deletions engine/src/links/RestAPILink/fetchData.aliasing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -50,7 +49,6 @@ describe('fetchData - query alias creation and caching', () => {
queryAliasCache: new LRUCache<string, any>(10),
config: {
baseUrl,
apiVersion,
apiToken: undefined,
},
}
Expand Down
1 change: 0 additions & 1 deletion engine/src/links/RestAPILink/fetchData.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ describe('networkFetch', () => {
const mockRefs = {
config: {
baseUrl: '',
apiVersion: 42,
} as DataEngineConfig,
queryAliasCache: new LRUCache<string, QueryAlias>(100),
}
Expand Down
7 changes: 1 addition & 6 deletions engine/src/links/RestAPILink/fetchData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,7 @@ const createQueryAlias = async (
refs: FetchDataRefs
) => {
const alias = <QueryAlias>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 }),
Expand Down
29 changes: 5 additions & 24 deletions engine/src/links/RestAPILink/queryToResourcePath.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { queryToResourcePath } from './queryToResourcePath'
const createLink = (config: DataEngineConfig) => new RestAPILink(config)
const defaultConfig: DataEngineConfig = {
basePath: '<base>',
apiVersion: '37',
serverVersion: {
major: 2,
minor: 37,
Expand All @@ -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'
Expand Down Expand Up @@ -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`
)
})
})
25 changes: 1 addition & 24 deletions engine/src/links/RestAPILink/queryToResourcePath.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { DataEngineConfig } from '../../types/DataEngineConfig'
import type { FetchType } from '../../types/ExecuteOptions'
import type { ResolvedResourceQuery } from '../../types/Query'
import type {
Expand Down Expand Up @@ -71,38 +70,16 @@ 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,
type: FetchType
): 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)

Expand Down
1 change: 0 additions & 1 deletion engine/src/types/DataEngineConfig.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export interface DataEngineConfig {
baseUrl: string
apiVersion: number
serverVersion?: {
major: number
minor: number
Expand Down
1 change: 0 additions & 1 deletion examples/cra/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand Down
2 changes: 0 additions & 2 deletions examples/cra/src/components/ConfigConsumer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ export const ConfigConsumer = () => {
return (
<span>
<strong>Base url:</strong> {config.baseUrl}
<br />
<strong>API version:</strong> {config.apiVersion}
</span>
)
}
9 changes: 1 addition & 8 deletions examples/cra/src/components/SwitchableProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,7 @@ import React from 'react'

export const SwitchableProvider = ({ type, config, children }) => {
if (type === 'data') {
return (
<DataProvider
baseUrl={config.baseUrl}
apiVersion={config.apiVersion}
>
{children}
</DataProvider>
)
return <DataProvider baseUrl={config.baseUrl}>{children}</DataProvider>
} else if (type === 'runtime') {
return <Provider config={config}>{children}</Provider>
}
Expand Down
8 changes: 2 additions & 6 deletions examples/standalone-engine-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <KEY> [--apiVersion 41]
// Usage: node node-engine-test.js --url https://play.dhis2.org/2.37.8 --apiKey <KEY>

const { DataEngine, RestAPILink } = require('@dhis2/data-engine')

Expand All @@ -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 <baseUrl> --apiKey <key> [--apiVersion <num>]'
'Usage: node node-engine-test.js --url <baseUrl> --apiKey <key>'
)
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(
Expand All @@ -42,7 +39,6 @@ async function main() {
try {
const config = {
baseUrl,
apiVersion,
apiToken: apiKey,
}

Expand Down
1 change: 0 additions & 1 deletion services/config/src/ConfigContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ import { Config } from './types'

export const ConfigContext = React.createContext<Config>({
baseUrl: '..',
apiVersion: 32,
})
7 changes: 2 additions & 5 deletions services/config/src/__tests__/integration.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { Config } from '../types'

const mockConfig: Config = {
baseUrl: 'http://test.com',
apiVersion: 42,
serverVersion: {
full: '2.35-SNAPSHOT',
major: 2,
Expand All @@ -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(
<ConfigProvider config={mockConfig}>
<ConfigContext.Consumer>
Expand All @@ -34,7 +31,7 @@ describe('Testing custom config provider', () => {
</ConfigProvider>
)

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)
})
Expand Down
Loading
Loading