From 3901cbbc36ade9d7c93430ee573f5f4724f5bcd5 Mon Sep 17 00:00:00 2001 From: eirikhaugstulen Date: Wed, 7 Dec 2022 12:03:37 +0100 Subject: [PATCH 01/20] fix: added generic T = QueryResult --- services/data/src/react/hooks/useDataQuery.ts | 5 +++-- services/data/src/types.ts | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/services/data/src/react/hooks/useDataQuery.ts b/services/data/src/react/hooks/useDataQuery.ts index 3c82c4bdc..a776945f6 100644 --- a/services/data/src/react/hooks/useDataQuery.ts +++ b/services/data/src/react/hooks/useDataQuery.ts @@ -6,6 +6,7 @@ import type { QueryRenderInput, QueryRefetchFunction } from '../../types' import { mergeAndCompareVariables } from './mergeAndCompareVariables' import { useDataEngine } from './useDataEngine' import { useStaticInput } from './useStaticInput' +import {QueryResult} from "../../../build/types/engine"; const noop = () => { /** @@ -28,7 +29,7 @@ type QueryState = { refetchCallback?: (data: any) => void } -export const useDataQuery = ( +export const useDataQuery = ( query: Query, { onComplete: userOnSuccess, @@ -36,7 +37,7 @@ export const useDataQuery = ( variables: initialVariables = {}, lazy: initialLazy = false, }: QueryOptions = {} -): QueryRenderInput => { +): QueryRenderInput => { const [staticQuery] = useStaticInput(query, { warn: true, name: 'query', diff --git a/services/data/src/types.ts b/services/data/src/types.ts index 550518e3d..df2038aaf 100644 --- a/services/data/src/types.ts +++ b/services/data/src/types.ts @@ -40,15 +40,15 @@ export interface ExecuteHookResult { data?: ReturnType } -export interface QueryState { +export interface QueryState { called: boolean loading: boolean fetching: boolean error?: FetchError - data?: QueryResult + data?: T } -export interface QueryRenderInput extends QueryState { +export interface QueryRenderInput extends QueryState { engine: DataEngine refetch: QueryRefetchFunction } From afcfdbbccd265c00116eb8b8e3d2944b6398903e Mon Sep 17 00:00:00 2001 From: eirikhaugstulen Date: Wed, 7 Dec 2022 12:13:30 +0100 Subject: [PATCH 02/20] fix: changed to correct import --- services/data/src/react/hooks/useDataQuery.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/data/src/react/hooks/useDataQuery.ts b/services/data/src/react/hooks/useDataQuery.ts index a776945f6..7c1ae53a3 100644 --- a/services/data/src/react/hooks/useDataQuery.ts +++ b/services/data/src/react/hooks/useDataQuery.ts @@ -1,12 +1,11 @@ import { useState, useRef, useCallback, useDebugValue } from 'react' import { useQuery, setLogger } from 'react-query' -import type { Query, QueryOptions, QueryVariables } from '../../engine' +import type { Query, QueryOptions, QueryResult, QueryVariables } from '../../engine' import type { FetchError } from '../../engine/types/FetchError' import type { QueryRenderInput, QueryRefetchFunction } from '../../types' import { mergeAndCompareVariables } from './mergeAndCompareVariables' import { useDataEngine } from './useDataEngine' import { useStaticInput } from './useStaticInput' -import {QueryResult} from "../../../build/types/engine"; const noop = () => { /** From 8083a276e342836be6cbe61f43ffc7795e701381 Mon Sep 17 00:00:00 2001 From: eirikhaugstulen Date: Wed, 7 Dec 2022 12:29:13 +0100 Subject: [PATCH 03/20] fix: prettier --- services/data/src/react/hooks/useDataQuery.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/services/data/src/react/hooks/useDataQuery.ts b/services/data/src/react/hooks/useDataQuery.ts index 7c1ae53a3..cc1dadfd9 100644 --- a/services/data/src/react/hooks/useDataQuery.ts +++ b/services/data/src/react/hooks/useDataQuery.ts @@ -1,6 +1,11 @@ import { useState, useRef, useCallback, useDebugValue } from 'react' import { useQuery, setLogger } from 'react-query' -import type { Query, QueryOptions, QueryResult, QueryVariables } from '../../engine' +import type { + Query, + QueryOptions, + QueryResult, + QueryVariables, +} from '../../engine' import type { FetchError } from '../../engine/types/FetchError' import type { QueryRenderInput, QueryRefetchFunction } from '../../types' import { mergeAndCompareVariables } from './mergeAndCompareVariables' From 96f7b70b11fa4180fad5b7adf0f6f4163182499f Mon Sep 17 00:00:00 2001 From: eirikhaugstulen Date: Wed, 7 Dec 2022 13:38:29 +0100 Subject: [PATCH 04/20] fix: changed name of T --- services/data/src/react/hooks/useDataQuery.ts | 4 ++-- services/data/src/types.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/services/data/src/react/hooks/useDataQuery.ts b/services/data/src/react/hooks/useDataQuery.ts index cc1dadfd9..9b81a145d 100644 --- a/services/data/src/react/hooks/useDataQuery.ts +++ b/services/data/src/react/hooks/useDataQuery.ts @@ -33,7 +33,7 @@ type QueryState = { refetchCallback?: (data: any) => void } -export const useDataQuery = ( +export const useDataQuery = ( query: Query, { onComplete: userOnSuccess, @@ -41,7 +41,7 @@ export const useDataQuery = ( variables: initialVariables = {}, lazy: initialLazy = false, }: QueryOptions = {} -): QueryRenderInput => { +): QueryRenderInput => { const [staticQuery] = useStaticInput(query, { warn: true, name: 'query', diff --git a/services/data/src/types.ts b/services/data/src/types.ts index df2038aaf..fa90af0dd 100644 --- a/services/data/src/types.ts +++ b/services/data/src/types.ts @@ -40,15 +40,15 @@ export interface ExecuteHookResult { data?: ReturnType } -export interface QueryState { +export interface QueryState { called: boolean loading: boolean fetching: boolean error?: FetchError - data?: T + data?: TQueryResult } -export interface QueryRenderInput extends QueryState { +export interface QueryRenderInput extends QueryState { engine: DataEngine refetch: QueryRefetchFunction } From fbcbaa94c5f23e6c8cf73e377e0798c7359b86d1 Mon Sep 17 00:00:00 2001 From: eirikhaugstulen Date: Wed, 7 Dec 2022 13:56:39 +0100 Subject: [PATCH 05/20] fix: prettier --- services/data/src/types.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/data/src/types.ts b/services/data/src/types.ts index fa90af0dd..3baed1573 100644 --- a/services/data/src/types.ts +++ b/services/data/src/types.ts @@ -48,7 +48,8 @@ export interface QueryState { data?: TQueryResult } -export interface QueryRenderInput extends QueryState { +export interface QueryRenderInput + extends QueryState { engine: DataEngine refetch: QueryRefetchFunction } From f9594d916535677b309f1801c2583606cd4f07f8 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Thu, 20 Apr 2023 16:34:47 +0200 Subject: [PATCH 06/20] fix(types): improve types for useDataQuery.ts --- services/data/src/engine/types/Query.ts | 12 +++++++++++- services/data/src/react/hooks/useDataQuery.ts | 13 ++++++++----- services/data/src/types.ts | 2 ++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/services/data/src/engine/types/Query.ts b/services/data/src/engine/types/Query.ts index 5a79c1c67..b9580ae83 100644 --- a/services/data/src/engine/types/Query.ts +++ b/services/data/src/engine/types/Query.ts @@ -18,9 +18,19 @@ export interface ResolvedResourceQuery extends ResourceQuery { params?: QueryParameters } -export type Query = Record +export type Query = Record< + keyof TQueryResult, + ResourceQuery +> export type QueryResult = JsonMap +export type QueryResultData< + TQuery extends Query, + TQueryResultData extends QueryResult = QueryResult +> = { + [K in keyof TQuery]: TQueryResultData +} + export interface QueryOptions { variables?: QueryVariables onComplete?: (data: QueryResult) => void diff --git a/services/data/src/react/hooks/useDataQuery.ts b/services/data/src/react/hooks/useDataQuery.ts index 9b81a145d..65ce83b32 100644 --- a/services/data/src/react/hooks/useDataQuery.ts +++ b/services/data/src/react/hooks/useDataQuery.ts @@ -3,11 +3,11 @@ import { useQuery, setLogger } from 'react-query' import type { Query, QueryOptions, - QueryResult, QueryVariables, + QueryResultData } from '../../engine' import type { FetchError } from '../../engine/types/FetchError' -import type { QueryRenderInput, QueryRefetchFunction } from '../../types' +import type { DataQueryResult, QueryRefetchFunction } from '../../types' import { mergeAndCompareVariables } from './mergeAndCompareVariables' import { useDataEngine } from './useDataEngine' import { useStaticInput } from './useStaticInput' @@ -33,15 +33,18 @@ type QueryState = { refetchCallback?: (data: any) => void } -export const useDataQuery = ( - query: Query, +export const useDataQuery = < + TQueryResultData extends QueryResultData, + TQuery extends Query = Query +>( + query: TQuery, { onComplete: userOnSuccess, onError: userOnError, variables: initialVariables = {}, lazy: initialLazy = false, }: QueryOptions = {} -): QueryRenderInput => { +): DataQueryResult => { const [staticQuery] = useStaticInput(query, { warn: true, name: 'query', diff --git a/services/data/src/types.ts b/services/data/src/types.ts index 3baed1573..8efe70b7b 100644 --- a/services/data/src/types.ts +++ b/services/data/src/types.ts @@ -54,6 +54,8 @@ export interface QueryRenderInput refetch: QueryRefetchFunction } +export type DataQueryResult = QueryRenderInput + export interface MutationState { engine: DataEngine called: boolean From aebb4971fe40ba39688970df6fb4652c3e7ba210 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Thu, 20 Apr 2023 19:35:19 +0200 Subject: [PATCH 07/20] fix: remove unnecessary generic for QueryResultData --- services/data/src/engine/types/Query.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/services/data/src/engine/types/Query.ts b/services/data/src/engine/types/Query.ts index b9580ae83..c5f6e8fd6 100644 --- a/services/data/src/engine/types/Query.ts +++ b/services/data/src/engine/types/Query.ts @@ -25,10 +25,9 @@ export type Query = Record< export type QueryResult = JsonMap export type QueryResultData< - TQuery extends Query, - TQueryResultData extends QueryResult = QueryResult + TQuery extends Query > = { - [K in keyof TQuery]: TQueryResultData + [K in keyof TQuery]: QueryResult } export interface QueryOptions { From de2f3ed0bb66e44913c72f53e0a1ada2e3c56185 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Thu, 20 Apr 2023 19:38:27 +0200 Subject: [PATCH 08/20] style: fix lint errors --- services/data/src/engine/types/Query.ts | 4 +--- services/data/src/react/hooks/useDataQuery.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/services/data/src/engine/types/Query.ts b/services/data/src/engine/types/Query.ts index c5f6e8fd6..db1c5ffb6 100644 --- a/services/data/src/engine/types/Query.ts +++ b/services/data/src/engine/types/Query.ts @@ -24,9 +24,7 @@ export type Query = Record< > export type QueryResult = JsonMap -export type QueryResultData< - TQuery extends Query -> = { +export type QueryResultData = { [K in keyof TQuery]: QueryResult } diff --git a/services/data/src/react/hooks/useDataQuery.ts b/services/data/src/react/hooks/useDataQuery.ts index 65ce83b32..dd6a6a8d7 100644 --- a/services/data/src/react/hooks/useDataQuery.ts +++ b/services/data/src/react/hooks/useDataQuery.ts @@ -4,7 +4,7 @@ import type { Query, QueryOptions, QueryVariables, - QueryResultData + QueryResultData, } from '../../engine' import type { FetchError } from '../../engine/types/FetchError' import type { DataQueryResult, QueryRefetchFunction } from '../../types' From da0871cfa04d3a599252df87533a91684e26305e Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Mon, 24 Apr 2023 12:42:24 +0200 Subject: [PATCH 09/20] docs: add ts example for useDataQuery --- docs/hooks/useDataQuery.md | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/docs/hooks/useDataQuery.md b/docs/hooks/useDataQuery.md index dd6b4a161..1f89d49bc 100644 --- a/docs/hooks/useDataQuery.md +++ b/docs/hooks/useDataQuery.md @@ -73,6 +73,57 @@ export const IndicatorList = () => { } ``` +### Typescript +```tsx +import React from 'react' +import { useDataQuery } from '@dhis2/app-runtime' +import { CircularLoader } from '@dhis2/ui' + +const query = { + dataElements: { + resource: 'dataElements', + params: { + fields: 'id,displayName', + }, + pageSize: 10, + } +} + +type DataElementsResult = { + dataElements: { + pager: { + page: number, + total: number, + pageSize: number, + pageCount: number, + nextPage: string + + } + dataElements: { + id: string, + displayName: string + }[] + } +} +export const DataElementList = () => { + const { loading, error, data } = useDataQuery(query) + return ( +
+

Data elements (first 10)

+ {loading && } + {error && {`ERROR: ${error.message}`}} + {data && ( +
+                    {data.dataElements.dataElements
+                        .map((de) => de.displayName)
+                        .join('\n')}
+                
+ )} +
+ ) +} +``` + ### Dynamic Query This example is similar to the previous one but builds on top of it by showing how to fetch new pages of data using dynamic variables. A similar approach can be used implement dynamic filtering, ordering, etc. From 5494a1beb5befbd2de0c9ec6545786541aae7dee Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Mon, 24 Apr 2023 13:35:45 +0200 Subject: [PATCH 10/20] style: format prettier --- docs/hooks/useDataQuery.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/hooks/useDataQuery.md b/docs/hooks/useDataQuery.md index 1f89d49bc..fe3690510 100644 --- a/docs/hooks/useDataQuery.md +++ b/docs/hooks/useDataQuery.md @@ -74,6 +74,7 @@ export const IndicatorList = () => { ``` ### Typescript + ```tsx import React from 'react' import { useDataQuery } from '@dhis2/app-runtime' @@ -86,21 +87,20 @@ const query = { fields: 'id,displayName', }, pageSize: 10, - } + }, } type DataElementsResult = { dataElements: { pager: { - page: number, - total: number, - pageSize: number, - pageCount: number, + page: number + total: number + pageSize: number + pageCount: number nextPage: string - } dataElements: { - id: string, + id: string displayName: string }[] } @@ -161,7 +161,7 @@ export const IndicatorList = () => {

Indicators (paginated)

{loading && } - {error && {`ERROR: ${error.message}`}} + {error && {`ERROR: ${error.message}`}} {data && (
                     {data.indicators.indicators

From 96bc85302d0ce3bfcbcd96a838eedeb7389ac7b3 Mon Sep 17 00:00:00 2001
From: Birk Johansson 
Date: Mon, 1 May 2023 13:48:06 +0200
Subject: [PATCH 11/20] docs: fix typo in docs

---
 docs/hooks/useDataQuery.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/hooks/useDataQuery.md b/docs/hooks/useDataQuery.md
index fe3690510..522321e58 100644
--- a/docs/hooks/useDataQuery.md
+++ b/docs/hooks/useDataQuery.md
@@ -161,7 +161,7 @@ export const IndicatorList = () => {
         

Indicators (paginated)

{loading && } - {error && {`ERROR: ${error.message}`}} + {error && {`ERROR: ${error.message}`}} {data && (
                     {data.indicators.indicators

From be8a420ca50009a5c2d1daecd277b0ce2d149191 Mon Sep 17 00:00:00 2001
From: Birk Johansson 
Date: Tue, 2 May 2023 15:03:16 +0200
Subject: [PATCH 12/20] fix(data): add Pager type

---
 services/data/src/engine/types/Pager.ts | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
 create mode 100644 services/data/src/engine/types/Pager.ts

diff --git a/services/data/src/engine/types/Pager.ts b/services/data/src/engine/types/Pager.ts
new file mode 100644
index 000000000..e6fb45fa2
--- /dev/null
+++ b/services/data/src/engine/types/Pager.ts
@@ -0,0 +1,15 @@
+import { QueryResult } from './Query'
+
+export type Pager = {
+    page: number
+    total: number
+    pageSize: number
+    pageCount: number
+    nextPage: string
+}
+
+export type PaginatedData = T & { pager: Pager }
+
+export type PaginatedQueryResult = {
+    [K in keyof TQueryResult]: PaginatedData
+}

From 7ed0780dddd1945484dca0fbb42a06c4849b17a1 Mon Sep 17 00:00:00 2001
From: Birk Johansson 
Date: Tue, 2 May 2023 15:05:59 +0200
Subject: [PATCH 13/20] docs: add docs for paginatedquery

---
 docs/hooks/useDataQuery.md        | 14 ++++----------
 services/data/src/engine/index.ts |  1 +
 2 files changed, 5 insertions(+), 10 deletions(-)

diff --git a/docs/hooks/useDataQuery.md b/docs/hooks/useDataQuery.md
index 522321e58..9118c7ca0 100644
--- a/docs/hooks/useDataQuery.md
+++ b/docs/hooks/useDataQuery.md
@@ -77,7 +77,7 @@ export const IndicatorList = () => {
 
 ```tsx
 import React from 'react'
-import { useDataQuery } from '@dhis2/app-runtime'
+import { useDataQuery, PaginatedQueryResult } from '@dhis2/app-runtime'
 import { CircularLoader } from '@dhis2/ui'
 
 const query = {
@@ -90,21 +90,15 @@ const query = {
     },
 }
 
-type DataElementsResult = {
+type DataElementsResult = PaginatedQueryResult<{
     dataElements: {
-        pager: {
-            page: number
-            total: number
-            pageSize: number
-            pageCount: number
-            nextPage: string
-        }
         dataElements: {
             id: string
             displayName: string
         }[]
     }
-}
+}>
+
 export const DataElementList = () => {
     const { loading, error, data } = useDataQuery(query)
     return (
diff --git a/services/data/src/engine/index.ts b/services/data/src/engine/index.ts
index 790c2b74c..3afd5f547 100644
--- a/services/data/src/engine/index.ts
+++ b/services/data/src/engine/index.ts
@@ -7,3 +7,4 @@ export * from './types/Mutation'
 export * from './types/PossiblyDynamic'
 export * from './types/Query'
 export * from './types/QueryParameters'
+export * from './types/Pager'

From f8909816abb3e40939b85b02964591cf0a8b1cf9 Mon Sep 17 00:00:00 2001
From: Birk Johansson 
Date: Tue, 2 May 2023 17:25:52 +0200
Subject: [PATCH 14/20] fix: simplify query type

---
 services/data/src/engine/types/Query.ts       | 5 +----
 services/data/src/react/hooks/useDataQuery.ts | 4 ++--
 2 files changed, 3 insertions(+), 6 deletions(-)

diff --git a/services/data/src/engine/types/Query.ts b/services/data/src/engine/types/Query.ts
index db1c5ffb6..6a41f8f0b 100644
--- a/services/data/src/engine/types/Query.ts
+++ b/services/data/src/engine/types/Query.ts
@@ -18,10 +18,7 @@ export interface ResolvedResourceQuery extends ResourceQuery {
     params?: QueryParameters
 }
 
-export type Query = Record<
-    keyof TQueryResult,
-    ResourceQuery
->
+export type Query = Record
 export type QueryResult = JsonMap
 
 export type QueryResultData = {
diff --git a/services/data/src/react/hooks/useDataQuery.ts b/services/data/src/react/hooks/useDataQuery.ts
index dd6a6a8d7..864fdd50f 100644
--- a/services/data/src/react/hooks/useDataQuery.ts
+++ b/services/data/src/react/hooks/useDataQuery.ts
@@ -35,7 +35,7 @@ type QueryState = {
 
 export const useDataQuery = <
     TQueryResultData extends QueryResultData,
-    TQuery extends Query = Query
+    TQuery extends Query = Query
 >(
     query: TQuery,
     {
@@ -45,7 +45,7 @@ export const useDataQuery = <
         lazy: initialLazy = false,
     }: QueryOptions = {}
 ): DataQueryResult => {
-    const [staticQuery] = useStaticInput(query, {
+    const [staticQuery] = useStaticInput(query, {
         warn: true,
         name: 'query',
     })

From 392fd8412d3720b6ae51d96b715c58bf2abb0593 Mon Sep 17 00:00:00 2001
From: Birk Johansson 
Date: Tue, 2 May 2023 18:06:47 +0200
Subject: [PATCH 15/20] fix(engine): add improved types to engine

---
 services/data/src/engine/DataEngine.test.ts   | 30 +++++++++++++++----
 services/data/src/engine/DataEngine.ts        | 19 +++++++-----
 .../data/src/engine/types/ExecuteOptions.ts   |  6 ++--
 3 files changed, 39 insertions(+), 16 deletions(-)

diff --git a/services/data/src/engine/DataEngine.test.ts b/services/data/src/engine/DataEngine.test.ts
index 2c47d870c..864392e3c 100644
--- a/services/data/src/engine/DataEngine.test.ts
+++ b/services/data/src/engine/DataEngine.test.ts
@@ -15,6 +15,12 @@ const mockMutation: Mutation = {
     data: {},
 }
 
+type ResultData = {
+    type: string,
+    resource: string,
+    answer: number,
+}
+
 const mockLink: DataEngineLink = {
     executeResourceQuery: jest.fn(
         async (type: FetchType, query: ResolvedResourceQuery) => {
@@ -81,11 +87,19 @@ describe('DataEngine', () => {
 
     it('Should call multilple queries in parallel', async () => {
         const engine = new DataEngine(mockLink)
-        const result = await engine.query({
+
+        type Result = {
+            test: ResultData,
+            test2: ResultData,
+            test3: ResultData,
+        }
+        const q = {
             test: { resource: 'test' },
             test2: { resource: 'test2' },
             test3: { resource: 'test3' },
-        })
+        }
+        const result = await engine.query(q)
+  
         expect(mockLink.executeResourceQuery).toHaveBeenCalledTimes(3)
         expect(result).toMatchObject({
             test: {
@@ -104,14 +118,19 @@ describe('DataEngine', () => {
                 answer: 42,
             },
         })
-    })
+    )
 
     it('Should call onComplete callback only once for multiple-query method', async () => {
+        type Result = {
+            test: ResultData,
+            test2: ResultData,
+            test3: ResultData,
+        }
         const options = {
-            onComplete: jest.fn(),
+            onComplete: (data: Result) => console.log(data),
         }
         const engine = new DataEngine(mockLink)
-        await engine.query(
+        await engine.query(
             {
                 test: { resource: 'test' },
                 test2: { resource: 'test2' },
@@ -119,6 +138,7 @@ describe('DataEngine', () => {
             },
             options
         )
+   
         expect(mockLink.executeResourceQuery).toHaveBeenCalledTimes(3)
         expect(options.onComplete).toHaveBeenCalledTimes(1)
     })
diff --git a/services/data/src/engine/DataEngine.ts b/services/data/src/engine/DataEngine.ts
index 1c43520dc..0accdcef5 100644
--- a/services/data/src/engine/DataEngine.ts
+++ b/services/data/src/engine/DataEngine.ts
@@ -6,12 +6,12 @@ import {
 } from './helpers/validate'
 import { DataEngineLink } from './types/DataEngineLink'
 import { QueryExecuteOptions } from './types/ExecuteOptions'
-import { JsonMap, JsonValue } from './types/JsonValue'
+import { JsonValue } from './types/JsonValue'
 import { Mutation } from './types/Mutation'
-import { Query } from './types/Query'
+import { Query, QueryResultData } from './types/Query'
 
 const reduceResponses = (responses: JsonValue[], names: string[]) =>
-    responses.reduce((out, response, idx) => {
+    responses.reduce((out, response, idx) => {
         out[names[idx]] = response
         return out
     }, {})
@@ -22,15 +22,18 @@ export class DataEngine {
         this.link = link
     }
 
-    public query(
-        query: Query,
+    public query<
+        TQueryResultData extends QueryResultData,
+        TQuery extends Query = Query
+    >(
+        query: TQuery,
         {
             variables = {},
             signal,
             onComplete,
             onError,
-        }: QueryExecuteOptions = {}
-    ): Promise {
+        }: QueryExecuteOptions = {}
+    ): Promise {
         const names = Object.keys(query)
         const queries = names
             .map((name) => query[name])
@@ -46,7 +49,7 @@ export class DataEngine {
             })
         )
             .then((results) => {
-                const data = reduceResponses(results, names)
+                const data: TQueryResultData = reduceResponses(results, names)
                 onComplete && onComplete(data)
                 return data
             })
diff --git a/services/data/src/engine/types/ExecuteOptions.ts b/services/data/src/engine/types/ExecuteOptions.ts
index c7ddb37e7..4f355691b 100644
--- a/services/data/src/engine/types/ExecuteOptions.ts
+++ b/services/data/src/engine/types/ExecuteOptions.ts
@@ -1,5 +1,5 @@
 import { FetchError } from './FetchError'
-import { QueryVariables } from './Query'
+import { QueryVariables, QueryResultData, QueryResult } from './Query'
 
 export type FetchType =
     | 'create'
@@ -8,9 +8,9 @@ export type FetchType =
     | 'json-patch'
     | 'replace'
     | 'delete'
-export interface QueryExecuteOptions {
+export interface QueryExecuteOptions{
     variables?: QueryVariables
     signal?: AbortSignal
-    onComplete?: (data: any) => void
+    onComplete?: (data: TQueryResultData) => void
     onError?: (error: FetchError) => void
 }

From 00c3fb8e3085602ca4dae1ce64a2de5c8df99d8b Mon Sep 17 00:00:00 2001
From: Birk Johansson 
Date: Tue, 2 May 2023 19:01:51 +0200
Subject: [PATCH 16/20] fix: use any for QueryResult

---
 services/data/src/engine/types/Query.ts | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/services/data/src/engine/types/Query.ts b/services/data/src/engine/types/Query.ts
index 6a41f8f0b..1a13ec3f0 100644
--- a/services/data/src/engine/types/Query.ts
+++ b/services/data/src/engine/types/Query.ts
@@ -1,5 +1,4 @@
 import { FetchError } from './FetchError'
-import { JsonMap } from './JsonValue'
 import { PossiblyDynamic } from './PossiblyDynamic'
 import { QueryParameters } from './QueryParameters'
 
@@ -19,9 +18,9 @@ export interface ResolvedResourceQuery extends ResourceQuery {
 }
 
 export type Query = Record
-export type QueryResult = JsonMap
+export type QueryResult = any
 
-export type QueryResultData = {
+export type QueryResultData = {
     [K in keyof TQuery]: QueryResult
 }
 

From 3396b63ea317ef1461c3cd5071a1a2bf08eafec6 Mon Sep 17 00:00:00 2001
From: Birk Johansson 
Date: Tue, 2 May 2023 19:28:37 +0200
Subject: [PATCH 17/20] fix(test): fix failing test

---
 services/data/src/engine/DataEngine.test.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/services/data/src/engine/DataEngine.test.ts b/services/data/src/engine/DataEngine.test.ts
index 864392e3c..01bca340e 100644
--- a/services/data/src/engine/DataEngine.test.ts
+++ b/services/data/src/engine/DataEngine.test.ts
@@ -118,7 +118,7 @@ describe('DataEngine', () => {
                 answer: 42,
             },
         })
-    )
+    })
 
     it('Should call onComplete callback only once for multiple-query method', async () => {
         type Result = {

From dbd76599c777441b4101a23e990f0a7275effccc Mon Sep 17 00:00:00 2001
From: Birk Johansson 
Date: Tue, 2 May 2023 19:37:56 +0200
Subject: [PATCH 18/20] fix: test

---
 services/data/src/engine/DataEngine.test.ts   | 24 +++++++++----------
 .../data/src/engine/types/ExecuteOptions.ts   |  4 +++-
 2 files changed, 15 insertions(+), 13 deletions(-)

diff --git a/services/data/src/engine/DataEngine.test.ts b/services/data/src/engine/DataEngine.test.ts
index 01bca340e..93e51cbb3 100644
--- a/services/data/src/engine/DataEngine.test.ts
+++ b/services/data/src/engine/DataEngine.test.ts
@@ -16,9 +16,9 @@ const mockMutation: Mutation = {
 }
 
 type ResultData = {
-    type: string,
-    resource: string,
-    answer: number,
+    type: string
+    resource: string
+    answer: number
 }
 
 const mockLink: DataEngineLink = {
@@ -89,9 +89,9 @@ describe('DataEngine', () => {
         const engine = new DataEngine(mockLink)
 
         type Result = {
-            test: ResultData,
-            test2: ResultData,
-            test3: ResultData,
+            test: ResultData
+            test2: ResultData
+            test3: ResultData
         }
         const q = {
             test: { resource: 'test' },
@@ -99,7 +99,7 @@ describe('DataEngine', () => {
             test3: { resource: 'test3' },
         }
         const result = await engine.query(q)
-  
+
         expect(mockLink.executeResourceQuery).toHaveBeenCalledTimes(3)
         expect(result).toMatchObject({
             test: {
@@ -122,12 +122,12 @@ describe('DataEngine', () => {
 
     it('Should call onComplete callback only once for multiple-query method', async () => {
         type Result = {
-            test: ResultData,
-            test2: ResultData,
-            test3: ResultData,
+            test: ResultData
+            test2: ResultData
+            test3: ResultData
         }
         const options = {
-            onComplete: (data: Result) => console.log(data),
+            onComplete: jest.fn(),
         }
         const engine = new DataEngine(mockLink)
         await engine.query(
@@ -138,7 +138,7 @@ describe('DataEngine', () => {
             },
             options
         )
-   
+
         expect(mockLink.executeResourceQuery).toHaveBeenCalledTimes(3)
         expect(options.onComplete).toHaveBeenCalledTimes(1)
     })
diff --git a/services/data/src/engine/types/ExecuteOptions.ts b/services/data/src/engine/types/ExecuteOptions.ts
index 4f355691b..d82d0f14a 100644
--- a/services/data/src/engine/types/ExecuteOptions.ts
+++ b/services/data/src/engine/types/ExecuteOptions.ts
@@ -8,7 +8,9 @@ export type FetchType =
     | 'json-patch'
     | 'replace'
     | 'delete'
-export interface QueryExecuteOptions{
+export interface QueryExecuteOptions<
+    TQueryResultData extends QueryResultData = QueryResult
+> {
     variables?: QueryVariables
     signal?: AbortSignal
     onComplete?: (data: TQueryResultData) => void

From 947304c1da1a4f7c2ca14ab00bab75f413b725d6 Mon Sep 17 00:00:00 2001
From: Birk Johansson 
Date: Fri, 5 May 2023 18:45:48 +0200
Subject: [PATCH 19/20] docs(usedataquery): add typescript example for multiple
 paged queries

---
 docs/hooks/useDataQuery.md | 127 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 125 insertions(+), 2 deletions(-)

diff --git a/docs/hooks/useDataQuery.md b/docs/hooks/useDataQuery.md
index 9118c7ca0..cd8164b3b 100644
--- a/docs/hooks/useDataQuery.md
+++ b/docs/hooks/useDataQuery.md
@@ -73,7 +73,7 @@ export const IndicatorList = () => {
 }
 ```
 
-### Typescript
+#### Typescript
 
 ```tsx
 import React from 'react'
@@ -85,8 +85,8 @@ const query = {
         resource: 'dataElements',
         params: {
             fields: 'id,displayName',
+            pageSize: 10,
         },
-        pageSize: 10,
     },
 }
 
@@ -179,3 +179,126 @@ export const IndicatorList = () => {
     )
 }
 ```
+
+### Multiple resources in one Query
+
+```tsx
+import React from 'react'
+import { useDataQuery, PaginatedQueryResult, Pager, PaginatedData } from '@dhis2/app-runtime'
+import { CircularLoader } from '@dhis2/ui'
+
+
+const query = {
+    dataElements: {
+        resource: "dataElements",
+        params: ({ dataElementsPage }: { dataElementsPage?: number }) => ({
+            fields: "id,displayName",
+            pageSize: 10,
+            page: dataElementsPage,
+        }),
+    },
+    dataSets: {
+        resource: "dataSets",
+        params: {
+            fields: "id,displayName",
+            paging: false,
+        },
+    },
+    indicators: {
+        resource: "indicators",
+        params: ({ indicatorsPage }: { indicatorsPage?: number }) => ({
+            fields: "id,displayName",
+            pageSize: 10,
+            page: indicatorsPage,
+        }),
+    },
+} as const;
+
+type QueryResult = {
+    dataElements: PaginatedData<{
+        //can wrap your data-type in utility type PaginatedData
+        dataElements: {
+            id: string;
+            displayName: string;
+        }[];
+    }>;
+    dataSets: {
+        // no pagination
+        dataSets: {
+            id: string;
+            displayName: string;
+        }[];
+    };
+    indicators: {
+        pager: Pager; // or you can specifiy the pager manually
+        indicators: {
+            id: string;
+            displayName: string;
+        }[];
+    };
+};
+export const Component = () => {
+    const { error, data, refetch } = useDataQuery(query);
+
+    // keep previous data, so list does not disappear when refetching/fetching new page 
+    const prevData = React.useRef(data)
+    const stableData = data || prevData.current
+    React.useEffect(() => {
+        if(data) {
+            prevData.current = data
+        }
+    }, [data])
+
+
+    if (error) {
+        return {`ERROR: ${error.message}`};
+    }
+    if(!stableData) { // initial fetch
+        return 
+    }
+
+    const indicators = stableData?.indicators.indicators;
+    const indicatorsPager = stableData?.indicators.pager;
+    const dataElements = stableData?.dataElements.dataElements;
+    const dataElementPager = stableData?.dataElements.pager;
+    const dataSets = stableData?.dataSets.dataSets;
+    
+    return (
+        
+

Data elements (first 10)

+
{dataElements?.map((de) => de.displayName).join("\n")}
+ + {dataElements && dataElementPager && ( + refetch({ dataElementsPage: page })} + hidePageSizeSelect={true} + pageLength={dataElements.length} + /> + )} + +

Data Sets

+
{dataSets?.map((de) => de.displayName).join("\n")}
+ +

Indicators Sets

+
{indicators?.map((de) => de.displayName).join("\n")}
+ {indicators && indicatorsPager && ( + refetch({ indicatorsPage: page })} + hidePageSizeSelect={true} + pageLength={indicators.length} + /> + )} +
+ ); +}; +``` \ No newline at end of file From 4f43f9c5a8be532008b1ea394e7702414f0a5b41 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Sat, 6 May 2023 00:13:00 +0200 Subject: [PATCH 20/20] style: run prettier --- docs/hooks/useDataQuery.md | 86 ++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/docs/hooks/useDataQuery.md b/docs/hooks/useDataQuery.md index cd8164b3b..6d14bea94 100644 --- a/docs/hooks/useDataQuery.md +++ b/docs/hooks/useDataQuery.md @@ -184,89 +184,93 @@ export const IndicatorList = () => { ```tsx import React from 'react' -import { useDataQuery, PaginatedQueryResult, Pager, PaginatedData } from '@dhis2/app-runtime' +import { + useDataQuery, + PaginatedQueryResult, + Pager, + PaginatedData, +} from '@dhis2/app-runtime' import { CircularLoader } from '@dhis2/ui' - const query = { dataElements: { - resource: "dataElements", + resource: 'dataElements', params: ({ dataElementsPage }: { dataElementsPage?: number }) => ({ - fields: "id,displayName", + fields: 'id,displayName', pageSize: 10, page: dataElementsPage, }), }, dataSets: { - resource: "dataSets", + resource: 'dataSets', params: { - fields: "id,displayName", + fields: 'id,displayName', paging: false, }, }, indicators: { - resource: "indicators", + resource: 'indicators', params: ({ indicatorsPage }: { indicatorsPage?: number }) => ({ - fields: "id,displayName", + fields: 'id,displayName', pageSize: 10, page: indicatorsPage, }), }, -} as const; +} as const type QueryResult = { dataElements: PaginatedData<{ //can wrap your data-type in utility type PaginatedData dataElements: { - id: string; - displayName: string; - }[]; - }>; + id: string + displayName: string + }[] + }> dataSets: { // no pagination dataSets: { - id: string; - displayName: string; - }[]; - }; + id: string + displayName: string + }[] + } indicators: { - pager: Pager; // or you can specifiy the pager manually + pager: Pager // or you can specifiy the pager manually indicators: { - id: string; - displayName: string; - }[]; - }; -}; + id: string + displayName: string + }[] + } +} export const Component = () => { - const { error, data, refetch } = useDataQuery(query); + const { error, data, refetch } = useDataQuery(query) - // keep previous data, so list does not disappear when refetching/fetching new page + // keep previous data, so list does not disappear when refetching/fetching new page const prevData = React.useRef(data) const stableData = data || prevData.current React.useEffect(() => { - if(data) { + if (data) { prevData.current = data } }, [data]) - if (error) { - return {`ERROR: ${error.message}`}; + return {`ERROR: ${error.message}`} } - if(!stableData) { // initial fetch + if (!stableData) { + // initial fetch return } - const indicators = stableData?.indicators.indicators; - const indicatorsPager = stableData?.indicators.pager; - const dataElements = stableData?.dataElements.dataElements; - const dataElementPager = stableData?.dataElements.pager; - const dataSets = stableData?.dataSets.dataSets; - + const indicators = stableData?.indicators.indicators + const indicatorsPager = stableData?.indicators.pager + const dataElements = stableData?.dataElements.dataElements + const dataElementPager = stableData?.dataElements.pager + const dataSets = stableData?.dataSets.dataSets + return (

Data elements (first 10)

-
{dataElements?.map((de) => de.displayName).join("\n")}
+
{dataElements?.map((de) => de.displayName).join('\n')}
{dataElements && dataElementPager && ( { )}

Data Sets

-
{dataSets?.map((de) => de.displayName).join("\n")}
+
{dataSets?.map((de) => de.displayName).join('\n')}

Indicators Sets

-
{indicators?.map((de) => de.displayName).join("\n")}
+
{indicators?.map((de) => de.displayName).join('\n')}
{indicators && indicatorsPager && ( { /> )}
- ); -}; -``` \ No newline at end of file + ) +} +```