Skip to content

Commit

Permalink
fix(DHIS2-13252): disable install button when no compatible versions …
Browse files Browse the repository at this point in the history
…are available
  • Loading branch information
kabaros committed Dec 13, 2023
1 parent a00f1e8 commit df9ddf2
Show file tree
Hide file tree
Showing 13 changed files with 1,802 additions and 87 deletions.
35 changes: 19 additions & 16 deletions .github/workflows/dhis2-verify-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,25 @@ jobs:
- name: Lint
run: yarn lint

# test:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v2
# - uses: actions/setup-node@v1
# with:
# node-version: 12.x
#
# - uses: c-hive/gha-yarn-cache@v1
# - run: yarn install --frozen-lockfile
#
# - name: Generate translations
# run: yarn d2-app-scripts i18n generate
#
# - name: Test
# run: yarn test
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12.x

- uses: c-hive/gha-yarn-cache@v1
- run: yarn install --frozen-lockfile

# - name: Generate translations
# run: yarn d2-app-scripts i18n generate

- name: Test
run: yarn test --coverage

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1

release:
runs-on: ubuntu-latest
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ npm-debug.log
node_modules
.d2
src/locales
build
build

coverage
6 changes: 6 additions & 0 deletions jest-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { configure } from '@testing-library/react'
import '@testing-library/jest-dom'

beforeEach(() => {
configure({ testIdAttribute: 'data-test' })
})
20 changes: 20 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const config = {
setupFilesAfterEnv: ['<rootDir>/jest-setup.js'],
collectCoverageFrom: [
"src/**/*.{js,jsx,ts,tsx}",
"!<rootDir>/node_modules/"
],
coverageThreshold: {
global: {
// TODO: The following should be 50
branches: 0,

// TODO: The following should be 75
functions: 0,
lines: 0,
statements: 0,
},
},
}

module.exports = config
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@
"homepage": "https://github.com/dhis2/app-management-app#readme",
"devDependencies": {
"@dhis2/cli-app-scripts": "^8.0.1",
"@dhis2/cli-style": "^10.4.1"
"@dhis2/cli-style": "^10.4.1",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^12",
"@types/jest": "^29.5.11",
"jest": "^29.7.0",
"react-dom": "^16"
},
"dependencies": {
"@dhis2/app-runtime": "^3.2.0",
Expand Down
30 changes: 25 additions & 5 deletions src/components/AppDetails/ManageInstalledVersion.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useAlert } from '@dhis2/app-runtime'
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 React, { useState } from 'react'
import semver from 'semver'
import { useApi } from '../../api.js'
import { getLatestVersion } from '../../get-latest-version.js'
import { semverGt } from '../../semver-gt.js'
import { getCompatibleVersions, semverGt } from '../../semver-helpers.js'
import styles from './AppDetails.module.css'
import { channelToDisplayName } from './channel-to-display-name.js'

