Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(DHIS2-13915): show spinner when an app is being installed #542

Merged
merged 4 commits into from
Dec 15, 2023
Merged
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-preview-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 16.x

- uses: c-hive/gha-yarn-cache@v1
- run: yarn install --frozen-lockfile
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/dhis2-verify-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 16.x

- uses: c-hive/gha-yarn-cache@v1
- run: yarn install --frozen-lockfile
Expand All @@ -42,7 +42,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 16.x

- uses: c-hive/gha-yarn-cache@v1
- run: yarn install --frozen-lockfile
Expand All @@ -59,7 +59,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 16.x

- uses: c-hive/gha-yarn-cache@v1
- run: yarn install --frozen-lockfile
Expand All @@ -84,7 +84,7 @@ jobs:

- uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 16.x

- uses: actions/download-artifact@v2
with:
Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v16
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,16 @@
"dependencies": {
"@dhis2/app-runtime": "^3.2.0",
"@dhis2/d2-i18n": "^1.1.0",
"@dhis2/prop-types": "^2.0.3",
"@dhis2/ui": "^7.2.0",
"@dhis2/ui": "^9.0.1",
"moment": "^2.29.0",
"prop-types": "^15.8.1",
"query-string": "^6.14.1",
"react-router-dom": "^5.2.0",
"semver": "^7.3.4",
"use-debounce": "^6.0.0",
"use-query-params": "^1.2.0"
},
"resolutions": {
"@dhis2/ui": "^9.0.1"
}
}
2 changes: 1 addition & 1 deletion src/components/AppCard/AppCard.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PropTypes } from '@dhis2/prop-types'
import PropTypes from 'prop-types'
import React from 'react'
import { AppIcon } from '../AppIcon/AppIcon.js'
import styles from './AppCard.module.css'
Expand Down
2 changes: 1 addition & 1 deletion src/components/AppCards/AppCards.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PropTypes } from '@dhis2/prop-types'
import PropTypes from 'prop-types'
import React from 'react'
import styles from './AppCards.module.css'

