diff --git a/.github/workflows/dhis2-verify-commits.yml b/.github/workflows/dhis2-verify-commits.yml index ae7831b56..22cd8b339 100644 --- a/.github/workflows/dhis2-verify-commits.yml +++ b/.github/workflows/dhis2-verify-commits.yml @@ -1,32 +1,43 @@ -name: 'dhis2: verify (commits)' +name: "dhis2: verify (commits)" on: - pull_request: - types: ['opened', 'edited', 'reopened', 'synchronize'] + pull_request: + types: ["opened", "edited", "reopened", "synchronize"] jobs: - lint-pr-title: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: c-hive/gha-yarn-cache@v1 - - run: yarn install --frozen-lockfile - - id: commitlint - run: echo ::set-output name=config_path::$(node -e "process.stdout.write(require('@dhis2/cli-style').config.commitlint)") - - uses: JulienKode/pull-request-name-linter-action@v0.5.0 - with: - configuration-path: ${{ steps.commitlint.outputs.config_path }} + lint-pr-title: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: c-hive/gha-yarn-cache@v1 + - run: yarn install --frozen-lockfile + - id: commitlint + run: echo ::set-output name=config_path::$(node -e "process.stdout.write(require('@dhis2/cli-style').config.commitlint)") + - uses: JulienKode/pull-request-name-linter-action@v0.5.0 + with: + configuration-path: ${{ steps.commitlint.outputs.config_path }} - lint-commits: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - uses: c-hive/gha-yarn-cache@v1 - - run: yarn install --frozen-lockfile - - id: commitlint - run: echo ::set-output name=config_path::$(node -e "process.stdout.write(require('@dhis2/cli-style').config.commitlint)") - - uses: wagoid/commitlint-github-action@v4 - with: - configFile: ${{ steps.commitlint.outputs.config_path }} + lint-commits: + runs-on: ubuntu-latest + if: | + !contains(github.event.pull_request.head.message, '[skip release]') && + !contains(github.event.pull_request.head.message, '[skip ci]') + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + - uses: c-hive/gha-yarn-cache@v1 + - run: yarn install --frozen-lockfile + - run: git log -10 --oneline + - run: echo ${{ toJson(github.event) }} + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github.event) }} + run: echo "$GITHUB_CONTEXT" + - id: commitlint + run: echo ::set-output name=config_path::$(node -e "process.stdout.write(require('@dhis2/cli-style').config.commitlint)") + - uses: wagoid/commitlint-github-action@v5 + with: + configFile: ${{ steps.commitlint.outputs.config_path }} + # commitDepth: 10 diff --git a/.github/workflows/rebuild-docs.yml b/.github/workflows/rebuild-docs.yml new file mode 100644 index 000000000..a9fe4dcd8 --- /dev/null +++ b/.github/workflows/rebuild-docs.yml @@ -0,0 +1,19 @@ +name: 'dhis2: rebuild developer docs' + +on: + push: + branches: + - master + paths: + - 'docs/**' + - 'CHANGELOG.md' + +concurrency: + group: ${{ github.workflow}}-${{ github.ref }} + cancel-in-progress: true + +jobs: + rebuild-docs: + runs-on: ubuntu-latest + steps: + - run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_DEVELOPER_DOCS_TOKEN }} diff --git a/README.md b/README.md index 4fc98a0dd..4f640356a 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ A query REPL platform application is also available at `./examples/query-playgro Running `yarn build` at root will automatically update the example app's copy, and running `yarn start` will build the runtime and then start the example. -A [`DHIS2 App Platform`](https://platform.dhis2.nu) example is available at [./examples/query-playground](./examples/query-playground). This is a full, deployable DHIS2 application and a live standalone version is available at [runtime.dhis2.nu/playground](https://runtime.dhis2.nu/playground) +A [`DHIS2 App Platform`](https://platform.dhis2.nu) example is available at [./examples/query-playground](./examples/query-playground). This is a full, deployable DHIS2 application and a live standalone version is available at [every play instance](https://play.dhis2.org), such as the `dev` instance [play.dhis2.org/dev](https://play.dhis2.org/dev/api/apps/query-playground/index.html). ## Release diff --git a/docs/advanced/offline/CustomOnlineStatusMessage.md b/docs/advanced/offline/CustomOnlineStatusMessage.md new file mode 100644 index 000000000..c70b0b805 --- /dev/null +++ b/docs/advanced/offline/CustomOnlineStatusMessage.md @@ -0,0 +1,46 @@ +# Custom Connection Status Message + +By default, the header bar shows either an "Online" or "Offline" message based on whether the app can connect to the DHIS2 server. If it's useful for an app to share other relevant info in that badge, for example "3 changes saved locally", then it's possible to set a custom message there: + +![Custom online status message](custom-online-status-message.png) + +## Hooks + +```ts +import { + useSetOnlineStatusMessage, + useOnlineStatusMessageValue, + useOnlineStatusMessage, +} from '@dhis2/app-runtime' +``` + +### `useSetOnlineStatusMessage` + +It's most likely that you'll only use `useSetOnlineStatusMessage()`, a hook that returns a `setState` function. It can set the value in the Header Bar without causing a rerender for itself. + +`setOnlineStatusMessage()` accepts a `ReactNode`-type object, so icons or other components can be added to the badge, but don't go crazy. A simple string is also a valid `ReactNode`: + +```ts +const setOnlineStatusMessage: (message: ReactNode) => void = + useSetOnlineStatusMessage() +``` + +### `useOnlineStatusMessageValue` + +This hook returns just the value: + +```ts +const onlineStatusMessage: ReactNode = useOnlineStatusMessageValue() +``` + +### `useOnlineStatusMessage` + +This hook returns both the value and the `set` function: + +```ts +const { onlineMessage, setOnlineStatusMessage } = useOnlineStatusMessage() +``` + +## Example + +You can see an example in use in the [Aggregate Data Entry app](https://github.com/dhis2/aggregate-data-entry-app/blob/dadd61392ea010a8017a19a25eaf76f885d9eea7/src/data-workspace/use-handle-headerbar-status.js) diff --git a/docs/advanced/offline/README.md b/docs/advanced/offline/README.md index 15afd54c4..847bffd80 100644 --- a/docs/advanced/offline/README.md +++ b/docs/advanced/offline/README.md @@ -1,10 +1,12 @@ # Offline tools -!> **WARNING** These features are considered **experimental** and are **subject to breaking changes outside of the normal release cycle.** +:::warning Experimental +These features are considered **experimental** and are **subject to breaking changes outside of the normal release cycle.** +::: -The app platform provides some support for PWA features, including a `manifest.json` file for installability and service worker which can provide offline caching. In addition to those features, the app runtime provides support for ["cacheable sections"](advanced/offline/CacheableSections), which are sections of an app that can be individually cached on-demand. The [`useCacheableSection` hook](advanced/offline/CacheableSections#usecacheablesection-api) and the [`CacheableSection` component](advanced/offline/CacheableSections#cacheablesection-api) provide the controls for the section and the wrapper for the section, respectively. The [`useCachedSections` hook](advanced/offline/CacheableSections#usecachedsections-api) returns a list of sections that are stored in the cache and a function that can delete them. +The app platform provides some support for PWA features, including a `manifest.json` file for installability and service worker which can provide offline caching. In addition to those features, the app runtime provides support for ["cacheable sections"](./CacheableSections.md), which are sections of an app that can be individually cached on-demand. The [`useCacheableSection` hook](./CacheableSections.md#usecacheablesection-api) and the [`CacheableSection` component](./CacheableSections.md#cacheablesection-api) provide the controls for the section and the wrapper for the section, respectively. The [`useCachedSections` hook](./CacheableSections.md#usecachedsections-api) returns a list of sections that are stored in the cache and a function that can delete them. -An important tool for offline-capable apps is the [`useDhis2ConnectionStatus` hook](advanced/offline/useDhis2ConnectionStatus.md), which can be used to determine whether or not the app can connect to the DHIS2 server. There is also a [`useOnlineStatus` hook](advanced/offline/useOnlineStatus.md) which returns whether or not the client is connected to the internet, but `useDhis2ConnectionStatus` is probably the one you want to use. On instances where DHIS2 is deployed locally in an environment without internet, `useOnlineStatus` can cause problems, because it will always return `false` even though the app can communicate with the DHIS2 server and therefore function just fine. `useDhis2ConnectionStatus` was created to address this problem. +An important tool for offline-capable apps is the [`useDhis2ConnectionStatus` hook](./useDhis2ConnectionStatus.md), which can be used to determine whether or not the app can connect to the DHIS2 server. There is also a [`useOnlineStatus` hook](./useOnlineStatus.md) which returns whether or not the client is connected to the internet, but `useDhis2ConnectionStatus` is probably the one you want to use. On instances where DHIS2 is deployed locally in an environment without internet, `useOnlineStatus` can cause problems, because it will always return `false` even though the app can communicate with the DHIS2 server and therefore function just fine. `useDhis2ConnectionStatus` was created to address this problem. ## Examples diff --git a/docs/advanced/offline/custom-online-status-message.png b/docs/advanced/offline/custom-online-status-message.png new file mode 100644 index 000000000..ac8841614 Binary files /dev/null and b/docs/advanced/offline/custom-online-status-message.png differ diff --git a/docs/advanced/offline/useDhis2ConnectionStatus.md b/docs/advanced/offline/useDhis2ConnectionStatus.md index 6d7ede27f..e9f4ae0bd 100644 --- a/docs/advanced/offline/useDhis2ConnectionStatus.md +++ b/docs/advanced/offline/useDhis2ConnectionStatus.md @@ -6,6 +6,8 @@ This hook is used to detect whether or not the app can connect to the DHIS2 serv It's designed to detect server connection because checking just the internet connection can lead to problems on DHIS2 instances that are implemented offline, where features or actions might be blocked because the device is offline, even though the app can connect to the DHIS2 server just fine. In these cases, what matters is whether or not the app can connect to the DHIS2 server. +This what the DHIS2 Header Bar uses to show an "Online" or "Offline" badge. + ```ts import { useDhis2ConnectionStatus } from '@dhis2/app-runtime' diff --git a/docs/advanced/offline/useOnlineStatus.md b/docs/advanced/offline/useOnlineStatus.md index 366a797fc..5ee5a2606 100644 --- a/docs/advanced/offline/useOnlineStatus.md +++ b/docs/advanced/offline/useOnlineStatus.md @@ -1,6 +1,8 @@ # useOnlineStatus -!> This hook only detects whether or not you're connected to the internet, which could be problematic for DHIS2 instances that are hosted locally or offline, where what really matters is whether or not you can communicate with the DHIS2 server. The [`useDhis2ConnectionStatus` hook](advanced/offline/useDhis2ConnectionStatus) is usually better for that reason, and is therefore recommended. +:::info Internet Only +This hook only detects whether or not you're connected to the internet, which could be problematic for DHIS2 instances that are hosted locally or offline, where what really matters is whether or not you can communicate with the DHIS2 server. The [`useDhis2ConnectionStatus` hook](./useDhis2ConnectionStatus.md) is usually better for that reason, and is therefore recommended. +::: The `useOnlineStatus` returns whether the client is online or offline. It debounces the returned values by default to prevent rapid changes of any UI elements that depend on the online status. diff --git a/docs/advanced/services.md b/docs/advanced/services.md index 7462f7832..7e9646591 100644 --- a/docs/advanced/services.md +++ b/docs/advanced/services.md @@ -1,6 +1,8 @@ # Services -!> **NOTE**
Services are purely a concept **internal** to `@dhis2/app-runtime` - you don't need to care about them to use it. +:::caution Note +Services are purely a concept **internal** to `@dhis2/app-runtime` - you don't need to care about them to use it. +::: Internally, `@dhis2/app-runtime` is composed of several functionally-isolated **services**. Each of these services is a private (unpublished) npm package which is bundled into the final `@dhis2/app-runtime` library at build-time. Each services exposes a relevant Context provider as well as various Hook and Component interfaces which are re-exported by the `@dhis2/app-runtime` public package. There are currently only 2 services, but more will be added soon. diff --git a/docs/components/DataMutation.md b/docs/components/DataMutation.md index fa8dc8f2c..914f02c0c 100644 --- a/docs/components/DataMutation.md +++ b/docs/components/DataMutation.md @@ -1,6 +1,6 @@ # DataMutation Component -A thin wrapper around the [useDataMutation](hooks/useDataMutation.md) hook +A thin wrapper around the [useDataMutation](../hooks/useDataMutation.md) hook ## Basic Usage @@ -19,13 +19,13 @@ return ( ## Input Props -| Name | Type | Required | Description | -| :------------: | :-----------------------------: | :----------: | ----------------------------------------------------------------------------------------------------------------------------- | -| **mutation** | [_Mutation_](types/Mutation.md) | **required** | The Mutation definition describing the requested operation | -| **variables** | _Object_ | | Variables to be passed to the dynamic portions of the mutation | -| **onComplete** | _Function_ | | Callback function to be called on successfull completion of the mutation. Called with the response data as the only argument. | -| **onError** | _Function_ | | Callback function to be called on failure of the mutation. Called with the error instance as the only argument. | -| **lazy** | _boolean_ | | If false, run the mutation immediately after the component mounts.
_**Default**: `true`_ | +| Name | Type | Required | Description | +| :------------: | :--------------------------------: | :----------: | ----------------------------------------------------------------------------------------------------------------------------- | +| **mutation** | [_Mutation_](../types/Mutation.md) | **required** | The Mutation definition describing the requested operation | +| **variables** | _Object_ | | Variables to be passed to the dynamic portions of the mutation | +| **onComplete** | _Function_ | | Callback function to be called on successfull completion of the mutation. Called with the response data as the only argument. | +| **onError** | _Function_ | | Callback function to be called on failure of the mutation. Called with the error instance as the only argument. | +| **lazy** | _boolean_ | | If false, run the mutation immediately after the component mounts.
_**Default**: `true`_ | ## Render Function Props @@ -34,13 +34,13 @@ The render function is called whenever the state of the mutation changes. It is 1. `mutate` - a function which can be called to trigger the mutation (for instance in an onClick callback) 2. `state` - an object containing the following properties: -| Name | Type | Description | -| :---------: | :-------------------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **called** | _boolean_ | **true** if the mutation has been triggered through a call to the `mutate` function or on component mount with `lazy: false` | -| **loading** | _boolean_ | **true** if the data is not yet available and no error has yet been encountered | -| **error** | _Error_
or
_undefined_ | **undefined** if no error has occurred, otherwise the Error which was thrown | -| **data** | _MutationResult_
or
_undefined_ | **undefined** if the data is loading or an error has occurred, otherwise a map from the name of each **QueryDefinition** defined in the **Query** to the resulting data for that query | -| **engine** | [_Data Engine_](advanced/DataEngine) | A reference to the DataEngine instance | +| Name | Type | Description | +| :---------: | :----------------------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **called** | _boolean_ | **true** if the mutation has been triggered through a call to the `mutate` function or on component mount with `lazy: false` | +| **loading** | _boolean_ | **true** if the data is not yet available and no error has yet been encountered | +| **error** | _Error_
or
_undefined_ | **undefined** if no error has occurred, otherwise the Error which was thrown | +| **data** | _MutationResult_
or
_undefined_ | **undefined** if the data is loading or an error has occurred, otherwise a map from the name of each **QueryDefinition** defined in the **Query** to the resulting data for that query | +| **engine** | [_Data Engine_](../advanced/DataEngine.md) | A reference to the DataEngine instance | ## Example diff --git a/docs/components/DataQuery.md b/docs/components/DataQuery.md index 5f1b49a14..a5a741d25 100644 --- a/docs/components/DataQuery.md +++ b/docs/components/DataQuery.md @@ -1,6 +1,6 @@ # DataQuery Component -A thin wrapper around the [useDataQuery](hooks/useDataQuery.md) hook +A thin wrapper around the [useDataQuery](../hooks/useDataQuery.md) hook ## Basic Usage @@ -19,24 +19,24 @@ return ( ## Input Props -| Name | Type | Required | Description | -| :------------: | :-----------------------: | :----------: | -------------------------------------------------------------------------------------------------------------------------- | -| **query** | [_Query_](types/Query.md) | **required** | The Query definition describing the requested data | -| **variables** | _Object_ | | Variables to be passed to the dynamic portions of the query | -| **onComplete** | _Function_ | | Callback function to be called on successfull completion of the query. Called with the response data as the only argument. | -| **onError** | _Function_ | | Callback function to be called on failure of the query. Called with the error instance as the only argument. | -| **lazy** | _boolean_ | | If true, wait until `refetch` is called before fetching data.
_**Default**: `false`_ | +| Name | Type | Required | Description | +| :------------: | :--------------------------: | :----------: | -------------------------------------------------------------------------------------------------------------------------- | +| **query** | [_Query_](../types/Query.md) | **required** | The Query definition describing the requested data | +| **variables** | _Object_ | | Variables to be passed to the dynamic portions of the query | +| **onComplete** | _Function_ | | Callback function to be called on successfull completion of the query. Called with the response data as the only argument. | +| **onError** | _Function_ | | Callback function to be called on failure of the query. Called with the error instance as the only argument. | +| **lazy** | _boolean_ | | If true, wait until `refetch` is called before fetching data.
_**Default**: `false`_ | ## Render Function Props -| Name | Type | Description | -| :---------: | :----------------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **called** | _boolean_ | **true** if the data request has been initiated with either `lazy: false` or `refetch()` at least once | -| **loading** | _boolean_ | **true** if the data is not yet available and no error has yet been encountered | -| **error** | _Error_
or
_undefined_ | **undefined** if no error has occurred, otherwise the Error which was thrown | -| **data** | _QueryResult_
or
_undefined_ | **undefined** if the data is loading or an error has occurred, otherwise a map from the name of each **QueryDefinition** defined in the **Query** to the resulting data for that query | -| **refetch** | _Function_ | This function can be called to refetch the data. Any in-flight HTTP requests will automatically be aborted. | -| **engine** | [_Data Engine_](advanced/DataEngine) | A reference to the DataEngine instance | +| Name | Type | Description | +| :---------: | :----------------------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **called** | _boolean_ | **true** if the data request has been initiated with either `lazy: false` or `refetch()` at least once | +| **loading** | _boolean_ | **true** if the data is not yet available and no error has yet been encountered | +| **error** | _Error_
or
_undefined_ | **undefined** if no error has occurred, otherwise the Error which was thrown | +| **data** | _QueryResult_
or
_undefined_ | **undefined** if the data is loading or an error has occurred, otherwise a map from the name of each **QueryDefinition** defined in the **Query** to the resulting data for that query | +| **refetch** | _Function_ | This function can be called to refetch the data. Any in-flight HTTP requests will automatically be aborted. | +| **engine** | [_Data Engine_](../advanced/DataEngine.md) | A reference to the DataEngine instance | ## Example diff --git a/docs/components/README.md b/docs/components/README.md index 3deab07b0..f790d06a4 100644 --- a/docs/components/README.md +++ b/docs/components/README.md @@ -1,6 +1,6 @@ # React Components -As an alternative to [React Hooks](hooks/), the DHIS2 Application Runtime support render-prop React components. The following Components are supported: +As an alternative to [React Hooks](../hooks/README.md), the DHIS2 Application Runtime support render-prop React components. The following Components are supported: -- [**DataQuery**](components/DataQuery) - a data fetching Component wrapper around the [useDataQuery](hooks/useDataQuery) hook. -- [**DataMutation**](components/DataMutation) - a Component wrapper around the [useDataMutation](hooks/useDataMutation) hook. +- [**DataQuery**](./DataQuery.md) - a data fetching Component wrapper around the [useDataQuery](../hooks/useDataQuery.md) hook. +- [**DataMutation**](./DataMutation.md) - a Component wrapper around the [useDataMutation](../hooks/useDataMutation.md) hook. diff --git a/docs/hooks/README.md b/docs/hooks/README.md index d27c5d037..bde9780a2 100644 --- a/docs/hooks/README.md +++ b/docs/hooks/README.md @@ -2,11 +2,11 @@ The DHIS2 Application Runtime supports [React Hooks](https://reactjs.org/docs/hooks-intro.html) (introduced in React 16.8). The following hooks are supported: -- [**useConfig**](hooks/useConfig) - Access the raw application configuration object -- [**useDataQuery**](hooks/useDataQuery) - Fetch data from the DHIS2 Core server API without worrying about HTTP requests! -- [**useDataMutation**](hooks/useDataMutation) - Mutate resources in the DHIS2 Core server API without worrying about HTTP requests! -- [**useDataEngine**](hooks/useDataEngine) _(Advanced)_ - Access the underlying [Data Engine](advanced/DataEngine) instance -- [**useAlert**](hooks/useAlert) - Add an alert to the central alerts-context -- [**useAlerts**](hooks/useAlert) - Read the alerts from the alerts-context +- [**useConfig**](./useConfig.md) - Access the raw application configuration object +- [**useDataQuery**](./useDataQuery.md) - Fetch data from the DHIS2 Core server API without worrying about HTTP requests! +- [**useDataMutation**](./useDataMutation.md) - Mutate resources in the DHIS2 Core server API without worrying about HTTP requests! +- [**useDataEngine**](./useDataEngine.md) _(Advanced)_ - Access the underlying [Data Engine](../advanced/DataEngine.md) instance +- [**useAlert**](./useAlert.md) - Add an alert to the central alerts-context +- [**useAlerts**](./useAlerts.md) - Read the alerts from the alerts-context -While these Hooks are incredibly powerful and usually preferable, some [Components](components/) are also provided which conveniently wrap their corresponding Hooks. +While these Hooks are incredibly powerful and usually preferable, some [Components](../components/README.md) are also provided which conveniently wrap their corresponding Hooks. diff --git a/docs/hooks/useConfig.md b/docs/hooks/useConfig.md index 93e39d61b..0c8aefc62 100644 --- a/docs/hooks/useConfig.md +++ b/docs/hooks/useConfig.md @@ -1,6 +1,6 @@ # useConfig Hook -Access the application configuration passed to the top-level [Provider](provider.md) +Access the application configuration passed to the top-level [Provider](../provider.md) ## Basic Usage: @@ -17,7 +17,7 @@ _None_ ## Output -This hook returns an object of type [Config](types/Config.md). +This hook returns an object of type [Config](../types/Config.md). ## Example diff --git a/docs/hooks/useDataEngine.md b/docs/hooks/useDataEngine.md index bbd2c1fc5..d22582392 100644 --- a/docs/hooks/useDataEngine.md +++ b/docs/hooks/useDataEngine.md @@ -1,6 +1,6 @@ # useDataEngine hook -Get access to the [Data Engine](advanced/DataEngine) instance for non-React contexts. +Get access to the [Data Engine](../advanced/DataEngine.md) instance for non-React contexts. ## Basic Usage: @@ -19,4 +19,4 @@ engine.mutate(mutation, { }) ``` -> See [Using with Redux](advanced/redux) for an advanced example +> See [Using with Redux](../advanced/redux.md) for an advanced example diff --git a/docs/hooks/useDataMutation.md b/docs/hooks/useDataMutation.md index 69b322626..0cc049419 100644 --- a/docs/hooks/useDataMutation.md +++ b/docs/hooks/useDataMutation.md @@ -16,25 +16,25 @@ const [mutate, { called, loading, error, data }] = useDataMutation( ## Input -| Name | Type | Required | Description | -| :--------------------: | :-----------------------------: | :----------: | ----------------------------------------------------------------------------------------------------------------------------- | -| **mutation** | [_Mutation_](types/Mutation.md) | **required** | The Mutation definition describing the requested action | -| **options** | _Object_ | | An optional set of query options | -| **options.variables** | _Object_ | | Variables to be passed to the dynamic portions of the mutation | -| **options.onComplete** | _Function_ | | Callback function to be called on successfull completion of the mutation. Called with the response data as the only argument. | -| **options.onError** | _Function_ | | Callback function to be called on failure of the mutation. Called with the error instance as the only argument. | -| **options.lazy** | _boolean_ | | If false, run the mutation immediately after the component mounts.
_**Default**: `true`_ | +| Name | Type | Required | Description | +| :--------------------: | :--------------------------------: | :----------: | ----------------------------------------------------------------------------------------------------------------------------- | +| **mutation** | [_Mutation_](../types/Mutation.md) | **required** | The Mutation definition describing the requested action | +| **options** | _Object_ | | An optional set of query options | +| **options.variables** | _Object_ | | Variables to be passed to the dynamic portions of the mutation | +| **options.onComplete** | _Function_ | | Callback function to be called on successfull completion of the mutation. Called with the response data as the only argument. | +| **options.onError** | _Function_ | | Callback function to be called on failure of the mutation. Called with the error instance as the only argument. | +| **options.lazy** | _boolean_ | | If false, run the mutation immediately after the component mounts.
_**Default**: `true`_ | ## Output -| Name | Type | Description | -| :---------: | :----------------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **mutate** | _Function_ | This function can be called to execute the mutation. Any in-flight HTTP requests will automatically be aborted. | -| **called** | _boolean_ | **true** if the mutation has been triggered by a call to the `mutate` function or on component mount with `lazy: false` | -| **loading** | _boolean_ | **true** if the data is not yet available and no error has yet been encountered | -| **error** | _Error_
or
_undefined_ | **undefined** if no error has occurred, otherwise the Error which was thrown | -| **data** | _QueryResult_
or
_undefined_ | **undefined** if the data is loading or an error has occurred, otherwise a map from the name of each **QueryDefinition** defined in the **Query** to the resulting data for that query | -| **engine** | [_Data Engine_](advanced/DataEngine) | A reference to the DataEngine instance | +| Name | Type | Description | +| :---------: | :----------------------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **mutate** | _Function_ | This function can be called to execute the mutation. Any in-flight HTTP requests will automatically be aborted. | +| **called** | _boolean_ | **true** if the mutation has been triggered by a call to the `mutate` function or on component mount with `lazy: false` | +| **loading** | _boolean_ | **true** if the data is not yet available and no error has yet been encountered | +| **error** | _Error_
or
_undefined_ | **undefined** if no error has occurred, otherwise the Error which was thrown | +| **data** | _QueryResult_
or
_undefined_ | **undefined** if the data is loading or an error has occurred, otherwise a map from the name of each **QueryDefinition** defined in the **Query** to the resulting data for that query | +| **engine** | [_Data Engine_](../advanced/DataEngine.md) | A reference to the DataEngine instance | ## Example diff --git a/docs/hooks/useDataQuery.md b/docs/hooks/useDataQuery.md index dd6b4a161..b5478d546 100644 --- a/docs/hooks/useDataQuery.md +++ b/docs/hooks/useDataQuery.md @@ -13,25 +13,25 @@ const { loading, error, data, refetch } = useDataQuery(query, options) ## Input -| Name | Type | Required | Description | -| :--------------------: | :--------------------: | :----------: | --------------------------------------------------------------------------------------------------------------------------- | -| **query** | [_Query_](types/Query) | **required** | The Query definition describing the requested data | -| **options** | _Object_ | | An optional set of query options | -| **options.variables** | _Object_ | | Variables to be passed to the dynamic portions of the query (can also be passed via the refetch function, see Output below) | -| **options.onComplete** | _Function_ | | Callback function to be called on successfull completion of the query. Called with the response data as the only argument. | -| **options.onError** | _Function_ | | Callback function to be called on failure of the query. Called with the error instance as the only argument. | -| **options.lazy** | _boolean_ | | If true, wait until `refetch` is called before fetching data.
_**Default**: `false`_ | +| Name | Type | Required | Description | +| :--------------------: | :--------------------------: | :----------: | --------------------------------------------------------------------------------------------------------------------------- | +| **query** | [_Query_](../types/Query.md) | **required** | The Query definition describing the requested data | +| **options** | _Object_ | | An optional set of query options | +| **options.variables** | _Object_ | | Variables to be passed to the dynamic portions of the query (can also be passed via the refetch function, see Output below) | +| **options.onComplete** | _Function_ | | Callback function to be called on successfull completion of the query. Called with the response data as the only argument. | +| **options.onError** | _Function_ | | Callback function to be called on failure of the query. Called with the error instance as the only argument. | +| **options.lazy** | _boolean_ | | If true, wait until `refetch` is called before fetching data.
_**Default**: `false`_ | ## Output -| Name | Type | Description | -| :---------: | :----------------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **called** | _boolean_ | **true** if the data request has been initiated with either `lazy: false` or `refetch()` at least once | -| **loading** | _boolean_ | **true** if the data is not yet available and no error has yet been encountered | -| **error** | _Error_
or
_undefined_ | **undefined** if no error has occurred, otherwise the Error which was thrown | -| **data** | _QueryResult_
or
_undefined_ | **undefined** if the data is loading or an error has occurred, otherwise a map from the name of each **QueryDefinition** defined in the **Query** to the resulting data for that query | -| **refetch** | _Function_ | This function can be called to refetch the data and can accept variables (see Examples below). Any in-flight HTTP requests will automatically be aborted. | -| **engine** | [_Data Engine_](advanced/DataEngine) | A reference to the DataEngine instance | +| Name | Type | Description | +| :---------: | :----------------------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **called** | _boolean_ | **true** if the data request has been initiated with either `lazy: false` or `refetch()` at least once | +| **loading** | _boolean_ | **true** if the data is not yet available and no error has yet been encountered | +| **error** | _Error_
or
_undefined_ | **undefined** if no error has occurred, otherwise the Error which was thrown | +| **data** | _QueryResult_
or
_undefined_ | **undefined** if the data is loading or an error has occurred, otherwise a map from the name of each **QueryDefinition** defined in the **Query** to the resulting data for that query | +| **refetch** | _Function_ | This function can be called to refetch the data and can accept variables (see Examples below). Any in-flight HTTP requests will automatically be aborted. | +| **engine** | [_Data Engine_](../advanced/DataEngine.md) | A reference to the DataEngine instance | ## Examples diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 000000000..4fb409912 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,18 @@ + + + + + Redirecting to Developer Portal + + +

