Skip to content

Commit 99d9f2a

Browse files
committed
fix(app-platform): upgrade platform tools to use vite and react 18
1 parent 1d962c5 commit 99d9f2a

File tree

139 files changed

+7458
-61
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

139 files changed

+7458
-61
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
![React 18](https://img.shields.io/badge/react-18-blue)
12
# Scheduler
23

34
## Cypress env settings

package.json

+61-61
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,63 @@
11
{
2-
"name": "scheduler-app",
3-
"private": true,
4-
"scripts": {
5-
"build": "d2-app-scripts build",
6-
"start": "d2-app-scripts start",
7-
"start:nobrowser": "BROWSER=none d2-app-scripts start",
8-
"test": "d2-app-scripts test --coverage",
9-
"test:watch": "d2-app-scripts test --watch",
10-
"lint": "d2-style check",
11-
"format": "d2-style apply",
12-
"cypress": "start-server-and-test 'yarn start:nobrowser' 3000 'yarn exec cypress open'"
13-
},
14-
"dependencies": {
15-
"@dhis2/app-runtime": "^3.8.0",
16-
"@dhis2/d2-i18n": "^1.1.0",
17-
"@dhis2/prop-types": "2.0.3",
18-
"@dhis2/ui": "^9.11.8",
19-
"@testing-library/react": "^16.0.1",
20-
"classnames": "^2.3.1",
21-
"cronstrue": "^1.114.0",
22-
"history": "^4.9.0",
23-
"moment": "^2.29.1",
24-
"package.json": "^2.0.1",
25-
"prop-types": "^15.8.1",
26-
"react-router": "^5.0.1",
27-
"react-router-dom": "^5.2.0",
28-
"styled-jsx": "^4.0.1"
29-
},
30-
"devDependencies": {
31-
"@badeball/cypress-cucumber-preprocessor": "^20.0.3",
32-
"@cfaester/enzyme-adapter-react-18": "^0.8.0",
33-
"@cypress/webpack-preprocessor": "^6.0.1",
34-
"@dhis2/cli-app-scripts": "^12.0.0-alpha.19",
35-
"@dhis2/cli-style": "^10.7.4",
36-
"@testing-library/cypress": "^10.0.1",
37-
"cypress": "^13.7.2",
38-
"enzyme": "^3.10.0",
39-
"eslint-plugin-compat": "^3.9.0",
40-
"eslint-plugin-i18next": "^5.1.1",
41-
"eslint-plugin-import": "^2.23.4",
42-
"eslint-plugin-jsx-a11y": "^6.4.1",
43-
"identity-obj-proxy": "^3.0.0",
44-
"start-server-and-test": "^2.0.3"
45-
},
46-
"jest": {
47-
"setupFilesAfterEnv": [
48-
"<rootDir>/src/setupTests.js"
49-
],
50-
"collectCoverageFrom": [
51-
"src/**/*.{js,jsx}",
52-
"!src/{index.js,serviceWorker.js,setupTests.js}"
53-
],
54-
"coveragePathIgnorePatterns": [
55-
"/node_modules/",
56-
"/src/locales/"
57-
],
58-
"moduleNameMapper": {
59-
"\\.css$": "identity-obj-proxy"
60-
}
61-
},
62-
"version": "101.6.12"
2+
"name": "scheduler-app",
3+
"private": true,
4+
"scripts": {
5+
"build": "d2-app-scripts build",
6+
"start": "d2-app-scripts start",
7+
"start:nobrowser": "BROWSER=none d2-app-scripts start",
8+
"test": "d2-app-scripts test --coverage",
9+
"test:watch": "d2-app-scripts test --watch",
10+
"lint": "d2-style check",
11+
"format": "d2-style apply",
12+
"cypress": "start-server-and-test 'yarn start:nobrowser' 3000 'yarn exec cypress open'"
13+
},
14+
"dependencies": {
15+
"@dhis2/app-runtime": "^3.8.0",
16+
"@dhis2/d2-i18n": "^1.1.0",
17+
"@dhis2/prop-types": "2.0.3",
18+
"@dhis2/ui": "^9.11.8",
19+
"@testing-library/react": "^16.0.1",
20+
"classnames": "^2.3.1",
21+
"cronstrue": "^1.114.0",
22+
"history": "^4.9.0",
23+
"moment": "^2.29.1",
24+
"package.json": "^2.0.1",
25+
"prop-types": "^15.8.1",
26+
"react-router": "^5.0.1",
27+
"react-router-dom": "^5.2.0",
28+
"styled-jsx": "^4.0.1"
29+
},
30+
"devDependencies": {
31+
"@badeball/cypress-cucumber-preprocessor": "^20.0.3",
32+
"@cfaester/enzyme-adapter-react-18": "^0.8.0",
33+
"@cypress/webpack-preprocessor": "^6.0.1",
34+
"@dhis2/cli-app-scripts": "^12.0.0-alpha.19",
35+
"@dhis2/cli-style": "^10.7.4",
36+
"@testing-library/cypress": "^10.0.1",
37+
"cypress": "^13.7.2",
38+
"enzyme": "^3.10.0",
39+
"eslint-plugin-compat": "^3.9.0",
40+
"eslint-plugin-i18next": "^5.1.1",
41+
"eslint-plugin-import": "^2.23.4",
42+
"eslint-plugin-jsx-a11y": "^6.4.1",
43+
"identity-obj-proxy": "^3.0.0",
44+
"start-server-and-test": "^2.0.3"
45+
},
46+
"jest": {
47+
"setupFilesAfterEnv": [
48+
"<rootDir>/src/setupTests.js"
49+
],
50+
"collectCoverageFrom": [
51+
"src/**/*.{js,jsx}",
52+
"!src/{index.js,serviceWorker.js,setupTests.js}"
53+
],
54+
"coveragePathIgnorePatterns": [
55+
"/node_modules/",
56+
"/src/locales/"
57+
],
58+
"moduleNameMapper": {
59+
"\\.css$": "identity-obj-proxy"
60+
}
61+
},
62+
"version": "101.6.12"
6363
}

src/components/App/App.jsx

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from 'react'
2+
import { CssVariables } from '@dhis2/ui'
3+
import { Routes } from '../Routes'
4+
import { AuthWall } from '../AuthWall'
5+
import { Store } from '../Store'
6+
import { PageWrapper } from '../PageWrapper'
7+
import './App.css'
8+
9+
/* eslint-disable-next-line import/no-unassigned-import -- Necessary for translations to work */
10+
import '../../locales'
11+
12+
const App = () => (
13+
<React.Fragment>
14+
<CssVariables spacers colors theme />
15+
<PageWrapper>
16+
<AuthWall>
17+
<Store>
18+
<Routes />
19+
</Store>
20+
</AuthWall>
21+
</PageWrapper>
22+
</React.Fragment>
23+
)
24+
25+
export default App

src/components/App/App.test.jsx

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import React from 'react'
2+
import { shallow } from 'enzyme'
3+
import App from './App.jsx'
4+
5+
describe('<App>', () => {
6+
it('renders without errors', () => {
7+
shallow(<App />)
8+
})
9+
})

src/components/AuthWall/AuthWall.jsx

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import React from 'react'
2+
import PropTypes from 'prop-types'
3+
import { NoticeBox } from '@dhis2/ui'
4+
import i18n from '@dhis2/d2-i18n'
5+
import { useDataQuery } from '@dhis2/app-runtime'
6+
import { Spinner } from '../Spinner'
7+
import { getAuthorized } from './selectors'
8+
import styles from './AuthWall.module.css'
9+
10+
const query = {
11+
me: {
12+
resource: 'me',
13+
},
14+
}
15+
16+
const AuthWall = ({ children }) => {
17+
const { loading, error, data } = useDataQuery(query)
18+
19+
if (loading) {
20+
return <Spinner />
21+
}
22+
23+
if (error) {
24+
return (
25+
<div className={styles.noticeBoxWrapper}>
26+
<NoticeBox error title={i18n.t('Something went wrong')}>
27+
{i18n.t(
28+
'Something went wrong whilst retrieving user permissions.'
29+
)}
30+
</NoticeBox>
31+
</div>
32+
)
33+
}
34+
35+
const isAuthorized = getAuthorized(data.me)
36+
37+
if (!isAuthorized) {
38+
return (
39+
<div className={styles.noticeBoxWrapper}>
40+
<NoticeBox error title={i18n.t('Not authorized')}>
41+
{i18n.t(
42+
"You don't have access to the Job Scheduler. Contact a system administrator to request access."
43+
)}
44+
</NoticeBox>
45+
</div>
46+
)
47+
}
48+
49+
return <React.Fragment>{children}</React.Fragment>
50+
}
51+
52+
const { node } = PropTypes
53+
54+
AuthWall.propTypes = {
55+
children: node.isRequired,
56+
}
57+
58+
export default AuthWall
+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import React from 'react'
2+
import { shallow, mount } from 'enzyme'
3+
import { useDataQuery } from '@dhis2/app-runtime'
4+
import { getAuthorized } from './selectors'
5+
import AuthWall from './AuthWall.jsx'
6+
7+
jest.mock('@dhis2/app-runtime', () => ({
8+
useDataQuery: jest.fn(),
9+
}))
10+
11+
jest.mock('./selectors', () => ({
12+
getAuthorized: jest.fn(),
13+
}))
14+
15+
afterEach(() => {
16+
jest.resetAllMocks()
17+
})
18+
19+
describe('<AuthWall>', () => {
20+
it('shows a spinner when loading', () => {
21+
useDataQuery.mockImplementation(() => ({ loading: true }))
22+
23+
const wrapper = mount(<AuthWall>Child</AuthWall>)
24+
const loadingIndicator = wrapper.find({
25+
'data-test': 'dhis2-uicore-circularloader',
26+
})
27+
28+
expect(loadingIndicator).toHaveLength(1)
29+
})
30+
31+
it('shows a noticebox for fetching errors', () => {
32+
const message = 'Something went wrong'
33+
const error = new Error(message)
34+
35+
useDataQuery.mockImplementation(() => ({
36+
loading: false,
37+
error,
38+
}))
39+
40+
const wrapper = shallow(<AuthWall>Child</AuthWall>)
41+
const noticebox = wrapper.find('NoticeBox')
42+
43+
expect(noticebox).toHaveLength(1)
44+
})
45+
46+
it('shows a noticebox for unauthorized users', () => {
47+
useDataQuery.mockImplementation(() => ({
48+
loading: false,
49+
error: undefined,
50+
data: {},
51+
}))
52+
getAuthorized.mockImplementation(() => false)
53+
54+
const wrapper = shallow(<AuthWall>Child</AuthWall>)
55+
const noticebox = wrapper.find('NoticeBox')
56+
57+
expect(noticebox).toHaveLength(1)
58+
})
59+
60+
it('renders the children for users that are authorized', () => {
61+
useDataQuery.mockImplementation(() => ({
62+
loading: false,
63+
error: undefined,
64+
data: {},
65+
}))
66+
getAuthorized.mockImplementation(() => true)
67+
68+
const wrapper = shallow(<AuthWall>Child</AuthWall>)
69+
70+
expect(wrapper.text()).toEqual(expect.stringContaining('Child'))
71+
})
72+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React, { useState } from 'react'
2+
import PropTypes from 'prop-types'
3+
import { Button } from '@dhis2/ui'
4+
import i18n from '@dhis2/d2-i18n'
5+
import { CronPresetModal } from '../Modal'
6+
7+
const CronPresetButton = ({ setCron, small }) => {
8+
const [showModal, setShowModal] = useState(false)
9+
10+
return (
11+
<React.Fragment>
12+
<Button onClick={() => setShowModal(true)} small={small}>
13+
{i18n.t('Choose from preset times')}
14+
</Button>
15+
{showModal && (
16+
<CronPresetModal
17+
hideModal={
18+
/* istanbul ignore next */
19+
() => setShowModal(false)
20+
}
21+
setCron={setCron}
22+
/>
23+
)}
24+
</React.Fragment>
25+
)
26+
}
27+
28+
CronPresetButton.defaultProps = {
29+
small: false,
30+
}
31+
32+
const { func, bool } = PropTypes
33+
34+
CronPresetButton.propTypes = {
35+
setCron: func.isRequired,
36+
small: bool,
37+
}
38+
39+
export default CronPresetButton
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React from 'react'
2+
import { shallow, mount } from 'enzyme'
3+
import CronPresetButton from './CronPresetButton.jsx'
4+
5+
describe('<CronPresetButton>', () => {
6+
it('renders without errors', () => {
7+
shallow(<CronPresetButton setCron={() => {}} />)
8+
})
9+
10+
it('renders without errors when small', () => {
11+
shallow(<CronPresetButton setCron={() => {}} small />)
12+
})
13+
14+
it('shows the modal when button is clicked', () => {
15+
const wrapper = mount(<CronPresetButton setCron={() => {}} />)
16+
17+
expect(wrapper.find('CronPresetModal')).toHaveLength(0)
18+
19+
wrapper.find('button').simulate('click')
20+
21+
expect(wrapper.find('CronPresetModal')).toHaveLength(1)
22+
})
23+
})
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React, { useState } from 'react'
2+
import PropTypes from 'prop-types'
3+
import { Button } from '@dhis2/ui'
4+
import i18n from '@dhis2/d2-i18n'
5+
import { DeleteJobModal } from '../Modal'
6+
7+
const DeleteJobButton = ({ id, onSuccess }) => {
8+
const [showModal, setShowModal] = useState(false)
9+
10+
return (
11+
<React.Fragment>
12+
<Button destructive onClick={() => setShowModal(true)}>
13+
{i18n.t('Delete job')}
14+
</Button>
15+
{showModal && (
16+
<DeleteJobModal
17+
id={id}
18+
hideModal={
19+
/* istanbul ignore next */
20+
() => setShowModal(false)
21+
}
22+
onSuccess={onSuccess}
23+
/>
24+
)}
25+
</React.Fragment>
26+
)
27+
}
28+
29+
const { string, func } = PropTypes
30+
31+
DeleteJobButton.propTypes = {
32+
id: string.isRequired,
33+
onSuccess: func.isRequired,
34+
}
35+
36+
export default DeleteJobButton

0 commit comments

Comments
 (0)