Expand All @@ -22,6 +23,16 @@ export const ManageInstalledVersion = ({
const latestVersion = getLatestVersion(versions)
const canInstall =
latestVersion && latestVersion.version !== installedApp?.version

const { serverVersion } = useConfig()

const dhisVersion = semver.coerce(
`${serverVersion.major}.${serverVersion.minor}`
)

const compatibleVersions = getCompatibleVersions(versions, dhisVersion)
const hasCompatibleVersions = compatibleVersions.length > 0

const canUninstall = installedApp && !isBundled
const canUpdate =
installedApp &&
Expand Down Expand Up @@ -72,7 +83,16 @@ export const ManageInstalledVersion = ({

return (
<div className={styles.manageInstalledVersion}>
{canInstall && (
{!hasCompatibleVersions && (
<>
<em>
{i18n.t(
'There are no compatible versions available.'
)}
</em>
</>
)}
{hasCompatibleVersions && canInstall && (
<>
<Button primary onClick={handleInstall}>
{canUpdate
Expand Down
87 changes: 39 additions & 48 deletions src/components/AppDetails/Versions.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import semver from 'semver'
import { useApi } from '../../api.js'
import styles from './AppDetails.module.css'
import { channelToDisplayName } from './channel-to-display-name.js'
import { getCompatibleVersions } from '../../semver-helpers.js'

const ChannelCheckbox = ({
name,
Expand Down Expand Up @@ -101,39 +102,43 @@ const VersionsTable = ({ installedVersion, versions, onVersionInstall }) => (
</TableRowHead>
</TableHead>
<TableBody>
{versions.map((version) => (
<TableRow key={version.id}>
<TableCell>{version.version}</TableCell>
<TableCell>
{channelToDisplayName[version.channel]}
</TableCell>
<TableCell>
{moment(version.created).format('ll')}
</TableCell>
<TableCell>
<Button
small
secondary
className={styles.installBtn}
disabled={version.version === installedVersion}
onClick={() => onVersionInstall(version)}
>
{version.version === installedVersion
? i18n.t('Installed')
: i18n.t('Install')}
</Button>
<a
download
href={version.downloadUrl}
className={styles.downloadLink}
>
<Button small secondary>
{i18n.t('Download')}
{versions.map((version) => {
const isVersionInstalling = versionBeingInstalled === version.id
return (
<TableRow key={version.id} dataTest='versions-table-row'>
<TableCell>{version.version}</TableCell>
<TableCell>
{channelToDisplayName[version.channel]}
</TableCell>
<TableCell>
{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>
</a>
</TableCell>
</TableRow>
))}
)})}
</TableBody>
</Table>
)
Expand All @@ -158,28 +163,14 @@ export const Versions = ({ installedVersion, versions, onVersionInstall }) => {
{ critical: true }
)
const { serverVersion } = useConfig()

const { installVersion } = useApi()
const dhisVersion = semver.coerce(
`${serverVersion.major}.${serverVersion.minor}`
)
const satisfiesDhisVersion = (version) => {
const { minDhisVersion: min, maxDhisVersion: max } = version
if (!min && !max) {
return true
} else if (min && max) {
const range = new semver.Range(`${min} - ${max}`)
return semver.satisfies(dhisVersion, range)
} else if (!min && max) {
const range = new semver.Range(`<=${max}`)
return semver.satisfies(dhisVersion, range)
} else if (min && !max) {
const range = new semver.Range(`>=${min}`)
return semver.satisfies(dhisVersion, range)
}
}
const filteredVersions = versions
.filter((version) => channelsFilter.has(version.channel))
.filter(satisfiesDhisVersion)

const filteredVersions = getCompatibleVersions(versions, dhisVersion, channelsFilter)

const handleVersionInstall = async (version) => {
try {
await installVersion(version.id)
Expand Down
118 changes: 118 additions & 0 deletions src/components/AppDetails/Versions.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { queryAllByRole, render } from '@testing-library/react'
import {Versions} from './Versions'
import { useConfig } from '@dhis2/app-runtime'

jest.mock('@dhis2/app-runtime', () => ({
...jest.requireActual('@dhis2/app-runtime'),
useConfig: jest.fn()
}))

describe('Versions table', () => {
const installedVersion = '100.2.27'
const versions = [
{
"created": 1700358745976,
"demoUrl": "",
"downloadUrl": "https://apps.dhis2.org/api/v1/apps/download/dhis2/app-management_100.2.29.zip",
"id": "6a8fab1a-da21-4340-8c68-92e84eaa4c40",
"lastUpdated": 1700358745976,
"maxDhisVersion": "",
"minDhisVersion": "2.37",
"version": "100.2.29",
"channel": "stable"
},
{
"created": 1694656721047,
"demoUrl": "",
"downloadUrl": "https://apps.dhis2.org/api/v1/apps/download/dhis2/app-management_100.2.28.zip",
"id": "447b0753-5064-4bd5-8894-1c64ab26b624",
"lastUpdated": 1694656721047,
"maxDhisVersion": "",
"minDhisVersion": "2.37",
"version": "100.2.28",
"channel": "stable"
},
{
"created": 1690854245259,
"demoUrl": "",
"downloadUrl": "https://apps.dhis2.org/api/v1/apps/download/dhis2/app-management_100.2.27.zip",
"id": "b380f11e-b761-4437-b30e-1d765ef9b06b",
"lastUpdated": 1690854245259,
"maxDhisVersion": "",
"minDhisVersion": "2.37",
"version": "100.2.27",
"channel": "stable"
}
]

beforeEach(() => {
useConfig.mockReturnValue({
serverVersion:{
"major": 2,
"minor": 37,
"patch": 11,
"tag": "SNAPSHOT"
}
})
})

it('should display all compatible versions', () => {
const onVersionInstall = jest.fn()
const { getAllByTestId } = render(<Versions installedVersion={installedVersion} versions={versions} onVersionInstall={onVersionInstall} />)

const rows = getAllByTestId('versions-table-row');
expect(rows.length).toEqual(versions.length)

rows.forEach((row, i) => {
expect(row).toHaveTextContent(versions[i].version)
expect(row).toHaveTextContent(new RegExp(versions[i].channel, 'i'))
})
})

it('should display "installed" for the installed version', () => {
const onVersionInstall = jest.fn()
const { getAllByTestId } = render(<Versions installedVersion={installedVersion} versions={versions} onVersionInstall={onVersionInstall} />)

const [firstRow, secondRow, thirdRow] = getAllByTestId('versions-table-row');

expect(queryAllByRole(firstRow, 'button')[0]).toHaveTextContent('Install')
expect(queryAllByRole(secondRow, 'button')[0]).toHaveTextContent('Install')

const thirdInstalledButton = queryAllByRole(thirdRow, 'button')[0]
expect(thirdInstalledButton).toHaveTextContent('Installed')
expect(thirdInstalledButton).toHaveAttribute('disabled')
})

it('should display empty table when no compatible versions', () => {
const onVersionInstall = jest.fn()
const versionsListWithoutMatch = [
{
"created": 1702172801684,
"demoUrl": "",
"downloadUrl": "https://apps.dhis2.org/api/v1/apps/download/dhis2/capture_100.47.2.zip",
"id": "f78e7344-8e95-4f7e-b2d9-fd8d98c30196",
"lastUpdated": 1702172801684,
"maxDhisVersion": "",
"minDhisVersion": "2.38",
"version": "100.47.2",
"channel": "stable"
},
{
"maxDhisVersion": "",
"minDhisVersion": "2.38",
"version": "100.47.1",
"channel": "stable"
},
{
"maxDhisVersion": "",
"minDhisVersion": "2.38",
"version": "100.47.0",
"channel": "stable"
},
]
const { getByText } = render(<Versions installedVersion={installedVersion} versions={versionsListWithoutMatch} onVersionInstall={onVersionInstall} />)

expect(getByText('There are no compatible versions matching your criteria')).toBeInTheDocument()
})

})
2 changes: 1 addition & 1 deletion src/pages/CoreApps/CoreApps.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { AppHubErrorNoticeBox } from '../../components/AppHubErrorNoticeBox/AppH
import { AppsList } from '../../components/AppsList/AppsList.js'
import { coreApps } from '../../core-apps.js'
import { getLatestVersion } from '../../get-latest-version.js'
import { semverGt } from '../../semver-gt.js'
import { semverGt } from '../../semver-helpers.js'

const query = {
overriddenCoreApps: {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/CustomApps/CustomApps.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import React from 'react'
import { AppHubErrorNoticeBox } from '../../components/AppHubErrorNoticeBox/AppHubErrorNoticeBox.js'
import { AppsList } from '../../components/AppsList/AppsList.js'
import { getLatestVersion } from '../../get-latest-version.js'
import { semverGt } from '../../semver-gt.js'
import { semverGt } from '../../semver-helpers.js'

const query = {
customApps: {
Expand Down
Loading

0 comments on commit df9ddf2

Please sign in to comment.