Redirecting to Developer Portal...

+ + + + diff --git a/docs/provider.md b/docs/provider.md index 706abebc9..516167da2 100644 --- a/docs/provider.md +++ b/docs/provider.md @@ -2,9 +2,9 @@ ## Input Props -| Name | Type | Description | -| :--------: | :-------------------------: | ----------------------------- | -| **config** | [_Config_](types/Config.md) | The application configuration | +| Name | Type | Description | +| :--------: | :---------------------------: | ----------------------------- | +| **config** | [_Config_](./types/Config.md) | The application configuration | ## Example diff --git a/docs/types/Config.md b/docs/types/Config.md index 8d0c221ac..85c0b42ec 100644 --- a/docs/types/Config.md +++ b/docs/types/Config.md @@ -1,8 +1,8 @@ # Config Type The Application Config type is a required input to the top-level -[Provider](provider), and can be accessed from within an application -with the [useConfig](hooks/useConfig) hook. +[Provider](../provider.md), and can be accessed from within an application +with the [useConfig](../hooks/useConfig.md) hook. | Property | Type | Description | | :------------: | :------: | --------------------------------------------------------------------------------------------------------------- | diff --git a/docs/types/Mutation.md b/docs/types/Mutation.md index 4067e997d..78b057ad0 100644 --- a/docs/types/Mutation.md +++ b/docs/types/Mutation.md @@ -1,6 +1,6 @@ # Mutation -The Mutation type is used to define declarative data mutations. It is required input for the [useDataMutation](hooks/useDataMutation.md) hook, [DataMutation](components/DataMutation.md) component, and [DataEngine.mutate](advanced/DataEngine) method. +The Mutation type is used to define declarative data mutations. It is required input for the [useDataMutation](../hooks/useDataMutation.md) hook, [DataMutation](../components/DataMutation.md) component, and [DataEngine.mutate](../advanced/DataEngine.md) method. A mutation defines a destructive operation performed on a collection or instance of a particular resource. diff --git a/docs/types/Query.md b/docs/types/Query.md index 4698021db..4952c33b2 100644 --- a/docs/types/Query.md +++ b/docs/types/Query.md @@ -1,6 +1,6 @@ # Data Queries -The Query type is used to define declarative data requests. It is required input for the [useDataQuery](hooks/useDataQuery.md) hook and [DataQuery](components/DataQuery.md) component. +The Query type is used to define declarative data requests. It is required input for the [useDataQuery](../hooks/useDataQuery.md) hook and [DataQuery](../components/DataQuery.md) component. ## Query diff --git a/package.json b/package.json index cb6be44ab..bacd0f965 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "test": "yarn test:services && yarn test:runtime", "format": "d2-style apply js --all --no-stage && d2-style apply text --all --no-stage", "start": "yarn build && cd examples/query-playground && yarn start", - "docs:build": "d2-utils-docsite build ./docs -o ./dist && yarn build:playground", + "docs:build": "mkdir -p dist && cp docs/index.html dist/ && yarn build:playground", "docs:serve": "d2-utils-docsite serve ./docs -o ./dist", "lint": "d2-style check js && d2-style check text" }, diff --git a/runtime/src/index.ts b/runtime/src/index.ts index 5bf44ba37..1ae215aa2 100644 --- a/runtime/src/index.ts +++ b/runtime/src/index.ts @@ -17,6 +17,8 @@ export { useOnlineStatus, useDhis2ConnectionStatus, useOnlineStatusMessage, + useSetOnlineStatusMessage, + useOnlineStatusMessageValue, useCacheableSection, CacheableSection, useCachedSections, diff --git a/services/offline/src/index.ts b/services/offline/src/index.ts index ef2fbf915..5d7411f1b 100644 --- a/services/offline/src/index.ts +++ b/services/offline/src/index.ts @@ -3,6 +3,10 @@ export { CacheableSection, useCacheableSection } from './lib/cacheable-section' export { useCachedSections } from './lib/cacheable-section-state' // Use "useOnlineStatus" name for backwards compatibility export { useNetworkStatus as useOnlineStatus } from './lib/network-status' -export { useOnlineStatusMessage } from './lib/online-status-message' +export { + useOnlineStatusMessage, + useSetOnlineStatusMessage, + useOnlineStatusMessageValue, +} from './lib/online-status-message' export { clearSensitiveCaches } from './lib/clear-sensitive-caches' export { useDhis2ConnectionStatus } from './lib/dhis2-connection-status' 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 71f44328e..c5801d93d 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 @@ -155,9 +155,9 @@ describe('initialization to the right values based on offline interface', () => wrapper: customWrapper, }) - expect(result.current.isConnected).toBe(false) - expect(result.current.isDisconnected).toBe(true) - expect(result.current.lastConnected).toEqual(testCurrentDate) + expect(result.current.isConnected).toBe(true) + expect(result.current.isDisconnected).toBe(false) + expect(result.current.lastConnected).toBe(null) }) }) diff --git a/services/offline/src/lib/dhis2-connection-status/dhis2-connection-status.tsx b/services/offline/src/lib/dhis2-connection-status/dhis2-connection-status.tsx index accf3ac69..35bf1b2ac 100644 --- a/services/offline/src/lib/dhis2-connection-status/dhis2-connection-status.tsx +++ b/services/offline/src/lib/dhis2-connection-status/dhis2-connection-status.tsx @@ -181,6 +181,16 @@ export const Dhis2ConnectionStatusProvider = ({ }, [pingAndHandleStatus, serverVersion]) useEffect(() => { + if (!offlineInterface.subscribeToDhis2ConnectionStatus) { + // Missing this functionality from the offline interface -- + // use a ping on startup to get the status + smartIntervalRef.current?.invokeCallbackImmediately() + console.warn( + 'Please upgrade to @dhis2/cli-app-scripts@>10.3.8 for full connection status features' + ) + return + } + const unsubscribe = offlineInterface.subscribeToDhis2ConnectionStatus({ onUpdate, }) @@ -190,23 +200,23 @@ export const Dhis2ConnectionStatusProvider = ({ }, [offlineInterface, onUpdate]) // Memoize this value to prevent unnecessary rerenders of context provider - const contextValue = useMemo( - () => ({ - // in the unlikely circumstance that offlineInterface.latestIsConnected - // is `null` when this initializes, fail safe by defaulting to - // `isConnected: false` - isConnected: Boolean(isConnected), - isDisconnected: !isConnected, - lastConnected: isConnected + const contextValue = useMemo(() => { + // in the unlikely circumstance that offlineInterface.latestIsConnected + // is `null` or `undefined` when this initializes, fail safe by defaulting to + // `isConnected: true`. A ping or SW update should update the status shortly. + const validatedIsConnected = isConnected ?? true + return { + isConnected: validatedIsConnected, + isDisconnected: !validatedIsConnected, + lastConnected: validatedIsConnected ? null : // Only evaluate if disconnected, since local storage // is synchronous and disk-based. // If lastConnected is not set in localStorage though, set it. // (relevant on startup) getLastConnected(appName) || updateLastConnected(appName), - }), - [isConnected, appName] - ) + } + }, [isConnected, appName]) return ( diff --git a/services/offline/src/lib/online-status-message.tsx b/services/offline/src/lib/online-status-message.tsx index 478d668b3..726b41a5e 100644 --- a/services/offline/src/lib/online-status-message.tsx +++ b/services/offline/src/lib/online-status-message.tsx @@ -1,39 +1,47 @@ import React, { ReactElement, ReactNode, useContext, useState } from 'react' -import { OnlineStatusMessageContextAPI } from '../types' -const defaultApi: OnlineStatusMessageContextAPI = { - onlineStatusMessage: undefined, - setOnlineStatusMessage: () => undefined, -} - -const OnlineStatusMessageContext = - React.createContext(defaultApi) - -export const useOnlineStatusMessage = (): OnlineStatusMessageContextAPI => { - const { onlineStatusMessage, setOnlineStatusMessage } = - useContext(OnlineStatusMessageContext) +type SetOnlineStatusMessage = (message: ReactNode) => void - return { - onlineStatusMessage, - setOnlineStatusMessage, - } -} +// 'get' and 'set' contexts are separated so 'setter' consumers that don't +// actually need the value don't have to rerender when the value changes: +const OnlineStatusMessageValueContext = + React.createContext(undefined) +const SetOnlineStatusMessageContext = + React.createContext(() => undefined) export const OnlineStatusMessageProvider = ({ children, }: { children: ReactNode }): ReactElement => { - const [onlineStatusMessage, setOnlineStatusMessage] = useState() + const [onlineStatusMessage, setOnlineStatusMessage] = useState() // note: not undefined return ( - - {children} - + + + {children} + + ) } + +export const useOnlineStatusMessageValue = () => { + return useContext(OnlineStatusMessageValueContext) +} + +export const useSetOnlineStatusMessage = () => { + return useContext(SetOnlineStatusMessageContext) +} + +// combination of both getter and setter (also provides backward compatability) +export const useOnlineStatusMessage = () => { + const onlineStatusMessage = useOnlineStatusMessageValue() + const setOnlineStatusMessage = useSetOnlineStatusMessage() + + return { + onlineStatusMessage, + setOnlineStatusMessage, + } +} diff --git a/services/offline/src/types.ts b/services/offline/src/types.ts index 730d9f13c..37580b406 100644 --- a/services/offline/src/types.ts +++ b/services/offline/src/types.ts @@ -1,5 +1,3 @@ -import { ReactNode } from 'react' - // Cacheable Section types export type RecordingState = 'default' | 'pending' | 'error' | 'recording' @@ -66,10 +64,3 @@ export interface OfflineInterface { getCachedSections: () => Promise removeSection: (id: string) => Promise } - -// Online status types - -export type OnlineStatusMessageContextAPI = { - onlineStatusMessage?: ReactNode - setOnlineStatusMessage: (additionalInfo: ReactNode) => void -}