Expand Down
2 changes: 1 addition & 1 deletion src/components/AppDetails/AppDetails.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import i18n from '@dhis2/d2-i18n'
import { PropTypes } from '@dhis2/prop-types'
import { Card, Divider } from '@dhis2/ui'
import moment from 'moment'
import PropTypes from 'prop-types'
import React, { useState } from 'react'
import { getAppIconSrc } from '../../get-app-icon-src.js'
import { getLatestVersion } from '../../get-latest-version.js'
Expand Down
29 changes: 22 additions & 7 deletions src/components/AppDetails/ManageInstalledVersion.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useAlert, useConfig } from '@dhis2/app-runtime'
import i18n from '@dhis2/d2-i18n'
import { PropTypes } from '@dhis2/prop-types'
import { Button } from '@dhis2/ui'
import React from 'react'
import { Button, CircularLoader } from '@dhis2/ui'
import PropTypes from 'prop-types'
import React, { useState } from 'react'
import semver from 'semver'
import { useApi } from '../../api.js'
import { getLatestVersion } from '../../get-latest-version.js'
Expand Down Expand Up @@ -41,8 +41,12 @@ export const ManageInstalledVersion = ({
const { installVersion, uninstallApp } = useApi()
const successAlert = useAlert(({ message }) => message, { success: true })
const errorAlert = useAlert(({ message }) => message, { critical: true })

const [isInstalling, setIsInstalling] = useState(false)

const handleInstall = async () => {
try {
setIsInstalling(true)
await installVersion(latestVersion.id)
successAlert.show({
message: canUpdate
Expand All @@ -62,6 +66,8 @@ export const ManageInstalledVersion = ({
nsSeparator: '-:-',
}),
})
} finally {
setIsInstalling(false)
}
}
const handleUninstall = async () => {
Expand All @@ -81,6 +87,12 @@ export const ManageInstalledVersion = ({
}
}

const installButtonText = isInstalling
? i18n.t('Installing...')
: canUpdate
? i18n.t('Update to latest version')
: i18n.t('Install')

return (
<div className={styles.manageInstalledVersion}>
{!hasCompatibleVersions && (
Expand All @@ -92,10 +104,13 @@ export const ManageInstalledVersion = ({
)}
{hasCompatibleVersions && canInstall && (
<>
<Button primary onClick={handleInstall}>
{canUpdate
? i18n.t('Update to latest version')
: i18n.t('Install')}
<Button
primary
onClick={handleInstall}
disabled={isInstalling}
icon={isInstalling ? <CircularLoader small /> : null}
>
{installButtonText}
</Button>
<span className={styles.manageInstalledVersionDescription}>
{i18n.t('{{channel}} release {{version}}', {
Expand Down
64 changes: 41 additions & 23 deletions src/components/AppDetails/Versions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useAlert, useConfig } from '@dhis2/app-runtime'
import i18n from '@dhis2/d2-i18n'
import { PropTypes } from '@dhis2/prop-types'
import {
Checkbox,
CircularLoader,
Expand All @@ -12,8 +11,10 @@ import {
TableBody,
TableRow,
TableCell,
ButtonStrip,
} from '@dhis2/ui'
import moment from 'moment'
import PropTypes from 'prop-types'
import React, { useState } from 'react'
import semver from 'semver'
import { useApi } from '../../api.js'
Expand Down Expand Up @@ -110,6 +111,11 @@ const VersionsTable = ({
<TableBody>
{versions.map((version) => {
const isVersionInstalling = versionBeingInstalled === version.id
const installButtonText = isVersionInstalling
? i18n.t('Installing...')
: version.version === installedVersion
? i18n.t('Installed')
: i18n.t('Install')
return (
<TableRow key={version.id} dataTest="versions-table-row">
<TableCell>{version.version}</TableCell>
Expand All @@ -120,28 +126,34 @@ const VersionsTable = ({
{moment(version.created).format('ll')}
</TableCell>
<TableCell>
<Button
small
secondary
className={styles.installBtn}
disabled={
version.version === installedVersion ||
versionBeingInstalled
}
onClick={() => onVersionInstall(version)}
>
{isVersionInstalling && (
<>
{i18n.t('Installing...')}
<CircularLoader small />
</>
)}
{!isVersionInstalling
? version.version === installedVersion
? i18n.t('Installed')
: i18n.t('Install')
: ''}
</Button>
<ButtonStrip>
<Button
small
secondary
className={styles.installBtn}
disabled={
version.version === installedVersion ||
!!versionBeingInstalled
}
onClick={() => onVersionInstall(version)}
icon={
isVersionInstalling ? (
<CircularLoader small />
) : null
}
>
{installButtonText}
</Button>
<a
download
href={version.downloadUrl}
className={styles.downloadLink}
>
<Button small secondary>
{i18n.t('Download')}
</Button>
</a>
</ButtonStrip>
</TableCell>
</TableRow>
)
Expand All @@ -159,6 +171,8 @@ VersionsTable.propTypes = {

export const Versions = ({ installedVersion, versions, onVersionInstall }) => {
const [channelsFilter, setChannelsFilter] = useState(new Set(['stable']))
const [versionBeingInstalled, setVersionBeingInstalled] = useState(null)

const installSuccessAlert = useAlert(i18n.t('App installed successfully'), {
success: true,
})
Expand All @@ -185,11 +199,14 @@ export const Versions = ({ installedVersion, versions, onVersionInstall }) => {

const handleVersionInstall = async (version) => {
try {
setVersionBeingInstalled(version.id)
await installVersion(version.id)
installSuccessAlert.show()
onVersionInstall()
} catch (error) {
installErrorAlert.show({ error })
} finally {
setVersionBeingInstalled(null)
}
}

Expand All @@ -202,6 +219,7 @@ export const Versions = ({ installedVersion, versions, onVersionInstall }) => {
/>
{filteredVersions.length > 0 ? (
<VersionsTable
versionBeingInstalled={versionBeingInstalled}
installedVersion={installedVersion}
versions={filteredVersions}
onVersionInstall={handleVersionInstall}
Expand Down
2 changes: 1 addition & 1 deletion src/components/AppIcon/AppIcon.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PropTypes } from '@dhis2/prop-types'
import PropTypes from 'prop-types'
import React from 'react'
import styles from './AppIcon.module.css'

Expand Down
2 changes: 1 addition & 1 deletion src/components/AppsList/AppsList.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import i18n from '@dhis2/d2-i18n'
import { PropTypes } from '@dhis2/prop-types'
import { InputField } from '@dhis2/ui'
import PropTypes from 'prop-types'
import React from 'react'
import { useHistory } from 'react-router-dom'
import { useQueryParam, StringParam, withDefault } from 'use-query-params'
Expand Down
2 changes: 1 addition & 1 deletion src/components/Sidebar/Sidebar.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useConfig } from '@dhis2/app-runtime'
import i18n from '@dhis2/d2-i18n'
import { PropTypes } from '@dhis2/prop-types'
import { Menu, MenuItem } from '@dhis2/ui'
import PropTypes from 'prop-types'
import React from 'react'
import { useHistory, useRouteMatch } from 'react-router-dom'
import styles from './Sidebar.module.css'
Expand Down
2 changes: 1 addition & 1 deletion src/pages/AppHub/AppHub.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useConfig, useDataQuery } from '@dhis2/app-runtime'
import i18n from '@dhis2/d2-i18n'
import { PropTypes } from '@dhis2/prop-types'
import {
InputField,
Pagination,
NoticeBox,
CenteredContent,
CircularLoader,
} from '@dhis2/ui'
import PropTypes from 'prop-types'
import React, { useEffect } from 'react'
import { useHistory } from 'react-router-dom'
import { useDebounce } from 'use-debounce'
Expand Down Expand Up @@ -117,7 +117,7 @@
})
useEffect(() => {
refetch(queryParams)
}, [debouncedQuery, queryParams.page])

Check warning on line 120 in src/pages/AppHub/AppHub.js

View workflow job for this annotation

GitHub Actions / lint

React Hook useEffect has missing dependencies: 'queryParams' and 'refetch'. Either include them or remove the dependency array
const handleQueryChange = (query) => {
setQueryParams({ query, page: 1 }, 'replace')
}
Expand Down
17 changes: 15 additions & 2 deletions src/pages/AppHubApp/AppHubApp.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useDataQuery } from '@dhis2/app-runtime'
import i18n from '@dhis2/d2-i18n'
import { PropTypes } from '@dhis2/prop-types'
import { NoticeBox, CenteredContent, CircularLoader } from '@dhis2/ui'
import PropTypes from 'prop-types'
import React from 'react'
import { useHistory } from 'react-router-dom'
import { AppDetails } from '../../components/AppDetails/AppDetails.js'
Expand Down Expand Up @@ -47,8 +47,21 @@ export const AppHubApp = ({ match }) => {
</NoticeBox>
)
}

// ToDo: This is a workaround to match non-core apps to fix this bug https://dhis2.atlassian.net/browse/DHIS2-15586
// we don't have an app ID for these apps, so we can't reliably match them. This is the best we can do for now:
// to match with the name + developer email
const matchesNonCoreApp = (installedApp, appHubDetails) => {
return (
!installedApp.app_hub_id &&
installedApp.name === appHubDetails.name &&
installedApp.developer?.email === appHubDetails.developer?.email
)
}

const installedApp = installedApps.find(
(app) => app.app_hub_id === appHubId
(app) =>
app.app_hub_id === appHubId || matchesNonCoreApp(app, appHubApp)
)

return (
Expand Down
2 changes: 1 addition & 1 deletion src/pages/InstalledApp/InstalledApp.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useDataQuery } from '@dhis2/app-runtime'
import i18n from '@dhis2/d2-i18n'
import { PropTypes } from '@dhis2/prop-types'
import { NoticeBox, CenteredContent, CircularLoader } from '@dhis2/ui'
import PropTypes from 'prop-types'
import React from 'react'
import { useHistory } from 'react-router-dom'
import { AppDetails } from '../../components/AppDetails/AppDetails.js'
Expand Down
Loading
Loading