zFi1u##7d(OKMB(!k%|nXH`(RqN@AvoRTkJ&94xZf^TqJ;{q$4Ue!4d{O!`{|eOf4B
zsyQx;`-Kq
zA4PVoDx%!Xa^4}Dnf>MJr;$A(LG|#U;NAPyV|RKNMzRFza*>&!%t6M>=dO`j#o%5;@&06Mxdz8P_SoEZkLx*R!b?+X>;pvh#^;DrW#id}>
zRKcAM0jvDJK7&BXp;WP#*X$lrmOdmfQ0YVhbTq$mMPi2kjBjr!b<@o$r+ZDj(=yZJ
z<4DjV_W0lXGV=#!s&$1?`q59548Xc7V*XOdJqkglNT^a|x-0E9)b;3YzI@5=;jpP*
z;6kctgPP`j;5wXp*4>k`=lEw;Y@kH7F3()x>hL;8D*Q=!&p_{0;voH_=rai+QGQ2k
z>ChOOBAxJ4EgG_>BRSlt!0^5rKnO^jj5V=&28jbpRoi2e&>i^ruZM$uYdG62D5gs2x!?|+7FP6qYoM$}vX-(h?
zezV?Yn^L%&K5$A`7ELAzit^p$g0a?uY%YFFg1xk
zy)WMe{nV4)BW2)rxy$zySo0poHT;Skzg^mLeghRuaqj1c%V_$nEqFlPh~k=-ASZ3C
zPuBC*eN_m4H^o^eZNGFYPjHhDHqN@TOvZUmm5&dW2cTg3awwn3*>{c~BW8a5sSOac
z5=I%ww%J@-8Xc1a!f9Ii-yw?Ih2OYcmXHkW9!
zmERYA1}ho=7;9|fBhT%{oZeGneW;-aqUOvU9r$OyF_RHGUEs|ACIfrISki0N3AjRo
z_u}@4109SJ)ZCSZn6`vT3kl>v0*LT^(H*Ydn<|)X^r{h5A`hPW&axG5x=|kfDfLBd7e>Om4JvZE0>;i
zaE10gx^v%lBi7dWg+OJ@qvwYjE)j+HGK?(~Mkqan{UJ6#C7F
z6-a``$*-556Zc7K1gnINH!L!7FIuZbeH9ha{^m>vJa_gg5X^qw+AGP^|EP?)xS~L;
z&xB$qk`2~e%0j@8?P_VL^gF_xc8`PZzo*I|B`k+-^Seama_2@u!wwoO$Tf1Zw=32?-K`5Q3k6kD61)`DBFP{
z8emloRb@G46%A!&v^AyUe*l8eUVc82|2yCUCoAp*fQYhlz}dKmU%~{Vef$EwFX19F
z-k1C^I8Oi|g0w*AJcG5DmhQCd>NZ}o0kB-*Q1s;Bzrt^td5(quk|7@2>cw*9o!zCb
n@!^iP?l$o@wl4gQA`?Zxx)3jpZlL<;gaTlE&BBmu;1>TMCEC5~
diff --git a/public/safari-pinned-tab.svg b/public/safari-pinned-tab.svg
deleted file mode 100755
index da8517ce..00000000
--- a/public/safari-pinned-tab.svg
+++ /dev/null
@@ -1,51 +0,0 @@
-
-
-
diff --git a/src/App.js b/src/App.js
index 053d1ae7..1af75671 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,115 +1,31 @@
-import 'typeface-roboto'
+import { useD2 } from '@dhis2/app-runtime-adapter-d2'
+import { CssVariables } from '@dhis2/ui'
+import { MuiThemeProvider } from 'material-ui/styles'
+import React from 'react'
+import { HashRouter } from 'react-router-dom'
import 'material-design-icons-iconfont'
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import { Link } from 'react-router-dom'
-import { HeaderBar } from '@dhis2/ui-widgets'
-import Sidebar from 'd2-ui/lib/sidebar/Sidebar.component'
-import CircularProgress from 'd2-ui/lib/circular-progress/CircularProgress'
-import FeedbackSnackbar from 'd2-ui/lib/feedback-snackbar/FeedbackSnackbar.component'
-import './custom-css/D2UISidebarOverrides.css'
-import i18n from './locales'
-import AppRouter from './components/app-router/AppRouter'
-import styles from './App.module.css'
-import { sections } from './pages/sections.conf'
+import App from './components/app/App'
+import appTheme from './theme'
+import './grid.css'
+import './locales'
-class App extends PureComponent {
- static childContextTypes = {
- d2: PropTypes.object,
- showSnackbar: PropTypes.bool,
- snackbarConf: PropTypes.shape({
- type: PropTypes.string,
- message: PropTypes.string,
- }),
- currentSection: PropTypes.string,
- updateAppState: PropTypes.func,
- }
-
- constructor(props) {
- super(props)
-
- this.state = {
- currentSection: '',
- showSnackbar: false,
- snackbarConf: {
- type: '',
- message: '',
- },
- pageState: {},
- }
+const AppWrapper = () => {
+ const { d2 } = useD2()
- this.updateAppState = this.updateAppState.bind(this)
+ if (!d2) {
+ return null
}
- getChildContext() {
- return {
- d2: this.props.d2,
- showSnackbar: this.state.showSnackbar,
- snackbarConf: this.state.snackbarConf,
- currentSection: this.state.currentSection,
- updateAppState: this.updateAppState,
- }
- }
-
- updateAppState(appState) {
- if (
- appState.currentSection &&
- !appState.pageState &&
- this.state.currentSection !== appState.currentSection
- ) {
- // clear page state because we are updating page
- this.setState({ ...appState, pageState: {}, showSnackbar: false })
- } else {
- this.setState(appState)
- }
- }
-
- render() {
- const nonOnChangeSection = () => null
- const translatedSections = sections.map(section =>
- Object.assign(section, {
- icon: section.info.icon,
- label: section.info.label(),
- containerElement: ,
- })
- )
-
- const feedbackElement = this.state.pageState.loading ? (
-
-
-
- ) : (
-
- )
-
- const hideSidebar =
- this.state.pageState && this.state.pageState.showTable
- const contentWrapperClassName = hideSidebar
- ? styles.contentWrapperNoMargin
- : styles.contentWrapper
-
- return (
-
-
- {!hideSidebar && (
-
- )}
-
-
{feedbackElement}
-
- )
- }
+ return (
+ <>
+
+
+
+
+
+
+ >
+ )
}
-export default App
+export default AppWrapper
diff --git a/src/App.module.css b/src/App.module.css
deleted file mode 100644
index 428d59aa..00000000
--- a/src/App.module.css
+++ /dev/null
@@ -1,29 +0,0 @@
-.contentArea {
- padding: 0px 20px 20px 20px;
-}
-
-.contentWrapper {
- margin-left: 295px;
- max-width: 1400px;
-}
-
-.contentWrapperNoMargin {
- margin-left: 0;
- max-width: 100%;
-}
-
-.leftBar {
- width: 295px;
- position: fixed;
- bottom: 0;
- top: 0;
- left: 0;
- margin-top: 3rem;
-}
-
-.centered {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
-}
diff --git a/src/components/alert-bar/AlertBar.js b/src/components/alert-bar/AlertBar.js
index 6de8753e..0d80a2dc 100644
--- a/src/components/alert-bar/AlertBar.js
+++ b/src/components/alert-bar/AlertBar.js
@@ -1,8 +1,8 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
+import i18n from '@dhis2/d2-i18n'
import { Paper } from 'material-ui'
+import PropTypes from 'prop-types'
+import React, { PureComponent } from 'react'
import styles from './AlertBar.module.css'
-import i18n from '@dhis2/d2-i18n'
class AlertBar extends PureComponent {
static propTypes = {
diff --git a/src/components/app-router/AppRouter.js b/src/components/app-router/AppRouter.js
index 6d962f79..1364dd82 100644
--- a/src/components/app-router/AppRouter.js
+++ b/src/components/app-router/AppRouter.js
@@ -1,9 +1,9 @@
-import React from 'react'
import PropTypes from 'prop-types'
+import React from 'react'
import { Route, Switch } from 'react-router-dom'
import Home from '../../pages/home/Home'
-import NoMatch from './NoMatch'
import { sections } from '../../pages/sections.conf'
+import NoMatch from './NoMatch'
const AppRouter = ({ pageState }) => {
const routes = sections.map(section => {
diff --git a/src/components/app-router/AppRouter.test.js b/src/components/app-router/AppRouter.test.js
deleted file mode 100644
index 7dc8f6ae..00000000
--- a/src/components/app-router/AppRouter.test.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/* eslint-disable */
-import React from 'react'
-import { shallow } from 'enzyme'
-import AppRouter from './AppRouter'
-import { Route, Switch } from 'react-router-dom'
-import { sections } from '../../pages/sections.conf'
-
-const pageState = {}
-
-jest.mock('d2-ui/lib/org-unit-tree/OrgUnitTree.component', () => 'OrgUnitTree')
-jest.mock(
- 'd2-ui/lib/feedback-snackbar/FeedbackSnackbarTypes',
- () => 'FeedbackSnackbarTypes'
-)
-
-const ownShallow = () => {
- return shallow(, {
- disableLifecycleMethods: true,
- })
-}
-
-it('AppRouter renders without crashing', () => {
- ownShallow()
-})
-
-it('AppRouter renders a Switch', () => {
- const wrapper = ownShallow()
- expect(wrapper.find(Switch)).toHaveLength(1)
-})
-
-it('AppRouter renders the correct number of Route', () => {
- const wrapper = ownShallow()
- expect(wrapper.find(Route)).toHaveLength(sections.length + 2) // Pages plus home and no match route
-})
diff --git a/src/components/app-router/NoMatch.js b/src/components/app-router/NoMatch.js
index 494b5815..16fe7d0a 100644
--- a/src/components/app-router/NoMatch.js
+++ b/src/components/app-router/NoMatch.js
@@ -1,5 +1,5 @@
-import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
+import React, { PureComponent } from 'react'
class NoMatch extends PureComponent {
static propTypes = {
diff --git a/src/components/app-router/NoMatch.test.js b/src/components/app-router/NoMatch.test.js
deleted file mode 100644
index 75c48730..00000000
--- a/src/components/app-router/NoMatch.test.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/* eslint-disable */
-import React from 'react'
-import { shallow } from 'enzyme'
-import NoMatch from './NoMatch'
-
-const location = {
- path: 'path',
-}
-
-const ownShallow = () => {
- return shallow(, {
- disableLifecycleMethods: true,
- })
-}
-
-it('NoMatch renders without crashing', () => {
- ownShallow()
-})
-
-it('NoMatch renders a div element', () => {
- const wrapper = ownShallow()
- expect(wrapper.find('div')).toHaveLength(1)
-})
-
-it('NoMatch renders a h3 element', () => {
- const wrapper = ownShallow()
- expect(wrapper.find('h3')).toHaveLength(1)
-})
-
-it('NoMatch renders a code element', () => {
- const wrapper = ownShallow()
- expect(wrapper.find('code')).toHaveLength(1)
-})
diff --git a/src/components/app/App.js b/src/components/app/App.js
new file mode 100644
index 00000000..8fb398e2
--- /dev/null
+++ b/src/components/app/App.js
@@ -0,0 +1,80 @@
+import Sidebar from 'd2-ui/lib/sidebar/Sidebar.component'
+import PropTypes from 'prop-types'
+import React, { PureComponent } from 'react'
+import { Link } from 'react-router-dom'
+import { withRouter } from 'react-router-dom'
+import { sections } from '../../pages/sections.conf'
+import AppRouter from '../app-router/AppRouter'
+import styles from './App.module.css'
+
+const noop = () => null
+
+class App extends PureComponent {
+ static childContextTypes = {
+ d2: PropTypes.object,
+ currentSection: PropTypes.string,
+ updateAppState: PropTypes.func,
+ }
+
+ constructor(props) {
+ super(props)
+
+ this.state = {
+ currentSection: '',
+ pageState: {},
+ }
+ }
+
+ getChildContext() {
+ return {
+ d2: this.props.d2,
+ currentSection: this.state.currentSection,
+ updateAppState: this.updateAppState,
+ }
+ }
+
+ updateAppState = appState => {
+ if (
+ appState.currentSection &&
+ !appState.pageState &&
+ this.state.currentSection !== appState.currentSection
+ ) {
+ // clear page state because we are updating page
+ this.setState({ ...appState, pageState: {} })
+ } else {
+ this.setState(appState)
+ }
+ }
+
+ render() {
+ const translatedSections = sections.map(section => ({
+ ...section,
+ icon: section.info.icon,
+ label: section.info.label(),
+ containerElement: ,
+ }))
+
+ const showSidebar = !this.state.pageState?.showTable
+
+ return (
+
+ {showSidebar && (
+
+ )}
+
+
+ )
+ }
+}
+
+App.propTypes = {
+ d2: PropTypes.object,
+}
+
+export default withRouter(App)
diff --git a/src/components/app/App.module.css b/src/components/app/App.module.css
new file mode 100644
index 00000000..e9917a29
--- /dev/null
+++ b/src/components/app/App.module.css
@@ -0,0 +1,12 @@
+.container {
+ display: flex;
+ min-height: 100%;
+ background: var(--colors-grey100);
+}
+
+.contentArea {
+ border-left: 1px solid var(--colors-grey400);
+ flex: 1;
+ padding-left: 1em;
+ padding: 0px 20px 20px 20px;
+}
diff --git a/src/components/available-datasets-select/AvailableDatasetsSelect.js b/src/components/available-datasets-select/AvailableDatasetsSelect.js
index 2590e3a6..665714c1 100644
--- a/src/components/available-datasets-select/AvailableDatasetsSelect.js
+++ b/src/components/available-datasets-select/AvailableDatasetsSelect.js
@@ -1,6 +1,7 @@
-import React, { PureComponent } from 'react'
+import i18n from '@dhis2/d2-i18n'
+import { MultiSelect, MultiSelectOption, Help, CircularLoader } from '@dhis2/ui'
import PropTypes from 'prop-types'
-import styles from './AvailableDatasetsSelect.module.css'
+import React, { PureComponent } from 'react'
class AvailableDatasetsSelect extends PureComponent {
static contextTypes = {
@@ -8,57 +9,63 @@ class AvailableDatasetsSelect extends PureComponent {
}
static propTypes = {
+ selected: PropTypes.array,
onChange: PropTypes.func,
}
- static defaultProps = {
- onChange: null,
- }
-
constructor() {
super()
this.state = {
dataSets: null,
+ error: false,
}
}
- componentDidMount() {
+ loadDatasets() {
const d2 = this.context.d2
- if (this.state.dataSets == null) {
- d2.models.dataSet
- .list({
- paging: false,
- fields: 'id,displayName',
- })
- .then(dataSetsResponse => {
- this.setState({
- dataSets: dataSetsResponse.toArray(),
- })
- })
- .catch(() => {
- this.manageError()
+ return d2.models.dataSet.list({
+ paging: false,
+ fields: 'id,displayName',
+ })
+ }
+
+ componentDidMount() {
+ this.loadDatasets()
+ .then(dataSetsResponse => {
+ this.setState({
+ dataSets: dataSetsResponse.toArray(),
})
- }
+ })
+ .catch(() => {
+ this.setState({ error: true })
+ })
}
render() {
+ if (this.state.error) {
+ return {i18n.t('Error loading datasets.')}
+ }
+
+ if (!this.state.dataSets) {
+ return
+ }
+
return (
-
+
)
}
}
diff --git a/src/components/available-datasets-select/AvailableDatasetsSelect.module.css b/src/components/available-datasets-select/AvailableDatasetsSelect.module.css
deleted file mode 100644
index 440516ff..00000000
--- a/src/components/available-datasets-select/AvailableDatasetsSelect.module.css
+++ /dev/null
@@ -1,11 +0,0 @@
-.select {
- width: 100%;
- height: 100%;
- border: none;
- font-size: 13px;
- border: solid 1px #bcbcbc;
-}
-
-.options {
- padding: .25rem .5rem;
-}
\ No newline at end of file
diff --git a/src/components/available-datasets-select/AvailableDatasetsSelect.test.js b/src/components/available-datasets-select/AvailableDatasetsSelect.test.js
deleted file mode 100644
index 1c06f060..00000000
--- a/src/components/available-datasets-select/AvailableDatasetsSelect.test.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/* eslint-disable */
-import React from 'react'
-import { shallow } from 'enzyme'
-import AvailableDatasetsSelect from './AvailableDatasetsSelect'
-
-jest.mock('d2-ui/lib/org-unit-tree/OrgUnitTree.component', () => 'OrgUnitTree')
-
-const ownShallow = () => {
- const onChange = jest.fn()
- return shallow(, {
- disableLifecycleMethods: true,
- context: {
- d2: {},
- },
- })
-}
-
-it('AvailableDatasetsSelect renders without crashing', () => {
- ownShallow()
-})
-
-it('AvailableDatasetsSelect renders one html select', () => {
- const wrapper = ownShallow()
- expect(wrapper.find('select')).toHaveLength(1)
-})
-
-it('AvailableDatasetsSelect renders no select options', () => {
- const wrapper = ownShallow()
- expect(wrapper.find('option')).toHaveLength(0)
-})
-
-it('AvailableDatasetsSelect renders correct number of select options', () => {
- const wrapper = ownShallow()
- const fakeDataSets = [
- { id: 'dataset1', displayName: 'dataset1' },
- { id: 'dataset2', displayName: 'dataset2' },
- ]
- wrapper.setState({ dataSets: fakeDataSets })
- expect(wrapper.find('option')).toHaveLength(fakeDataSets.length)
-})
diff --git a/src/components/available-organisation-units-tree/AvailableOrganisationUnitsTree.js b/src/components/available-organisation-units-tree/AvailableOrganisationUnitsTree.js
index 7bd2ca29..35b5f55f 100644
--- a/src/components/available-organisation-units-tree/AvailableOrganisationUnitsTree.js
+++ b/src/components/available-organisation-units-tree/AvailableOrganisationUnitsTree.js
@@ -1,131 +1,88 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import OrgUnitTree from 'd2-ui/lib/org-unit-tree/OrgUnitTree.component'
+import { useDataQuery } from '@dhis2/app-runtime'
import i18n from '@dhis2/d2-i18n'
+import { OrganisationUnitTree } from '@dhis2/ui'
+import { CircularLoader, Help } from '@dhis2/ui'
+import PropTypes from 'prop-types'
+import React, { useState } from 'react'
import styles from './AvailableOrganisationUnitsTree.module.css'
-class AvailableOrganisationUnitsTree extends PureComponent {
- static contextTypes = {
- d2: PropTypes.object,
- }
-
- static propTypes = {
- onChange: PropTypes.func,
- multiselect: PropTypes.bool,
- }
-
- static defaultProps = {
- onChange: null,
- multiselect: false,
- }
-
- constructor() {
- super()
-
- this.state = {
- selected: [],
- rootsWithMembers: null,
- }
+const query = {
+ roots: {
+ resource: 'organisationUnits',
+ params: {
+ filter: 'level:eq:1',
+ fields: 'id',
+ paging: 'false',
+ },
+ },
+}
- this.getOrgUnitIdFromPath = this.getOrgUnitIdFromPath.bind(this)
- this.handleOrgUnitClickSingle = this.handleOrgUnitClickSingle.bind(this)
- this.handleOrgUnitClickMulti = this.handleOrgUnitClickMulti.bind(this)
- this.loadAvailableOrgUnits = this.loadAvailableOrgUnits.bind(this)
- }
+const AvailableOrganisationUnitsTree = ({ multiselect = false, onChange }) => {
+ const { loading, data, error } = useDataQuery(query)
+ const [selected, setSelected] = useState(new Map())
- componentDidMount() {
- if (this.state.rootsWithMembers === null) {
- this.loadAvailableOrgUnits()
- .then(organisationUnitsResponse => {
- const organisationUnits = organisationUnitsResponse.toArray()
- this.setState({
- rootsWithMembers: organisationUnits,
- })
- })
- .catch(() => {
- this.manageError()
- })
- }
+ if (loading) {
+ return
}
- async loadAvailableOrgUnits() {
- const d2 = this.context.d2
-
- const orgUnits = await d2.currentUser.getOrganisationUnits({
- fields: 'id,displayName,path,children::isNotEmpty,memberCount',
- paging: false,
- })
-
- if (!orgUnits.size && d2.currentUser.authorities.has('ALL')) {
- // if special all-authority we have access to all orgunits
- return d2.models.organisationUnits.list({
- paging: false,
- level: 1,
- fields: 'id,displayName,path,children::isNotEmpty,memberCount',
- })
- }
-
- return orgUnits
+ if (error) {
+ return (
+
+ {i18n.t(
+ 'Something went wrong whilst loading your organisation units.'
+ )}
+
+ )
}
- getOrgUnitIdFromPath(path) {
- return path.split('/').pop()
+ if (data.roots.organisationUnits.length === 0) {
+ return (
+ {i18n.t('You do not have access to any organisation units.')}
+ )
}
- handleOrgUnitClickSingle(event, orgUnit) {
- if (this.state.selected.includes(orgUnit.path)) {
+ const handleOrgUnitClickSingle = ({ id, path }) => {
+ if (selected.has(path)) {
return
}
- const id = this.getOrgUnitIdFromPath(orgUnit.path)
- this.setState({ selected: [orgUnit.path] })
- this.props.onChange && this.props.onChange(id)
- }
-
- handleOrgUnitClickMulti(event, { path }) {
- const { selected } = this.state
- const paths = selected.includes(path)
- ? selected.filter(selectedPath => selectedPath !== path)
- : [...this.state.selected, path]
-
- this.setState({ selected: paths })
- this.props.onChange &&
- this.props.onChange(paths.map(this.getOrgUnitIdFromPath))
+ setSelected(new Map(selected).set(path, id))
+ if (onChange) {
+ onChange(id)
+ }
}
- render() {
- if (!this.state.rootsWithMembers) {
- return {i18n.t('Updating Organisation Units Tree...')}
+ const handleOrgUnitClickMulti = ({ id, path, selected: s }) => {
+ const newSelected = new Map(selected)
+ if (s.includes(path)) {
+ newSelected.set(path, id)
+ } else {
+ newSelected.delete(path)
}
-
- if (this.state.rootsWithMembers.length < 1) {
- return (
-
- {i18n.t(
- 'You do not have access to any organisation units.'
- )}
-
- )
+ setSelected(newSelected)
+ if (onChange) {
+ onChange([...newSelected.values()])
}
+ }
- const onSelectClick = this.props.multiselect
- ? this.handleOrgUnitClickMulti
- : this.handleOrgUnitClickSingle
+ const handleChange = multiselect
+ ? handleOrgUnitClickMulti
+ : handleOrgUnitClickSingle
+
+ return (
+
+ ou.id)}
+ singleSelection={!multiselect}
+ onChange={handleChange}
+ />
+
+ )
+}
- return (
-
- {this.state.rootsWithMembers.map(rootOrgUnit => (
-
- ))}
-
- )
- }
+AvailableOrganisationUnitsTree.propTypes = {
+ multiselect: PropTypes.bool,
+ onChange: PropTypes.func,
}
export default AvailableOrganisationUnitsTree
diff --git a/src/components/available-organisation-units-tree/AvailableOrganisationUnitsTree.module.css b/src/components/available-organisation-units-tree/AvailableOrganisationUnitsTree.module.css
index 53bc0632..f6b72ff5 100644
--- a/src/components/available-organisation-units-tree/AvailableOrganisationUnitsTree.module.css
+++ b/src/components/available-organisation-units-tree/AvailableOrganisationUnitsTree.module.css
@@ -1,6 +1,4 @@
-.tree {
- border: solid 1px #bcbcbc;
- overflow: auto;
- width: 100%;
- height: 100%;
-}
\ No newline at end of file
+.wrapper {
+ max-height: var(--spacers-dp512);
+ overflow-y: auto;
+}
diff --git a/src/components/available-organisation-units-tree/AvailableOrganisationUnitsTree.test.js b/src/components/available-organisation-units-tree/AvailableOrganisationUnitsTree.test.js
deleted file mode 100644
index 5d1e5f3a..00000000
--- a/src/components/available-organisation-units-tree/AvailableOrganisationUnitsTree.test.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/* eslint-disable */
-import React from 'react'
-import { shallow } from 'enzyme'
-import OrgUnitTree from 'd2-ui/lib/org-unit-tree/OrgUnitTree.component'
-import AvailableOrganisationUnitsTree from './AvailableOrganisationUnitsTree'
-
-jest.mock('d2-ui/lib/org-unit-tree/OrgUnitTree.component', () => 'OrgUnitTree')
-
-const ownShallow = () => {
- const onChange = jest.fn()
- return shallow(, {
- disableLifecycleMethods: true,
- context: {
- d2: {},
- },
- })
-}
-
-it('AvailableOrganisationUnitsTree renders without crashing', () => {
- ownShallow()
-})
-
-it('AvailableOrganisationUnitsTree does not render OrgUnitTree', () => {
- const wrapper = ownShallow()
- expect(wrapper.find(OrgUnitTree)).toHaveLength(0)
-})
-
-it('AvailableOrganisationUnitsTree render no access message when no user-org units', () => {
- const wrapper = ownShallow()
- wrapper.setState({ rootsWithMembers: [] })
- expect(
- wrapper
- .children()
- .contains('You do not have access to any organisation units.')
- ).toBe(true)
-})
-
-it('AvailableOrganisationUnitsTree does render OrgUnitTree', () => {
- const wrapper = ownShallow()
- wrapper.setState({ rootsWithMembers: [{ id: 'someId' }] })
- expect(wrapper.find(OrgUnitTree)).toHaveLength(1)
-})
diff --git a/src/components/download-as/DownloadAs.js b/src/components/download-as/DownloadAs.js
index ee09ecf0..c8fdfee3 100644
--- a/src/components/download-as/DownloadAs.js
+++ b/src/components/download-as/DownloadAs.js
@@ -1,6 +1,6 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
import i18n from '@dhis2/d2-i18n'
+import PropTypes from 'prop-types'
+import React, { PureComponent } from 'react'
import styles from './DownloadAs.module.css'
class DownloadAs extends PureComponent {
@@ -38,18 +38,18 @@ class DownloadAs extends PureComponent {
return (
diff --git a/src/components/formatters/FormattedNumber.js b/src/components/formatters/FormattedNumber.js
index 60dd4b4c..feea9eb2 100644
--- a/src/components/formatters/FormattedNumber.js
+++ b/src/components/formatters/FormattedNumber.js
@@ -1,5 +1,6 @@
-import React from 'react'
+import i18n from '@dhis2/d2-i18n'
import PropTypes from 'prop-types'
+import React from 'react'
const FormattedNumber = ({
value,
@@ -7,7 +8,7 @@ const FormattedNumber = ({
maximumFractionDigits,
}) => (
- {new Intl.NumberFormat(sessionStorage.getItem('uiLocale'), {
+ {new Intl.NumberFormat(i18n.language, {
minimumFractionDigits,
maximumFractionDigits,
}).format(value)}
@@ -15,14 +16,14 @@ const FormattedNumber = ({
)
FormattedNumber.defaultProps = {
- minimumFractionDigits: 2,
maximumFractionDigits: 2,
+ minimumFractionDigits: 2,
}
FormattedNumber.propTypes = {
value: PropTypes.number.isRequired,
- minimumFractionDigits: PropTypes.number,
maximumFractionDigits: PropTypes.number,
+ minimumFractionDigits: PropTypes.number,
}
export default FormattedNumber
diff --git a/src/components/outlier-analysis-table/OutlierAnalysisTable.js b/src/components/outlier-analysis-table/OutlierAnalysisTable.js
index d8f86b50..9e989dec 100644
--- a/src/components/outlier-analysis-table/OutlierAnalysisTable.js
+++ b/src/components/outlier-analysis-table/OutlierAnalysisTable.js
@@ -1,5 +1,5 @@
-import React from 'react'
-import PropTypes from 'prop-types'
+import { useAlert } from '@dhis2/app-runtime'
+import i18n from '@dhis2/d2-i18n'
import classNames from 'classnames'
import {
Checkbox,
@@ -10,12 +10,14 @@ import {
TableRow,
TableRowColumn,
} from 'material-ui'
-import FormattedNumber from '../../components/formatters/FormattedNumber'
+import PropTypes from 'prop-types'
+import React from 'react'
import DownloadAs from '../../components/download-as/DownloadAs'
-import i18n from '@dhis2/d2-i18n'
-import { apiConf } from '../../server.conf'
+import FormattedNumber from '../../components/formatters/FormattedNumber'
+import { Z_SCORE } from '../../pages/outlier-detection/constants'
import cssPageStyles from '../../pages/Page.module.css'
import jsPageStyles from '../../pages/PageStyles'
+import { apiConf } from '../../server.conf'
import styles from './OutlierAnalysisTable.module.css'
const OutlierAnalyisTable = ({
@@ -24,7 +26,17 @@ const OutlierAnalyisTable = ({
toggleCheckbox,
algorithm,
}) => {
- const isZScoreAlgorithm = algorithm === 'Z_SCORE'
+ const successfulMarkAlert = useAlert(
+ ({ marked }) =>
+ marked
+ ? i18n.t('Marked for follow-up')
+ : i18n.t('Unmarked for follow-up'),
+ { success: true }
+ )
+ const errorAlert = useAlert(({ error }) => error.message, {
+ critical: true,
+ })
+ const isZScoreAlgorithm = algorithm === Z_SCORE
const downloadLink = (
)
- // Table Rows
- const rows = elements.map(element => {
- const updateCheckbox = () => {
- toggleCheckbox(element)
+ const tableRows = elements.map(element => {
+ const updateCheckbox = async (event, marked) => {
+ try {
+ await toggleCheckbox(element)
+ successfulMarkAlert.show({ marked })
+ } catch (error) {
+ errorAlert.show({ error })
+ }
}
return (
- {element.displayName}
+
+
+ {element.displayName}
+
+
{element.pe}
- {element.ouName}
+
+ {element.ouName}
+
- {rows}
+ {tableRows}
- `${e.attributeOptionComboId}-${e.categoryOptionComboId}-${e.periodId}-${
- e.sourceId
- }-${e.dataElementId}`
+ `${e.attributeOptionComboId}-${e.categoryOptionComboId}-${e.periodId}-${e.sourceId}-${e.dataElementId}`
OutlierAnalyisTable.convertElementFromApiResponse = e => ({
displayName: getDisplayName(e),
@@ -195,10 +215,10 @@ OutlierAnalyisTable.convertElementToToggleFollowupRequest = e => ({
})
OutlierAnalyisTable.propTypes = {
- algorithm: PropTypes.oneOf(['Z_SCORE', 'MIN_MAX']),
csvQueryStr: PropTypes.string.isRequired,
elements: PropTypes.array.isRequired,
toggleCheckbox: PropTypes.func.isRequired,
+ algorithm: PropTypes.oneOf([Z_SCORE, 'MIN_MAX']),
}
OutlierAnalyisTable.contextTypes = {
diff --git a/src/components/outlier-analysis-table/OutlierAnalysisTable.test.js b/src/components/outlier-analysis-table/OutlierAnalysisTable.test.js
deleted file mode 100644
index d54cf5db..00000000
--- a/src/components/outlier-analysis-table/OutlierAnalysisTable.test.js
+++ /dev/null
@@ -1,162 +0,0 @@
-/* eslint-disable */
-import React from 'react'
-import { shallow } from 'enzyme'
-import OutlierAnalysisTable from './OutlierAnalysisTable'
-import { Checkbox, TableRow, TableRowColumn } from 'material-ui'
-import DownloadAs from '../download-as/DownloadAs'
-
-jest.mock(
- 'd2-ui/lib/feedback-snackbar/FeedbackSnackbarTypes',
- () => 'FeedbackSnackbarTypes'
-)
-jest.mock('d2-ui/lib/org-unit-tree/OrgUnitTree.component', () => 'OrgUnitTree')
-
-const response = [
- {
- absDev: 7512.666666666667,
- aoc: 'HllvX50cXC0',
- aocName: 'default',
- coc: 'HllvX50cXC0',
- cocName: 'default',
- de: 'p1MDHOT6ENy',
- deName: 'Stock PHU dispensed BCG',
- followup: false,
- lowerBound: -6093.141742038514,
- mean: 702.3333333333334,
- ou: 'ApLCxUmnT6q',
- ouName: 'Maborie MCHP',
- pe: '202010',
- stdDev: 2265.1583584572822,
- upperBound: 7497.80840870518,
- value: 8215,
- zScore: 3.31661874262216,
- },
- {
- absDev: 5538.909090909091,
- aoc: 'HllvX50cXC0',
- aocName: 'default',
- coc: 'pq2XI5kz2BY',
- cocName: 'Fixed',
- de: 'rbkr8PL0rwM',
- deName: 'Iron Folate given at ANC 3rd',
- followup: false,
- lowerBound: -4454.381991130682,
- mean: 887.0909090909091,
- ou: 'el8sgzyHuEe',
- ouName: 'Rosint Buya MCHP',
- pe: '202010',
- stdDev: 1780.4909667405302,
- upperBound: 6228.5638093125,
- value: 6426,
- zScore: 3.1108886225067116,
- },
-]
-
-const testElements = response.map(
- OutlierAnalysisTable.convertElementFromApiResponse
-)
-
-const expectedElementFormat = {
- absDev: 7512.666666666667,
- aoc: 'HllvX50cXC0',
- aocName: 'default',
- coc: 'HllvX50cXC0',
- cocName: 'default',
- de: 'p1MDHOT6ENy',
- deName: 'Stock PHU dispensed BCG',
- displayName: 'Stock PHU dispensed BCG',
- followup: false,
- key: 'HllvX50cXC0-HllvX50cXC0-p1MDHOT6ENy-202010-ApLCxUmnT6q',
- lowerBound: -6093.141742038514,
- marked: false,
- mean: 702.3333333333334,
- ou: 'ApLCxUmnT6q',
- ouName: 'Maborie MCHP',
- pe: '202010',
- stdDev: 2265.1583584572822,
- upperBound: 7497.80840870518,
- value: 8215,
- zScore: 3.31661874262216,
-}
-
-const expectedToggleFollowupRequest = {
- attributeOptionComboId: 4,
- categoryOptionComboId: 4,
- dataElementId: 360075,
- followup: true,
- organisationUnitId: 642,
- periodId: 475192,
-}
-
-const ownShallow = () => {
- return shallow(
-
,
- {
- disableLifecycleMethods: true,
- }
- )
-}
-
-describe('Test
rendering:', () => {
- let wrapper
- beforeEach(() => {
- wrapper = ownShallow()
- })
-
- it('OutlierAnalysisTable renders without crashing.', () => {
- ownShallow()
- })
-
- it('Should render correct number of rows.', () => {
- expect(wrapper.find(TableRow).length).toBe(3) // Two elements plus header row
- })
-
- it('Should render correct number of columns.', () => {
- expect(
- wrapper
- .find(TableRow)
- .at(1)
- .find(TableRowColumn).length
- ).toBe(8) // First row after header
- })
-
- // it('Should render a Mark "Checkbox" for each element.', () => {
- // expect(wrapper.find(Checkbox).length).toBe(2)
- // })
-
- it('Render "DownloadAs" components.', () => {
- expect(wrapper.find(DownloadAs).length).toBe(2)
- })
-})
-
-describe('Test
actions:', () => {
- it('Should correctly convert elements from API response.', () => {
- const element = OutlierAnalysisTable.convertElementFromApiResponse(
- response[0]
- )
- expect(element).toEqual(expectedElementFormat)
- })
-
- // it('Should correctly convert elements to Mark request.', () => {
- // const element = OutlierAnalysisTable.convertElementToToggleFollowupRequest(
- // testElements[0]
- // )
- // expect(element).toEqual(expectedToggleFollowupRequest)
- // })
-
- it('Should correctly generateElementKey.', () => {
- const element = OutlierAnalysisTable.generateElementKey(response[0])
- const responseElement = response[0]
- expect(element).toBe(
- `${responseElement.attributeOptionComboId}-` +
- `${responseElement.categoryOptionComboId}-` +
- `${responseElement.periodId}-` +
- `${responseElement.sourceId}-` +
- `${responseElement.dataElementId}`
- )
- })
-})
diff --git a/src/components/page-helper/PageHelper.js b/src/components/page-helper/PageHelper.js
index b5581dc7..6ff325aa 100644
--- a/src/components/page-helper/PageHelper.js
+++ b/src/components/page-helper/PageHelper.js
@@ -1,7 +1,7 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import { IconButton } from 'material-ui'
import i18n from '@dhis2/d2-i18n'
+import { IconButton } from 'material-ui'
+import PropTypes from 'prop-types'
+import React, { PureComponent } from 'react'
import {
getDocsVersion,
DOCS_LINK,
@@ -10,8 +10,8 @@ import {
class PageHelper extends PureComponent {
static propTypes = {
- lng: PropTypes.string,
sectionDocsKey: PropTypes.string.isRequired,
+ lng: PropTypes.string,
}
static defaultProps = {
diff --git a/src/components/page-helper/PageHelper.test.js b/src/components/page-helper/PageHelper.test.js
deleted file mode 100644
index 9916fc5b..00000000
--- a/src/components/page-helper/PageHelper.test.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/* eslint-disable */
-import React from 'react'
-import { shallow } from 'enzyme'
-import PageHelper from './PageHelper'
-import { IconButton } from 'material-ui'
-import {
- VALIDATION_RULES_ANALYSIS_SECTION_KEY,
- getDocsKeyForSection,
-} from '../../pages/sections.conf'
-
-jest.mock('d2-ui/lib/org-unit-tree/OrgUnitTree.component', () => 'OrgUnitTree')
-jest.mock(
- 'd2-ui/lib/org-unit-select/OrgUnitSelectByLevel.component',
- () => 'OrgUnitSelectByLevel'
-)
-jest.mock(
- 'd2-ui/lib/org-unit-select/OrgUnitSelectByGroup.component',
- () => 'OrgUnitSelectByGroup'
-)
-jest.mock(
- 'd2-ui/lib/org-unit-select/OrgUnitSelectAll.component',
- () => 'OrgUnitSelectAll'
-)
-jest.mock('d2-ui/lib/select-field/SelectField', () => 'SelectField')
-jest.mock(
- 'd2-ui/lib/period-picker/PeriodPicker.component',
- () => 'PeriodPicker'
-)
-jest.mock('d2-ui/lib/data-table/DataTable.component', () => 'DataTable')
-jest.mock('d2-ui/lib/pagination/Pagination.component', () => 'Pagination')
-jest.mock(
- 'd2-ui/lib/feedback-snackbar/FeedbackSnackbarTypes',
- () => 'FeedbackSnackbarTypes'
-)
-
-const ownShallow = () => {
- return shallow(
-
,
- {
- context: {
- d2: {
- system: {
- version: {
- snapshot: true,
- },
- },
- },
- },
- }
- )
-}
-
-it('Page Helper renders without crashing.', () => {
- ownShallow()
-})
-
-it('Page Helper should have an icon button.', () => {
- const wrapper = ownShallow()
- expect(wrapper.find(IconButton)).toHaveLength(1)
-})
diff --git a/src/components/table/TableCellContent.js b/src/components/table/TableCellContent.js
index f02c795a..15d50aa9 100644
--- a/src/components/table/TableCellContent.js
+++ b/src/components/table/TableCellContent.js
@@ -1,19 +1,21 @@
-import React from 'react'
+import cx from 'classnames'
import PropTypes from 'prop-types'
+import React from 'react'
import styles from './TableCellContent.module.css'
-import cx from 'classnames'
-const TableCellContent = props => (
+const TableCellContent = ({ className, size, children }) => (
- {props.children}
+ {children}
)
TableCellContent.propTypes = {
+ children: PropTypes.any,
+ className: PropTypes.string,
size: PropTypes.oneOf(['narrow', 'medium', 'wide']),
}
diff --git a/src/components/table/TableCellContent.module.css b/src/components/table/TableCellContent.module.css
index fbd725e2..9d824277 100644
--- a/src/components/table/TableCellContent.module.css
+++ b/src/components/table/TableCellContent.module.css
@@ -2,10 +2,6 @@
max-height: 64px;
margin: 5px 0 5px 0;
display: -webkit-box;
- -webkit-line-clamp: 3;
- /* autoprefixer: off */
- -webkit-box-orient: vertical;
- /* autoprefixer: on */
overflow: hidden;
white-space: pre-wrap;
text-overflow: ellipsis;
diff --git a/src/components/validation-rule-groups-select/ValidationRuleGroupsSelect.js b/src/components/validation-rule-groups-select/ValidationRuleGroupsSelect.js
index 7736f987..924bc4be 100644
--- a/src/components/validation-rule-groups-select/ValidationRuleGroupsSelect.js
+++ b/src/components/validation-rule-groups-select/ValidationRuleGroupsSelect.js
@@ -1,8 +1,8 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import SelectField from 'material-ui/SelectField'
-import MenuItem from 'material-ui/MenuItem'
import i18n from '@dhis2/d2-i18n'
+import MenuItem from 'material-ui/MenuItem'
+import SelectField from 'material-ui/SelectField'
+import PropTypes from 'prop-types'
+import React, { PureComponent } from 'react'
export const ALL_VALIDATION_RULE_GROUPS_ID = -1
export const ALL_VALIDATION_RULE_GROUPS_OPTION = {
@@ -12,8 +12,8 @@ export const ALL_VALIDATION_RULE_GROUPS_OPTION = {
class ValidationRuleGroupsSelect extends PureComponent {
static propTypes = {
- style: PropTypes.object,
onChange: PropTypes.func.isRequired,
+ style: PropTypes.object,
}
static defaultProps = {
diff --git a/src/components/validation-rule-groups-select/ValidationRuleGroupsSelect.test.js b/src/components/validation-rule-groups-select/ValidationRuleGroupsSelect.test.js
deleted file mode 100644
index 46cb1ec7..00000000
--- a/src/components/validation-rule-groups-select/ValidationRuleGroupsSelect.test.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/* eslint-disable */
-import React from 'react'
-import { shallow } from 'enzyme'
-import SelectField from 'material-ui/SelectField'
-import MenuItem from 'material-ui/MenuItem'
-import ValidationRuleGroupsSelect from './ValidationRuleGroupsSelect'
-
-jest.mock('d2-ui/lib/org-unit-tree/OrgUnitTree.component', () => 'OrgUnitTree')
-
-const ownShallow = () => {
- const onChange = jest.fn()
- return shallow(
, {
- disableLifecycleMethods: true,
- context: {
- d2: {},
- },
- })
-}
-
-it('ValidationRuleGroupsSelect renders without crashing', () => {
- ownShallow()
-})
-
-it('ValidationRuleGroupsSelect renders SelectField component', () => {
- const wrapper = ownShallow()
- expect(wrapper.find(SelectField)).toHaveLength(1)
-})
-
-it('ValidationRuleGroupsSelect renders Select All option', () => {
- const wrapper = ownShallow()
- expect(wrapper.find(MenuItem)).toHaveLength(1)
-})
-
-it('ValidationRuleGroupsSelect renders correct number of select options', () => {
- const wrapper = ownShallow()
- const fakeValidationRuleGroups = [
- {
- id: 'validation rule group 1',
- displayName: 'validation rule group 1',
- },
- {
- id: 'validation rule group 2',
- displayName: 'validation rule group 2',
- },
- ]
- wrapper.setState({ validationRuleGroups: fakeValidationRuleGroups })
- expect(wrapper.find(MenuItem)).toHaveLength(fakeValidationRuleGroups.length)
-})
diff --git a/src/configI18n.js b/src/configI18n.js
deleted file mode 100644
index 07b050c5..00000000
--- a/src/configI18n.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import i18n from '@dhis2/d2-i18n'
-import moment from 'moment'
-
-const isLangRTL = code => {
- const langs = ['ar', 'fa', 'ur']
- const prefixed = langs.map(c => `${c}-`)
-
- return (
- langs.includes(code) ||
- prefixed.filter(c => code.startsWith(c)).length > 0
- )
-}
-
-export const configI18n = userSettings => {
- const lang = userSettings.keyUiLocale
-
- if (isLangRTL(lang)) {
- document.body.setAttribute('dir', 'rtl')
- }
- i18n.changeLanguage(lang)
- moment.locale(lang)
-}
-
-export const injectTranslationsToD2 = d2 => {
- if (d2) {
- const translations = {
- settings: 'Settings',
- app_search_placeholder: 'Search apps',
- profile: 'Profile',
- account: 'Account',
- help: 'Help',
- log_out: 'Log out',
- about_dhis2: 'About DHIS 2',
- manage_my_apps: 'Manage my apps',
- no_results_found: 'No results found',
- interpretations: 'Interpretations',
- messages: 'Messages',
- }
- const translationKeys = Object.keys(translations)
-
- translationKeys.forEach(key => {
- translations[key] = i18n.t(translations[key])
- })
-
- Object.assign(d2.i18n.translations, translations)
- }
-}
-
-export default configI18n
diff --git a/src/custom-css/D2UISidebarOverrides.css b/src/custom-css/D2UISidebarOverrides.css
deleted file mode 100644
index 9937f1d4..00000000
--- a/src/custom-css/D2UISidebarOverrides.css
+++ /dev/null
@@ -1,7 +0,0 @@
-.left-bar {
- position: fixed !important;
- bottom: 0 !important;
- top: 0 !important;
- left: 0 !important;
- margin-top: 3rem !important;
-}
diff --git a/src/grid.css b/src/grid.css
new file mode 100644
index 00000000..07797d3a
--- /dev/null
+++ b/src/grid.css
@@ -0,0 +1,37 @@
+.row {
+ display: flex;
+ flex-wrap: wrap;
+ max-width: 1000px;
+}
+
+.row > [class^="col-"] {
+ padding: 0.5em;
+}
+
+.col-sm-12 {
+ width: 100%;
+}
+
+@media (min-width: 992px) {
+ .row > [class^="col-md-"] {
+ flex: 0 0 auto;
+ }
+
+ .col-md-4 {
+ width: 33.3333333333%;
+ }
+
+ .col-md-6 {
+ width: 50%;
+ }
+}
+
+@media (min-width: 1200px) {
+ .row > [class^="col-lg-"] {
+ flex: 0 0 auto;
+ }
+
+ .col-lg-4 {
+ width: 33.3333333333%;
+ }
+}
diff --git a/src/index.css b/src/index.css
deleted file mode 100644
index 51ddb990..00000000
--- a/src/index.css
+++ /dev/null
@@ -1,17 +0,0 @@
-html {
- background: #f3f3f3;
- font-family: 'Roboto', sans-serif;
-}
-
-body {
- margin: 0;
- padding: 0;
-}
-
-h1 {
- font-family: 'Roboto', sans-serif;
- font-size: 24px;
- font-weight: 300;
- letter-spacing: 1.2px;
- color: rgba(0, 0, 0, 0.87);
-}
diff --git a/src/index.js b/src/index.js
deleted file mode 100644
index 00d7e2b6..00000000
--- a/src/index.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import React from 'react'
-import ReactDOM from 'react-dom'
-import { HashRouter, withRouter } from 'react-router-dom'
-import { getUserSettings, init } from 'd2/lib/d2'
-import log from 'loglevel'
-import { configI18n, injectTranslationsToD2 } from './configI18n'
-import './index.css'
-import App from './App'
-import appTheme from './theme'
-import * as serviceWorker from './serviceWorker'
-
-import { Provider } from '@dhis2/app-runtime'
-import { CssReset } from '@dhis2/ui-core'
-import { MuiThemeProvider } from 'material-ui/styles'
-
-log.setLevel(
- process.env.NODE_ENV === 'production' ? log.levels.INFO : log.levels.DEBUG
-)
-
-const AppComponent = withRouter(App)
-const baseUrl = `${process.env.REACT_APP_DHIS2_BASE_URL}/api/${
- process.env.REACT_APP_DHIS2_API_VERSION
-}`
-const schemas = ['organisationUnit', 'dataSet', 'validationRuleGroup']
-
-const config = {
- baseUrl: process.env.REACT_APP_DHIS2_BASE_URL,
- apiVersion: process.env.REACT_APP_DHIS2_API_VERSION,
-}
-
-const render = d2 =>
- ReactDOM.render(
-
-
-
-
- ,
- document.getElementById('root')
- )
-
-const start = async () => {
- const d2 = await init({ baseUrl, schemas })
- const userSettings = await getUserSettings()
- const uiLocale = userSettings.keyUiLocale
-
- sessionStorage.setItem('uiLocale', uiLocale || 'en')
- injectTranslationsToD2(d2)
- configI18n(userSettings)
- render(d2)
-}
-
-start()
-serviceWorker.unregister()
diff --git a/src/pages/Page.js b/src/pages/Page.js
index 1fee569c..89f2c9da 100644
--- a/src/pages/Page.js
+++ b/src/pages/Page.js
@@ -1,7 +1,5 @@
-import { Component } from 'react'
import PropTypes from 'prop-types'
-import { ERROR } from 'd2-ui/lib/feedback-snackbar/FeedbackSnackbarTypes'
-import i18n from '@dhis2/d2-i18n'
+import { Component } from 'react'
class Page extends Component {
static propTypes = {
@@ -14,7 +12,7 @@ class Page extends Component {
updateAppState: PropTypes.func,
}
- componentWillMount() {
+ UNSAFE_componentWillMount() {
this.pageMounted = true
// update section on side bar
@@ -34,23 +32,7 @@ class Page extends Component {
}
manageError(error) {
- if (this.isPageMounted()) {
- const messageError =
- error && error.message
- ? error.message
- : i18n.t('An unexpected error happened during analysis')
-
- this.context.updateAppState({
- showSnackbar: true,
- snackbarConf: {
- type: ERROR,
- message: messageError,
- },
- pageState: {
- loading: false,
- },
- })
- }
+ console.error(error)
}
}
diff --git a/src/pages/Page.module.css b/src/pages/Page.module.css
index 219a580d..965b5880 100644
--- a/src/pages/Page.module.css
+++ b/src/pages/Page.module.css
@@ -1,10 +1,12 @@
-.section {
- min-height: 280px;
- max-height: 598px;
+.card {
+ padding: var(--spacers-dp16);
}
.mainButton {
- margin-top: 48px;
+ min-width: 100px;
+ margin-top: 36px;
+ /* align with grid padding */
+ margin-left: 0.5em;
}
.appTable th {
@@ -15,7 +17,7 @@
}
.appTable tr {
- color: #000000 !important;
+ color: black !important;
border-bottom: 1px solid #e0e0e0 !important;
}
@@ -40,6 +42,12 @@
.pageHeader {
display: flex;
align-items: center;
+ margin-top: var(--spacers-dp12);
+ margin-bottom: var(--spacers-dp12);
+}
+
+.pageHeader h1 {
+ margin: 0;
}
.cardHeader {
@@ -65,11 +73,13 @@
}
.formLabel {
+ margin-top: 0;
+ margin-bottom: 5px;
font-family: Roboto;
font-size: 14px;
+ font-weight: 500;
text-align: left;
- color: #757575;
- margin-bottom: 5px;
+ color: var(--colors-grey600);
}
.checkboxWrapper {
diff --git a/src/pages/Page.test.js b/src/pages/Page.test.js
deleted file mode 100644
index 0d822c0c..00000000
--- a/src/pages/Page.test.js
+++ /dev/null
@@ -1,70 +0,0 @@
-/* eslint-disable */
-import React from 'react'
-import { shallow } from 'enzyme'
-import Page from './Page'
-import ValidationRulesAnalysis from './validation-rules-analysis/ValidationRulesAnalysis'
-import Home from './home/Home'
-import { VALIDATION_RULES_ANALYSIS_SECTION_KEY } from './sections.conf'
-
-jest.mock('d2-ui/lib/org-unit-tree/OrgUnitTree.component', () => 'OrgUnitTree')
-jest.mock(
- 'd2-ui/lib/feedback-snackbar/FeedbackSnackbarTypes',
- () => 'FeedbackSnackbarTypes'
-)
-jest.mock('./Page') // Page is now a mock constructor
-jest.mock('material-ui/RaisedButton')
-
-const notPageComponentShallow = () => {
- return shallow(
)
-}
-
-const pageComponentShallow = () => {
- return shallow(
-
,
- {
- context: {
- updateAppState: jest.fn(),
- d2: {
- Api: {
- getApi: jest.fn(),
- },
- },
- },
- }
- )
-}
-
-beforeEach(() => {
- // Clear all instances and calls to constructor and all methods:
- Page.mockClear()
-})
-
-it('Page constructor is called', () => {
- const page = new Page()
- expect(Page).toHaveBeenCalled()
-})
-
-it('Not Page component does not call page constructor', () => {
- notPageComponentShallow()
- expect(Page).toHaveBeenCalledTimes(0)
-})
-
-it('Page component calls page constructor', () => {
- pageComponentShallow()
- expect(Page).toHaveBeenCalledTimes(1)
-})
-
-it('componentWillMount was called after render', () => {
- const spy = spyOn(Page.prototype, 'componentWillMount')
- pageComponentShallow()
- expect(spy).toHaveBeenCalled()
-})
-
-it('componentWillUnmount was called after unmount', () => {
- const spy = spyOn(Page.prototype, 'componentWillUnmount')
- const wrapper = pageComponentShallow()
- wrapper.unmount()
- expect(spy).toHaveBeenCalled()
-})
diff --git a/src/pages/follow-up-analysis/FollowUpAnalysis.js b/src/pages/follow-up-analysis/FollowUpAnalysis.js
index 866eca67..41690569 100644
--- a/src/pages/follow-up-analysis/FollowUpAnalysis.js
+++ b/src/pages/follow-up-analysis/FollowUpAnalysis.js
@@ -1,23 +1,17 @@
-import React from 'react'
+import i18n from '@dhis2/d2-i18n'
+import { Card } from '@dhis2/ui'
import { FontIcon, IconButton } from 'material-ui'
-import { Card, CardText } from 'material-ui/Card'
-import RaisedButton from 'material-ui/RaisedButton'
-import DatePicker from 'material-ui/DatePicker'
-import { SUCCESS } from 'd2-ui/lib/feedback-snackbar/FeedbackSnackbarTypes'
-import classNames from 'classnames'
-import Page from '../Page'
-import AvailableDatasetsSelect from '../../components/available-datasets-select/AvailableDatasetsSelect'
-import AvailableOrganisationUnitsTree from '../../components/available-organisation-units-tree/AvailableOrganisationUnitsTree'
-import PageHelper from '../../components/page-helper/PageHelper'
-import FollowUpAnalysisTable from './follow-up-analysis-table/FollowUpAnalysisTable'
+import React from 'react'
import AlertBar from '../../components/alert-bar/AlertBar'
-import i18n from '@dhis2/d2-i18n'
+import PageHelper from '../../components/page-helper/PageHelper'
import { convertDateToApiDateFormat } from '../../helpers/dates'
-import { getDocsKeyForSection } from '../sections.conf'
+import threeMonthsAgo from '../../helpers/threeMonthsAgo'
import { apiConf } from '../../server.conf'
+import Page from '../Page'
import cssPageStyles from '../Page.module.css'
-import jsPageStyles from '../PageStyles'
-import threeMonthsAgo from '../../helpers/threeMonthsAgo'
+import { getDocsKeyForSection } from '../sections.conf'
+import FollowUpAnalysisTable from './follow-up-analysis-table/FollowUpAnalysisTable'
+import Form from './Form'
class FollowUpAnalysis extends Page {
static STATE_PROPERTIES = [
@@ -42,174 +36,124 @@ class FollowUpAnalysis extends Page {
elements: [],
loading: false,
}
-
- this.getFollowUpList = this.getFollowUpList.bind(this)
- this.back = this.back.bind(this)
-
- this.startDateOnChange = this.startDateOnChange.bind(this)
- this.endDateOnChange = this.endDateOnChange.bind(this)
- this.organisationUnitOnChange = this.organisationUnitOnChange.bind(this)
- this.dataSetsOnChange = this.dataSetsOnChange.bind(this)
- this.toggleCheckbox = this.toggleCheckbox.bind(this)
- this.unfollow = this.unfollow.bind(this)
}
- componentWillReceiveProps(nextProps) {
+ UNSAFE_componentWillReceiveProps(nextProps) {
const nextState = {}
Object.keys(nextProps).forEach(property => {
- if (
- nextProps.hasOwnProperty(property) &&
- FollowUpAnalysis.STATE_PROPERTIES.includes(property)
- ) {
+ if (FollowUpAnalysis.STATE_PROPERTIES.includes(property)) {
nextState[property] = nextProps[property]
}
})
- if (nextState !== {}) {
- this.setState(nextState)
- }
+ this.setState(nextState)
}
- getFollowUpList() {
- const api = this.context.d2.Api.getApi()
- if (this.isFormValid()) {
- this.context.updateAppState({
- pageState: {
- loading: true,
- },
- })
-
- const request = {
- startDate: convertDateToApiDateFormat(this.state.startDate),
- endDate: convertDateToApiDateFormat(this.state.endDate),
- ou: this.state.organisationUnitId,
- ds: this.state.dataSetIds,
- }
-
- api.post(apiConf.endpoints.folloupAnalysis, request)
- .then(response => {
- if (this.isPageMounted()) {
- const elements = response.map(
- FollowUpAnalysisTable.convertElementFromApiResponse
- )
-
- const feedback =
- elements && elements.length > 0
- ? {
- showSnackbar: false,
- }
- : {
- showSnackbar: true,
- snackbarConf: {
- type: SUCCESS,
- message: i18n.t('No values found'),
- },
- }
+ getFollowUpList = async () => {
+ this.context.updateAppState({
+ pageState: {
+ loading: true,
+ },
+ })
- this.context.updateAppState({
- ...feedback,
- pageState: {
- loading: false,
- elements,
- showTable: elements && elements.length > 0,
- },
- })
- }
- })
- .catch(() => {
- this.manageError()
- })
+ const api = this.context.d2.Api.getApi()
+ const request = {
+ startDate: convertDateToApiDateFormat(this.state.startDate),
+ endDate: convertDateToApiDateFormat(this.state.endDate),
+ ou: this.state.organisationUnitId,
+ ds: this.state.dataSetIds,
+ }
+ const response = await api.post(
+ apiConf.endpoints.folloupAnalysis,
+ request
+ )
+ if (!this.isPageMounted()) {
+ return
}
+
+ const elements = response.map(
+ FollowUpAnalysisTable.convertElementFromApiResponse
+ )
+ this.context.updateAppState({
+ pageState: {
+ loading: false,
+ elements,
+ showTable: elements && elements.length > 0,
+ },
+ })
+ return elements.length === 0 ? 'NO_VALUES_FOUND' : null
}
- back() {
+ back = () => {
this.setState({ showTable: false })
this.context.updateAppState({
pageState: { showTable: false },
})
}
- startDateOnChange(event, date) {
+ startDateOnChange = (event, date) => {
this.setState({ startDate: new Date(date) })
}
- endDateOnChange(event, date) {
+ endDateOnChange = (event, date) => {
this.setState({ endDate: new Date(date) })
}
- organisationUnitOnChange(organisationUnitId) {
+ handleOrganisationUnitChange = organisationUnitId => {
this.setState({ organisationUnitId })
}
- dataSetsOnChange(event) {
- const dataSetIds = []
- const selectedOptions = event.target.selectedOptions
- for (let i = 0; i < selectedOptions.length; i++) {
- dataSetIds.push(selectedOptions[i].value)
- }
- this.setState({ dataSetIds })
+ handleDataSetsChange = ({ selected }) => {
+ this.setState({ dataSetIds: selected })
}
- toggleCheckbox(element) {
- const elements = this.state.elements
- for (let i = 0; i < elements.length; i++) {
- const currentElement = elements[i]
- if (currentElement.key === element.key) {
- currentElement.marked = !currentElement.marked
- elements[i] = currentElement
- this.setState({ elements })
- break
- }
- }
+ toggleCheckbox = element => {
+ this.setState({
+ elements: this.state.elements.map(e => {
+ if (e.key === element.key) {
+ return {
+ ...e,
+ marked: !e.marked,
+ }
+ }
+ return e
+ }),
+ })
}
- unfollow(unfollowups) {
- const api = this.context.d2.Api.getApi()
+ unfollow = async unfollowups => {
this.context.updateAppState({
pageState: {
loading: true,
},
})
- api.post(apiConf.endpoints.markDataValue, {
+ const api = this.context.d2.Api.getApi()
+ await api.post(apiConf.endpoints.markFollowUpDataValue, {
followups: unfollowups,
})
- .then(() => {
- if (this.isPageMounted()) {
- // remove unfollowed elements
- const elements = this.state.elements.filter(element => {
- for (let j = 0; j < unfollowups.length; j++) {
- const unfollow = unfollowups[j]
- if (
- FollowUpAnalysisTable.areElementsTheSame(
- element,
- unfollow
- )
- ) {
- return false
- }
- }
-
- return true
- })
+ if (!this.isPageMounted()) {
+ return
+ }
- this.context.updateAppState({
- showSnackbar: true,
- snackbarConf: {
- type: SUCCESS,
- message: i18n.t('Unfollow done'),
- },
- pageState: {
- loading: false,
- elements,
- },
- })
+ // remove unfollowed elements
+ const elements = this.state.elements.filter(element => {
+ for (const unfollow of unfollowups) {
+ if (
+ FollowUpAnalysisTable.areElementsTheSame(element, unfollow)
+ ) {
+ return false
}
- })
- .catch(() => {
- this.manageError()
- })
+ }
+ return true
+ })
+ this.context.updateAppState({
+ pageState: {
+ loading: false,
+ elements,
+ },
+ })
}
isFormValid() {
@@ -237,7 +181,7 @@ class FollowUpAnalysis extends Page {
render() {
return (
-
+
- {i18n.t('Follow-Up Analysis')}
+ {i18n.t('Follow-Up Analysis')}
-
+
-
- {/* FORM: hidden using style to avoid not needed api requests when going back from table */}
-
-
-
-
- {i18n.t('Data Set')}
-
-
-
-
-
- {i18n.t('Parent organisation unit')}
-
-
-
-
-
-
-
-
-
+ {!this.state.showTable && (
+
-
- {/* TABLE */}
-
+ )}
+ {this.state.showTable && (
-
+ )}
)
diff --git a/src/pages/follow-up-analysis/FollowUpAnalysis.test.js b/src/pages/follow-up-analysis/FollowUpAnalysis.test.js
deleted file mode 100644
index a5d9fce9..00000000
--- a/src/pages/follow-up-analysis/FollowUpAnalysis.test.js
+++ /dev/null
@@ -1,291 +0,0 @@
-/* eslint-disable */
-import React from 'react'
-import { shallow } from 'enzyme'
-import { RaisedButton, IconButton } from 'material-ui'
-import DatePicker from 'material-ui/DatePicker'
-import PageHelper from '../../components/page-helper/PageHelper'
-import AlertBar from '../../components/alert-bar/AlertBar'
-import FollowUpAnalysis from './FollowUpAnalysis'
-import FollowUpAnalysisTable from './follow-up-analysis-table/FollowUpAnalysisTable'
-import AvailableOrganisationUnitsTree from '../../components/available-organisation-units-tree/AvailableOrganisationUnitsTree'
-import AvailableDatasetsSelect from '../../components/available-datasets-select/AvailableDatasetsSelect'
-import { sections, FOLLOW_UP_ANALYSIS_SECTION_KEY } from '../sections.conf'
-
-let pageInfo = {}
-for (let i = 0; i < sections.length; i++) {
- const section = sections[i]
- if (section.key === FOLLOW_UP_ANALYSIS_SECTION_KEY) {
- pageInfo = section.info
- break
- }
-}
-
-const ownShallow = () => {
- return shallow(
-
,
- {
- context: {
- updateAppState: jest.fn(),
- },
- disableLifecycleMethods: true,
- }
- )
-}
-
-jest.mock(
- 'd2-ui/lib/feedback-snackbar/FeedbackSnackbarTypes',
- () => 'FeedbackSnackbarTypes'
-)
-jest.mock('d2-ui/lib/org-unit-tree/OrgUnitTree.component', () => 'OrgUnitTree')
-
-describe('Test
rendering:', () => {
- let wrapper
- beforeEach(() => {
- wrapper = ownShallow()
- })
-
- it('Followup Analysis renders without crashing', () => {
- ownShallow()
- })
-
- it('Should show correct title.', () => {
- expect(wrapper.find('h1')).toHaveLength(1)
- expect(wrapper.find('h1').text()).toBe(
- `
Follow-Up Analysis
`
- )
- })
-
- it('Followup Analysis renders an IconButton', () => {
- expect(wrapper.find(IconButton)).toHaveLength(1)
- })
-
- it('Should have an "AlertBar" component.', () => {
- expect(wrapper.find(AlertBar)).toHaveLength(1)
- })
-
- it('Should have an "PageHelper" component.', () => {
- expect(wrapper.find(PageHelper)).toHaveLength(1)
- })
-
- it('Should render an "AvailableDatasetsSelect" component.', () => {
- expect(wrapper.find(AvailableDatasetsSelect)).toHaveLength(1)
- })
-
- it('Should render an "AvailableOrganisationUnitsTree" component.', () => {
- expect(wrapper.find(AvailableOrganisationUnitsTree)).toHaveLength(1)
- })
-
- it('Renders a "Start Date" - DatePicker.', () => {
- expect(wrapper.find(DatePicker).length).toBe(2)
- expect(
- wrapper
- .find(DatePicker)
- .at(0)
- .props().floatingLabelText
- ).toBe('Start Date')
- })
-
- it('Renders a "End Date" - DatePicker.', () => {
- expect(wrapper.find(DatePicker).length).toBe(2)
- expect(
- wrapper
- .find(DatePicker)
- .at(1)
- .props().floatingLabelText
- ).toBe('End Date')
- })
-
- it('Should not render a "FollowUpAnalysisTable" component when has no elements.', () => {
- const elements = []
- wrapper.setState({
- elements,
- showTable: elements && elements.length > 0,
- })
- expect(wrapper.find(IconButton)).toHaveLength(1)
- expect(wrapper.find(IconButton).props().style.display).toBe('none')
- expect(wrapper.find(FollowUpAnalysisTable)).toHaveLength(1)
- expect(
- wrapper
- .find(FollowUpAnalysisTable)
- .parent()
- .props().style.display
- ).toBe('none')
- expect(wrapper.state('showTable')).toBeFalsy()
- })
-
- it('Should show "FollowUpAnalysisTable" component and back icon when has elements.', () => {
- const elements = ['one', 'two', 'three']
- wrapper.setState({
- elements,
- showTable: elements && elements.length > 0,
- })
- expect(wrapper.find(IconButton)).toHaveLength(1)
- expect(wrapper.find(IconButton).props().style.display).toBe('inline')
- expect(wrapper.find(FollowUpAnalysisTable)).toHaveLength(1)
- expect(
- wrapper
- .find(FollowUpAnalysisTable)
- .parent()
- .props().style.display
- ).toBe('block')
- expect(wrapper.state('showTable')).toBeTruthy()
- })
-
- it('Renders a disabled "Start" RaisedButton when loading info.', () => {
- wrapper.setState({
- loading: true,
- endDate: new Date(),
- startDate: new Date(),
- organisationUnitId: 'TestOrganisationUnitId',
- dataSetIds: ['id1', 'id2', 'id3'],
- })
- expect(wrapper.find(RaisedButton)).toHaveLength(1)
- expect(wrapper.find(RaisedButton).props().disabled).toBeTruthy()
- expect(wrapper.instance().isActionDisabled()).toBeTruthy()
- })
-
- it('Followup Analysis renders a disabled "Follow Up" RaisedButton', () => {
- wrapper.setState({
- endDate: null,
- startDate: null,
- organisationUnitId: null,
- dataSetIds: null,
- })
- expect(wrapper.find(RaisedButton)).toHaveLength(1)
- expect(wrapper.find(RaisedButton).props().disabled).toBeTruthy()
- expect(wrapper.instance().isActionDisabled()).toBeTruthy()
- })
-
- it('Should render an active "Follow Up" RaisedButton when selected filters.', () => {
- wrapper.setState({
- loading: false,
- endDate: new Date(),
- startDate: new Date(),
- organisationUnitId: 'TestOrganisationUnitId',
- dataSetIds: ['id1', 'id2', 'id3'],
- })
- expect(wrapper.find(RaisedButton)).toHaveLength(1)
- expect(wrapper.find(RaisedButton).props().disabled).toBeFalsy()
- expect(wrapper.instance().isActionDisabled()).toBeFalsy()
- })
-})
-
-describe('Test
actions:', () => {
- it('Should call dataSetsOnChange function when Available Datasets Select changes.', () => {
- const spy = spyOn(
- FollowUpAnalysis.prototype,
- 'dataSetsOnChange'
- ).and.callThrough()
- const wrapper = ownShallow()
- wrapper.setState({
- dataSetIds: [],
- })
- wrapper.find(AvailableDatasetsSelect).simulate('change', {
- target: {
- selectedOptions: [{ value: 'id1' }, { value: 'id2' }],
- },
- })
- expect(spy).toHaveBeenCalledWith({
- target: {
- selectedOptions: [{ value: 'id1' }, { value: 'id2' }],
- },
- })
- expect(wrapper.state('dataSetIds')).toMatchObject(['id1', 'id2'])
- })
-
- it('Should call organisationUnitOnChange function when Available Organisation Units Tree changes.', () => {
- const spy = spyOn(
- FollowUpAnalysis.prototype,
- 'organisationUnitOnChange'
- ).and.callThrough()
- const wrapper = ownShallow()
- wrapper.setState({
- organisationUnitId: null,
- })
- wrapper
- .find(AvailableOrganisationUnitsTree)
- .simulate('change', 'TestOrganisationUnitId')
- expect(spy).toHaveBeenCalledWith('TestOrganisationUnitId')
- expect(wrapper.state('organisationUnitId')).toBe(
- 'TestOrganisationUnitId'
- )
- })
-
- it('Should call startDateOnChange function when Start Date DatePicker changes.', () => {
- const spy = spyOn(
- FollowUpAnalysis.prototype,
- 'startDateOnChange'
- ).and.callThrough()
- const wrapper = ownShallow()
- const testStartDate = new Date()
- wrapper.setState({
- startDate: null,
- })
- wrapper
- .find(DatePicker)
- .at(0)
- .simulate('change', null, testStartDate)
- expect(spy).toHaveBeenCalledWith(null, testStartDate)
- expect(wrapper.state('startDate')).toMatchObject(testStartDate)
- })
-
- it('Should call endDateOnChange function when End Date DatePicker changes.', () => {
- const spy = spyOn(
- FollowUpAnalysis.prototype,
- 'endDateOnChange'
- ).and.callThrough()
- const wrapper = ownShallow()
- const testEndDate = new Date()
- wrapper.setState({
- endDate: null,
- })
- wrapper
- .find(DatePicker)
- .at(1)
- .simulate('change', null, testEndDate)
- expect(spy).toHaveBeenCalledWith(null, testEndDate)
- expect(wrapper.state('endDate')).toMatchObject(testEndDate)
- })
-
- it('Followup Analysis calls back method when IconButton (back) is clicked', () => {
- const spy = spyOn(FollowUpAnalysis.prototype, 'back')
- const wrapper = ownShallow()
- wrapper.find(IconButton).simulate('click')
- expect(spy).toHaveBeenCalled()
- })
-
- it('Followup Analysis calls getFollowUpList method when RaisedButton is clicked', () => {
- const spy = spyOn(FollowUpAnalysis.prototype, 'getFollowUpList')
- const wrapper = ownShallow()
- wrapper.find(RaisedButton).simulate('click')
- expect(spy).toHaveBeenCalled()
- })
-
- it('Followup Analysis update state when back button is clicked', () => {
- const wrapper = ownShallow()
- wrapper.setState({ showTable: true })
- wrapper.find(IconButton).simulate('click')
- expect(wrapper.state('showTable')).toBe(false)
- })
-
- it('Should change element marked state when toggleCheckbox is called.', () => {
- const wrapper = ownShallow()
- wrapper.setState({
- elements: [
- {
- key: 'key1',
- marked: false,
- },
- {
- key: 'key2',
- marked: false,
- },
- ],
- })
- wrapper.instance().toggleCheckbox({ key: 'key1' })
- expect(wrapper.state('elements')[0].marked).toBe(true)
- })
-})
diff --git a/src/pages/follow-up-analysis/Form.js b/src/pages/follow-up-analysis/Form.js
new file mode 100644
index 00000000..1bf9e51d
--- /dev/null
+++ b/src/pages/follow-up-analysis/Form.js
@@ -0,0 +1,102 @@
+import { useAlert } from '@dhis2/app-runtime'
+import i18n from '@dhis2/d2-i18n'
+import { Button } from '@dhis2/ui'
+import DatePicker from 'material-ui/DatePicker'
+import React from 'react'
+import AvailableDatasetsSelect from '../../components/available-datasets-select/AvailableDatasetsSelect'
+import AvailableOrganisationUnitsTree from '../../components/available-organisation-units-tree/AvailableOrganisationUnitsTree'
+import cssPageStyles from '../Page.module.css'
+import jsPageStyles from '../PageStyles'
+
+/* eslint-disable react/prop-types */
+
+const FollowUpButton = ({ onClick, disabled }) => {
+ const noValuesFoundAlert = useAlert(i18n.t('No values found'), {
+ success: true,
+ })
+ const errorAlert = useAlert(
+ ({ error }) => {
+ error?.message ||
+ i18n.t('An unexpected error happened during analysis')
+ },
+ { critical: true }
+ )
+ const handleClick = async () => {
+ try {
+ const result = await onClick()
+ if (result === 'NO_VALUES_FOUND') {
+ noValuesFoundAlert.show()
+ }
+ } catch (error) {
+ errorAlert.show({ error })
+ }
+ }
+
+ return (
+
+ )
+}
+
+const Form = ({
+ onSubmit,
+ submitDisabled,
+ dataSetIds,
+ onDataSetsChange,
+ onOrganisationUnitChange,
+ startDate,
+ onStartDateChange,
+ endDate,
+ onEndDateChange,
+}) => (
+ <>
+
+
+
+ {i18n.t('Data Set')}
+
+
+
+
+
+ {i18n.t('Parent organisation unit')}
+
+
+
+
+
+
+
+
+
+ >
+)
+
+export default Form
diff --git a/src/pages/follow-up-analysis/follow-up-analysis-table/FollowUpAnalysisTable.js b/src/pages/follow-up-analysis/follow-up-analysis-table/FollowUpAnalysisTable.js
index 83aca02a..15ceac75 100644
--- a/src/pages/follow-up-analysis/follow-up-analysis-table/FollowUpAnalysisTable.js
+++ b/src/pages/follow-up-analysis/follow-up-analysis-table/FollowUpAnalysisTable.js
@@ -1,5 +1,4 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
+import i18n from '@dhis2/d2-i18n'
import classNames from 'classnames'
import {
Checkbox,
@@ -15,20 +14,21 @@ import {
Dialog,
FlatButton,
} from 'material-ui'
-import i18n from '@dhis2/d2-i18n'
-import FormattedNumber from '../../../components/formatters/FormattedNumber'
+import PropTypes from 'prop-types'
+import React, { Component } from 'react'
import DownloadAs from '../../../components/download-as/DownloadAs'
+import FormattedNumber from '../../../components/formatters/FormattedNumber'
+import { apiConf } from '../../../server.conf'
import cssPageStyles from '../../Page.module.css'
import jsPageStyles from '../../PageStyles'
import styles from './FollowUpAnalysisTable.module.css'
-import { apiConf } from '../../../server.conf'
class FollowUpAnalysisTable extends Component {
static propTypes = {
elements: PropTypes.array.isRequired,
+ loading: PropTypes.bool.isRequired,
toggleCheckbox: PropTypes.func.isRequired,
unfollow: PropTypes.func.isRequired,
- loading: PropTypes.bool.isRequired,
}
static contextTypes = {
@@ -36,9 +36,7 @@ class FollowUpAnalysisTable extends Component {
}
static generateElementKey = e =>
- `${e.attributeOptionComboId}-${e.categoryOptionComboId}-${e.periodId}-${
- e.sourceId
- }-${e.dataElementId}`
+ `${e.attributeOptionComboId}-${e.categoryOptionComboId}-${e.periodId}-${e.sourceId}-${e.dataElementId}`
static convertElementFromApiResponse = e => ({
key: FollowUpAnalysisTable.generateElementKey(e),
@@ -125,6 +123,7 @@ class FollowUpAnalysisTable extends Component {
const commentDialogActions = [
'FeedbackSnackbarTypes'
-)
-jest.mock('d2-ui/lib/org-unit-tree/OrgUnitTree.component', () => 'OrgUnitTree')
-
-const response = [
- {
- dataElementId: 360049,
- periodId: 475192,
- sourceId: 73739,
- categoryOptionComboId: 4,
- attributeOptionComboId: 4,
- value: '10',
- followup: true,
- min: 11,
- max: 22,
- dataElementName: 'Stock PHU dispensed BCG',
- period: {
- code: 201801,
- name: 'January 2018',
- externalAccess: false,
- displayName: 'January 2018',
- shortName: '201801',
- displayShortName: '201801',
- dimensionItemType: 'PERIOD',
- periodType: 'Monthly',
- startDate: '2018-01-01T00:00:00.000',
- endDate: '2018-01-31T00:00:00.000',
- dimensionItem: '201801',
- favorite: 'false',
- id: 201801,
- },
- sourceName: 'Bontiwo MCHP',
- categoryOptionComboName: 'default',
- },
- {
- dataElementId: 8240,
- periodId: 475192,
- sourceId: 559,
- categoryOptionComboId: 358734,
- attributeOptionComboId: 1153396,
- value: '12',
- comment: '1234',
- followup: true,
- min: 1,
- max: 23,
- dataElementName: 'ART enrollment stage 1',
- period: {
- code: '201801',
- name: 'January 2018',
- externalAccess: false,
- displayName: 'January 2018',
- shortName: '201801',
- displayShortName: '201801',
- dimensionItemType: 'PERIOD',
- periodType: 'Monthly',
- startDate: '2018-01-01T00:00:00.000',
- endDate: '2018-01-31T00:00:00.000',
- dimensionItem: '201801',
- favorite: false,
- id: '201801',
- },
- sourceName: 'Ngelehun CHC',
- categoryOptionComboName: 'Male, <15y',
- },
-]
-
-const testElements = response.map(
- FollowUpAnalysisTable.convertElementFromApiResponse
-)
-
-const expectedElementFormat = {
- key: '4-4-475192-73739-360049',
- attributeOptionComboId: 4,
- categoryOptionComboId: 4,
- periodId: 475192,
- organisationUnitId: 73739,
- dataElementId: 360049,
- dataElement: 'Stock PHU dispensed BCG',
- organisation: 'Bontiwo MCHP',
- period: 'January 2018',
- min: 11,
- max: 22,
- value: 10,
- marked: false,
- comment: undefined,
-}
-
-const expectedUnfollowFormat = {
- dataElementId: 360049,
- periodId: 475192,
- organisationUnitId: 73739,
- categoryOptionComboId: 4,
- attributeOptionComboId: 4,
- followup: false,
-}
-
-const ownShallow = () => {
- return shallow(
- ,
- {
- disableLifecycleMethods: true,
- }
- )
-}
-
-describe('Test rendering:', () => {
- let wrapper
- beforeEach(() => {
- wrapper = ownShallow()
- })
-
- it('FollowUpAnalysisTable renders without crashing.', () => {
- ownShallow()
- })
-
- it('Should render correct number of rows.', () => {
- expect(wrapper.find(TableRow).length).toBe(3) // Two elements plus header row
- })
-
- it('Should render correct number of columns.', () => {
- expect(
- wrapper
- .find(TableRow)
- .at(1)
- .find(TableRowColumn).length
- ).toBe(8) // First row after header
- })
-
- it('Should render a Unfollow "Checkbox" for each element.', () => {
- expect(wrapper.find(Checkbox).length).toBe(2)
- })
-
- it('Should render comment "Dialog" if element have it.', () => {
- expect(wrapper.find(Dialog).length).toBe(1)
- })
-
- it('Should render speaker_notes "FontIcon" if element have a comment.', () => {
- expect(wrapper.find(IconButton).length).toBe(1)
- const iconButton = mount(
-
- {wrapper.find(IconButton)}
-
- )
- expect(iconButton.text()).toBe('speaker_notes')
- })
-
- it('Should render an disabled "Unfollow" button when no element selected.', () => {
- expect(wrapper.find(RaisedButton).length).toBe(1)
- expect(wrapper.find(RaisedButton).props().disabled).toBe(true)
- })
-
- it('Should render an enabled "Unfollow" button when element(s) selected to unfollow.', () => {
- expect(wrapper.find(RaisedButton).length).toBe(1)
- wrapper.instance().oneChecked = true
- expect(wrapper.find(RaisedButton).props().disabled).toBe(true)
- })
-
- it('Render "DownloadAs" components.', () => {
- expect(wrapper.find(DownloadAs).length).toBe(2)
- })
-})
-
-describe('Test actions:', () => {
- it('Should call updateCheckbox method when checkbox is checked/unchecked.', () => {
- const spy = spyOn(FollowUpAnalysisTable.prototype, 'updateCheckbox')
- const wrapper = ownShallow()
- wrapper
- .find(Checkbox)
- .at(0)
- .simulate('check')
- expect(spy).toHaveBeenCalledWith(testElements[0])
- })
-
- it('Should call showComment method and update state when comment icon is clicked.', () => {
- const spy = spyOn(
- FollowUpAnalysisTable.prototype,
- 'showComment'
- ).and.callThrough()
- const wrapper = ownShallow()
- wrapper.setState({
- showComment: false,
- comment: null,
- })
- wrapper
- .find(IconButton)
- .at(0)
- .simulate('click')
- expect(spy).toHaveBeenCalledWith(testElements[1])
- expect(wrapper.state().showComment).toBe(true)
- expect(wrapper.state().comment).toBe(testElements[1].comment)
- })
-
- it('Should change showDialog state to false when closeCommentDialog method is called.', () => {
- const wrapper = ownShallow()
- wrapper.setState({
- showComment: true,
- comment: 'Test comment!',
- })
- wrapper.instance().closeCommentDialog()
- expect(wrapper.state().showComment).toBe(false)
- })
-
- it('Should call unfollow method when "Unfollow" button is clicked.', () => {
- const spy = spyOn(FollowUpAnalysisTable.prototype, 'unfollow')
- const wrapper = ownShallow()
- wrapper
- .find(RaisedButton)
- .at(0)
- .simulate('click')
- expect(spy).toHaveBeenCalled()
- })
-
- it('Should correctly convert elements from API response.', () => {
- const element = FollowUpAnalysisTable.convertElementFromApiResponse(
- response[0]
- )
- expect(element).toEqual(expectedElementFormat)
- })
-
- it('Should correctly convert elements to Unfollowup request.', () => {
- const element = FollowUpAnalysisTable.convertElementToUnFollowupRequest(
- testElements[0]
- )
- expect(element).toEqual(expectedUnfollowFormat)
- })
-
- it('Should correctly generateElementKey.', () => {
- const element = FollowUpAnalysisTable.generateElementKey(response[0])
- const responseElement = response[0]
- expect(element).toBe(
- `${responseElement.attributeOptionComboId}-` +
- `${responseElement.categoryOptionComboId}-` +
- `${responseElement.periodId}-` +
- `${responseElement.sourceId}-` +
- `${responseElement.dataElementId}`
- )
- })
-})
diff --git a/src/pages/home/Home.js b/src/pages/home/Home.js
index 62e303dc..5cabbe6f 100644
--- a/src/pages/home/Home.js
+++ b/src/pages/home/Home.js
@@ -1,27 +1,23 @@
-import React from 'react'
import classNames from 'classnames'
+import React from 'react'
+import { sections } from '../sections.conf'
import GridSection from './grid-section/GridSection'
import styles from './Home.module.css'
-import { sections } from '../sections.conf'
-
-const Home = () => {
- const gridElements = sections.map(section => (
-
-
-
- ))
- return (
-
- {gridElements}
-
- )
-}
+const Home = () => (
+
+ {sections.map(section => (
+
+
+
+ ))}
+
+)
export default Home
diff --git a/src/pages/home/Home.module.css b/src/pages/home/Home.module.css
index 6c517d5d..e6985162 100644
--- a/src/pages/home/Home.module.css
+++ b/src/pages/home/Home.module.css
@@ -1,5 +1,6 @@
.gridContainer {
- margin-bottom: 8px;
+ margin-top: 15px;
+ margin-bottom: 15px;
}
.gridContainer a {
diff --git a/src/pages/home/Home.test.js b/src/pages/home/Home.test.js
deleted file mode 100644
index a75f4730..00000000
--- a/src/pages/home/Home.test.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/* eslint-disable */
-import React from 'react'
-import { shallow } from 'enzyme'
-import Homepage from './Home'
-import GridSection from './grid-section/GridSection'
-import { sections } from '../sections.conf'
-
-jest.mock('d2-ui/lib/org-unit-tree/OrgUnitTree.component', () => 'OrgUnitTree')
-jest.mock(
- 'd2-ui/lib/feedback-snackbar/FeedbackSnackbarTypes',
- () => 'FeedbackSnackbarTypes'
-)
-
-const ownShallow = () => {
- return shallow(, {
- disableLifecycleMethods: true,
- context: {
- updateAppState: jest.fn(),
- },
- })
-}
-
-it('Homepage renders without crashing', () => {
- ownShallow()
-})
-
-it('Homepage renders a GridList', () => {
- const wrapper = ownShallow()
- expect(wrapper.find('#grid-list-id')).toHaveLength(1)
-})
-
-it('Home renders the correct number of GridSection', () => {
- const wrapper = ownShallow()
- expect(wrapper.find(GridSection)).toHaveLength(sections.length)
-})
diff --git a/src/pages/home/grid-section/GridSection.js b/src/pages/home/grid-section/GridSection.js
index 7c80e3b5..bce2ca7f 100644
--- a/src/pages/home/grid-section/GridSection.js
+++ b/src/pages/home/grid-section/GridSection.js
@@ -1,60 +1,59 @@
-import React, { PureComponent } from 'react'
+import { Card } from '@dhis2/ui'
+import classNames from 'classnames'
+import FontIcon from 'material-ui/FontIcon'
import PropTypes from 'prop-types'
+import React, { PureComponent } from 'react'
import { Link } from 'react-router-dom'
-import { GridTile } from 'material-ui/GridList'
-import FontIcon from 'material-ui/FontIcon'
-import classNames from 'classnames'
import styles from './GridSection.module.css'
class GridSection extends PureComponent {
static propTypes = {
section: PropTypes.shape({
- key: PropTypes.string,
- path: PropTypes.string,
info: PropTypes.shape({
- label: PropTypes.func,
+ actionText: PropTypes.func,
description: PropTypes.func,
icon: PropTypes.string,
- actionText: PropTypes.func,
+ label: PropTypes.func,
}),
+ key: PropTypes.string,
+ path: PropTypes.string,
}).isRequired,
}
render() {
return (
-
-
-
-
- {this.props.section.info.label()}
-
-
+
+
+
+
+ {this.props.section.info.label()}
+
+
+ {this.props.section.info.icon}
+
+
+
- {this.props.section.info.icon}
-
+ {this.props.section.info.description()}
+
-
- {this.props.section.info.description()}
-
{this.props.section.info.actionText()}
-
+
)
}
diff --git a/src/pages/home/grid-section/GridSection.module.css b/src/pages/home/grid-section/GridSection.module.css
index c7a07aca..71880e17 100644
--- a/src/pages/home/grid-section/GridSection.module.css
+++ b/src/pages/home/grid-section/GridSection.module.css
@@ -1,17 +1,21 @@
+.link {
+ display: block;
+ height: 100%;
+ text-decoration: none !important;
+}
+
.gridElement {
- border-radius: 5px;
- background-color: #ffffff;
- box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 6px, rgba(0, 0, 0, 0.12) 0px 1px 4px;
- padding-left: 38px;
- padding-right: 38px;
- margin-bottom: 8px;
- min-height: 218px;
- min-width: 200px;
+ display: flex !important;
+ flex-direction: column;
+ padding: var(--spacers-dp24);
+}
+
+.gridElement > div {
+ flex: 1;
}
.gridTitleBar {
position: relative;
- margin-top: 30px;
height: 42px;
flex-wrap: nowrap;
justify-content: space-between;
@@ -19,32 +23,28 @@
}
.gridTitleDescription {
- font-size: 24px;
- text-align: left;
- color: #000000;
+ margin: 0;
+ font-size: 18px;
+ color: var(--colors-grey800);
}
.gridIcon {
- float: right;
color: #757575 !important;
- font-size: 50px !important;
}
.gridDescription {
+ display: block;
+ /* min-height: 70px; */
+ margin: var(--spacers-dp12) 0;
font-size: 14px;
text-align: left;
color: #757575;
- margin-top: 42px;
- margin-bottom: 35px;
- display: block;
}
.gridActionText {
- position: absolute;
- bottom: 20px;
display: block;
font-size: 16px;
font-weight: 600;
text-align: left;
- color: #2196f3;
+ color: var(--colors-blue700);
}
diff --git a/src/pages/home/grid-section/GridSection.test.js b/src/pages/home/grid-section/GridSection.test.js
deleted file mode 100644
index 8ca571d7..00000000
--- a/src/pages/home/grid-section/GridSection.test.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/* eslint-disable */
-import React from 'react'
-import { shallow } from 'enzyme'
-import { Link } from 'react-router-dom'
-import { GridTile } from 'material-ui/GridList/index'
-import GridSection from './GridSection'
-import { sections } from '../../sections.conf'
-
-jest.mock('d2-ui/lib/org-unit-tree/OrgUnitTree.component', () => 'OrgUnitTree')
-jest.mock(
- 'd2-ui/lib/feedback-snackbar/FeedbackSnackbarTypes',
- () => 'FeedbackSnackbarTypes'
-)
-
-const section = sections[0]
-const ownShallow = () => {
- return shallow(, {
- disableLifecycleMethods: true,
- })
-}
-
-it('GridSection renders without crashing', () => {
- ownShallow()
-})
-
-it('GridSection renders a Link', () => {
- const wrapper = ownShallow()
- expect(wrapper.find(Link)).toHaveLength(1)
-})
-
-it('GridSection renders a GridTile', () => {
- const wrapper = ownShallow()
- expect(wrapper.find(GridTile)).toHaveLength(1)
-})
diff --git a/src/pages/outlier-detection/Form.js b/src/pages/outlier-detection/Form.js
new file mode 100644
index 00000000..f5491978
--- /dev/null
+++ b/src/pages/outlier-detection/Form.js
@@ -0,0 +1,231 @@
+import { useAlert } from '@dhis2/app-runtime'
+import i18n from '@dhis2/d2-i18n'
+import { Button } from '@dhis2/ui'
+import DatePicker from 'material-ui/DatePicker'
+import MenuItem from 'material-ui/MenuItem'
+import SelectField from 'material-ui/SelectField'
+import React from 'react'
+import AvailableDatasetsSelect from '../../components/available-datasets-select/AvailableDatasetsSelect'
+import AvailableOrganisationUnitsTree from '../../components/available-organisation-units-tree/AvailableOrganisationUnitsTree'
+import cssPageStyles from '../Page.module.css'
+import jsPageStyles from '../PageStyles'
+import { Z_SCORE } from './constants'
+import styles from './Form.module.css'
+
+/* eslint-disable react/prop-types */
+
+const StartButton = ({ onClick, disabled }) => {
+ const noValuesFoundAlert = useAlert(i18n.t('No values found'), {
+ success: true,
+ })
+ const errorAlert = useAlert(
+ ({ error }) => {
+ error?.message ||
+ i18n.t('An unexpected error happened during analysis')
+ },
+ { critical: true }
+ )
+ const handleClick = async () => {
+ try {
+ const result = await onClick()
+ if (result === 'NO_VALUES_FOUND') {
+ noValuesFoundAlert.show()
+ }
+ } catch (error) {
+ errorAlert.show({ error })
+ }
+ }
+
+ return (
+
+ )
+}
+
+const ThresholdField = ({ threshold, onChange }) => (
+
+
+
+
+
+
+
+
+
+
+
+)
+
+const ZScoreFields = ({
+ showAdvancedZScoreFields,
+ onToggleAdvancedZScoreFields,
+ sortBy,
+ onSortByChange,
+ dataStartDate,
+ dataEndDate,
+ onDataStartDateChange,
+ onDataEndDateChange,
+}) => (
+ <>
+
+ {showAdvancedZScoreFields && (
+ <>
+
+
+
+
+
+
+ >
+ )}
+ >
+)
+
+const Form = ({
+ onSubmit,
+ submitDisabled,
+ startDate,
+ endDate,
+ algorithm,
+ showAdvancedZScoreFields,
+ onToggleAdvancedZScoreFields,
+ onAlgorithmChange,
+ threshold,
+ onThresholdChange,
+ sortBy,
+ onSortByChange,
+ dataStartDate,
+ dataEndDate,
+ onDataStartDateChange,
+ onDataEndDateChange,
+ dataSetIds,
+ onDataSetsOnChange,
+ onOrganisationUnitChange,
+ maxResults,
+ onMaxResultsChange,
+ onStartDateChange,
+ onEndDateChange,
+}) => (
+ <>
+
+
+
+ {i18n.t('Data set')}
+
+
+
+
+
+ {i18n.t('Organisation units')}
+
+
+
+
+
+
+
+
+
+
+ {algorithm === Z_SCORE && (
+
+ )}
+
+
+
+
+
+ {algorithm === Z_SCORE && (
+
+ )}
+
+
+
+ >
+)
+
+export default Form
diff --git a/src/pages/outlier-detection/Form.module.css b/src/pages/outlier-detection/Form.module.css
new file mode 100644
index 00000000..b67bf8c6
--- /dev/null
+++ b/src/pages/outlier-detection/Form.module.css
@@ -0,0 +1,3 @@
+.toggleBtn {
+ margin-top: var(--spacers-dp12);
+}
diff --git a/src/pages/outlier-detection/OutlierDetection.js b/src/pages/outlier-detection/OutlierDetection.js
index 6ede9c5e..08958360 100644
--- a/src/pages/outlier-detection/OutlierDetection.js
+++ b/src/pages/outlier-detection/OutlierDetection.js
@@ -1,35 +1,23 @@
-import React from 'react'
-import { Card, CardText } from 'material-ui/Card'
-import RaisedButton from 'material-ui/RaisedButton'
-import DatePicker from 'material-ui/DatePicker'
-import SelectField from 'material-ui/SelectField'
-import MenuItem from 'material-ui/MenuItem'
-import FlatButton from 'material-ui/FlatButton'
+import i18n from '@dhis2/d2-i18n'
+import { Card } from '@dhis2/ui'
import { FontIcon, IconButton } from 'material-ui'
-import { SUCCESS } from 'd2-ui/lib/feedback-snackbar/FeedbackSnackbarTypes'
-import classNames from 'classnames'
-import Page from '../Page'
-import AvailableDatasetsSelect from '../../components/available-datasets-select/AvailableDatasetsSelect'
-import AvailableOrganisationUnitsTree from '../../components/available-organisation-units-tree/AvailableOrganisationUnitsTree'
-import PageHelper from '../../components/page-helper/PageHelper'
-import OutlierAnalyisTable from '../../components/outlier-analysis-table/OutlierAnalysisTable'
+import React from 'react'
import AlertBar from '../../components/alert-bar/AlertBar'
-import i18n from '@dhis2/d2-i18n'
+import OutlierAnalyisTable from '../../components/outlier-analysis-table/OutlierAnalysisTable'
+import PageHelper from '../../components/page-helper/PageHelper'
import { convertDateToApiDateFormat } from '../../helpers/dates'
-import { getDocsKeyForSection } from '../sections.conf'
import { apiConf } from '../../server.conf'
+import Page from '../Page'
import cssPageStyles from '../Page.module.css'
-import jsPageStyles from '../PageStyles'
-import threeMonthsAgo from '../../helpers/threeMonthsAgo'
-
-export const Z_SCORE = 'Z_SCORE'
-export const DEFAULT_THRESHOLD = 3.0
-export const DEFAULT_ALGORITHM = Z_SCORE
-export const DEFAULT_MAX_RESULTS = 500
-export const DEFAULT_SORT_BY = Z_SCORE
-
-const getMarkedForFollowUpSuccesMessage = marked =>
- marked ? i18n.t('Marked for follow-up') : i18n.t('Unmarked for follow-up')
+import { getDocsKeyForSection } from '../sections.conf'
+import {
+ Z_SCORE,
+ DEFAULT_THRESHOLD,
+ DEFAULT_ALGORITHM,
+ DEFAULT_MAX_RESULTS,
+ DEFAULT_SORT_BY,
+} from './constants'
+import Form from './Form'
class OutlierDetection extends Page {
static STATE_PROPERTIES = [
@@ -52,6 +40,12 @@ class OutlierDetection extends Page {
constructor() {
super()
+ const threeMonthsAgo = () => {
+ const date = new Date()
+ date.setMonth(date.getMonth() - 3)
+ return date
+ }
+
this.state = {
showTable: false,
startDate: threeMonthsAgo(),
@@ -68,91 +62,46 @@ class OutlierDetection extends Page {
maxResults: DEFAULT_MAX_RESULTS,
sortBy: DEFAULT_SORT_BY,
}
-
- this.start = this.start.bind(this)
- this.back = this.back.bind(this)
-
- this.startDateOnChange = this.startDateOnChange.bind(this)
- this.endDateOnChange = this.endDateOnChange.bind(this)
- this.organisationUnitOnChange = this.organisationUnitOnChange.bind(this)
- this.dataSetsOnChange = this.dataSetsOnChange.bind(this)
- this.thresholdOnChange = this.thresholdOnChange.bind(this)
- this.algorithmOnChange = this.algorithmOnChange.bind(this)
- this.dataStartDateOnChange = this.dataStartDateOnChange.bind(this)
- this.dataEndDateOnChange = this.dataEndDateOnChange.bind(this)
- this.maxResultsOnChange = this.maxResultsOnChange.bind(this)
- this.sortByOnChange = this.sortByOnChange.bind(this)
-
- this.toggleShowAdvancedZScoreFields = this.toggleShowAdvancedZScoreFields.bind(
- this
- )
- this.toggleCheckbox = this.toggleCheckbox.bind(this)
}
- componentWillReceiveProps(nextProps) {
+ UNSAFE_componentWillReceiveProps(nextProps) {
const nextState = {}
Object.keys(nextProps).forEach(property => {
- if (
- nextProps.hasOwnProperty(property) &&
- OutlierDetection.STATE_PROPERTIES.includes(property)
- ) {
+ if (OutlierDetection.STATE_PROPERTIES.includes(property)) {
nextState[property] = nextProps[property]
}
})
- if (nextState !== {}) {
- this.setState(nextState)
- }
+ this.setState(nextState)
}
- start() {
+ start = async () => {
+ this.context.updateAppState({
+ pageState: {
+ loading: true,
+ },
+ })
+ const endpoint = apiConf.endpoints.outlierDetection
+ const csvQueryStr = this.createQueryString()
const api = this.context.d2.Api.getApi()
- if (this.isFormValid()) {
- this.context.updateAppState({
- pageState: {
- loading: true,
- },
- })
-
- const endpoint = apiConf.endpoints.outlierDetection
- const csvQueryStr = this.createQueryString()
-
- api.get(`${endpoint}?${csvQueryStr}`)
- .then(response => {
- if (this.isPageMounted()) {
- const elements = response.outlierValues.map(
- OutlierAnalyisTable.convertElementFromApiResponse
- )
-
- const feedback =
- elements && elements.length > 0
- ? {
- showSnackbar: false,
- }
- : {
- showSnackbar: true,
- snackbarConf: {
- type: SUCCESS,
- message: i18n.t('No values found'),
- },
- }
-
- this.context.updateAppState({
- ...feedback,
- pageState: {
- loading: false,
- elements,
- showTable: elements && elements.length > 0,
- csvQueryStr,
- },
- })
- }
- })
- .catch(error => {
- this.manageError(error)
- })
+ const response = await api.get(`${endpoint}?${csvQueryStr}`)
+ if (!this.isPageMounted()) {
+ return
}
+
+ const elements = response.outlierValues.map(
+ OutlierAnalyisTable.convertElementFromApiResponse
+ )
+ this.context.updateAppState({
+ pageState: {
+ loading: false,
+ elements,
+ showTable: elements && elements.length > 0,
+ csvQueryStr,
+ },
+ })
+ return elements.length === 0 ? 'NO_VALUES_FOUND' : null
}
createQueryString() {
@@ -190,40 +139,39 @@ class OutlierDetection extends Page {
return querySegments.join('&')
}
- back() {
+ back = () => {
this.setState({ showTable: false, csvQueryStr: null })
this.context.updateAppState({
pageState: { showTable: false },
})
}
- startDateOnChange(event, date) {
+ handleStartDateChange = (event, date) => {
this.setState({ startDate: new Date(date) })
}
- endDateOnChange(event, date) {
+ handleEndDateOnChange = (event, date) => {
this.setState({ endDate: new Date(date) })
}
- dataStartDateOnChange(event, date) {
+ handleDataStartDateChange = (event, date) => {
this.setState({ dataStartDate: new Date(date) })
}
- dataEndDateOnChange(event, date) {
+ handleDataEndDateChange = (event, date) => {
this.setState({ dataEndDate: new Date(date) })
}
- maxResultsOnChange(event, index, value) {
+ handleMaxResultsChange = (event, index, value) => {
this.setState({ maxResults: value })
}
- sortByOnChange(event, index, value) {
+ handleSortByChange = (event, index, value) => {
this.setState({ sortBy: value })
}
- toggleShowAdvancedZScoreFields(event, index, value) {
+ toggleShowAdvancedZScoreFields = () => {
const shouldShow = !this.state.showAdvancedZScoreFields
-
if (shouldShow) {
this.setState({
showAdvancedZScoreFields: true,
@@ -239,34 +187,27 @@ class OutlierDetection extends Page {
}
}
- organisationUnitOnChange(organisationUnitIds) {
+ handleOrganisationUnitChange = organisationUnitIds => {
this.setState({ organisationUnitIds })
}
- dataSetsOnChange(event) {
- const dataSetIds = []
- const selectedOptions = event.target.selectedOptions
- for (let i = 0; i < selectedOptions.length; i++) {
- dataSetIds.push(selectedOptions[i].value)
- }
- this.setState({ dataSetIds })
+ handleDataSetsChange = ({ selected }) => {
+ this.setState({ dataSetIds: selected })
}
- thresholdOnChange(event, index, value) {
+ handleThresholdChange = (event, index, value) => {
this.setState({ threshold: value })
}
- algorithmOnChange(event, index, value) {
+ handleAlgorithmChange = (event, index, value) => {
this.setState({ algorithm: value })
}
- toggleCheckbox(element) {
- const api = this.context.d2.Api.getApi()
+ toggleCheckbox = async element => {
const currentElementIndex = this.state.elements.findIndex(
({ key }) => key === element.key
)
const currentElement = this.state.elements[currentElementIndex]
-
if (!currentElement) {
return
}
@@ -281,141 +222,50 @@ class OutlierDetection extends Page {
const data = OutlierAnalyisTable.convertElementToToggleFollowupRequest(
currentElement
)
- api.update(apiConf.endpoints.markDataValue, data)
- .then(() => {
- if (this.isPageMounted()) {
- const updatedElement = {
- ...currentElement,
- marked: !currentElement.marked,
- }
- const elements = [
- ...this.state.elements.slice(0, currentElementIndex),
- updatedElement,
- ...this.state.elements.slice(currentElementIndex + 1),
- ]
-
- this.context.updateAppState({
- showSnackbar: true,
- snackbarConf: {
- type: SUCCESS,
- message: getMarkedForFollowUpSuccesMessage(
- updatedElement.marked
- ),
- },
- pageState: {
- elements,
- loading: false,
- showTable: true,
- },
- })
- }
- })
- .catch(error => {
- this.manageError(error)
- })
- }
-
- isFormValid() {
- return (
- this.state.startDate &&
- this.state.endDate &&
- this.state.organisationUnitIds &&
- this.state.organisationUnitIds.length > 0 &&
- this.state.threshold &&
- this.state.dataSetIds &&
- this.state.dataSetIds.length > 0
- )
- }
-
- isActionDisabled() {
- return !this.isFormValid() || this.state.loading
- }
+ const api = this.context.d2.Api.getApi()
+ await api.update(apiConf.endpoints.markOutlierDataValue, data)
+ if (!this.isPageMounted()) {
+ return
+ }
- showAlertBar() {
- return (
- this.state.showTable &&
- this.state.elements &&
- this.state.elements.length >= apiConf.results.analysis.limit
- )
+ const updatedElement = {
+ ...currentElement,
+ marked: !currentElement.marked,
+ }
+ const elements = [
+ ...this.state.elements.slice(0, currentElementIndex),
+ updatedElement,
+ ...this.state.elements.slice(currentElementIndex + 1),
+ ]
+ this.context.updateAppState({
+ pageState: {
+ elements,
+ loading: false,
+ showTable: true,
+ },
+ })
}
- renderThresholdField() {
- return (
-
-
-
-
-
-
-
-
-
-
-
- )
- }
+ isFormValid = () =>
+ this.state.startDate &&
+ this.state.endDate &&
+ this.state.organisationUnitIds &&
+ this.state.organisationUnitIds.length > 0 &&
+ this.state.threshold &&
+ this.state.dataSetIds &&
+ this.state.dataSetIds.length > 0
- renderZScoreFields() {
- const { showAdvancedZScoreFields } = this.state
- const buttonLabel = showAdvancedZScoreFields
- ? i18n.t('Hide advanced options')
- : i18n.t('Show advanced options')
+ isActionDisabled = () => !this.isFormValid() || this.state.loading
- return (
- <>
-
- {showAdvancedZScoreFields && (
- <>
-
-
-
-
-
-
- >
- )}
- >
- )
- }
+ showAlertBar = () =>
+ this.state.showTable &&
+ this.state.elements &&
+ this.state.elements.length >= apiConf.results.analysis.limit
render() {
return (
-
+
- {i18n.t('Outlier Detection')}
+ {i18n.t('Outlier Detection')}
-
+
-
- {/* FORM: hidden using style to avoid not needed api requests when going back from table */}
-
-
-
-
- {i18n.t('Data set')}
-
-
-
-
-
- {i18n.t('Organisation units')}
-
-
-
-
-
-
-
-
-
-
- {this.state.algorithm === Z_SCORE &&
- this.renderThresholdField()}
-
-
-
-
-
- {this.state.algorithm === Z_SCORE &&
- this.renderZScoreFields()}
-
-
-
+ {/* FORM: hidden to avoid not needed api requests when going back from table */}
+ {!this.state.showTable && (
+
-
- {/* TABLE */}
+ )}
{this.state.showTable && this.state.csvQueryStr && (
-
-
-
+
)}
diff --git a/src/pages/outlier-detection/OutlierDetection.test.js b/src/pages/outlier-detection/OutlierDetection.test.js
deleted file mode 100644
index 33d1ab95..00000000
--- a/src/pages/outlier-detection/OutlierDetection.test.js
+++ /dev/null
@@ -1,305 +0,0 @@
-/* eslint-disable */
-import React from 'react'
-import { shallow } from 'enzyme'
-import { RaisedButton, IconButton } from 'material-ui'
-import DatePicker from 'material-ui/DatePicker'
-import SelectField from 'material-ui/SelectField'
-import OutlierDetection, {
- DEFAULT_ALGORITHM,
- DEFAULT_STANDARD_DEVIATION,
- DEFAULT_THRESHOLD,
-} from './OutlierDetection'
-import OutlierAnalyisTable from '../../components/outlier-analysis-table/OutlierAnalysisTable'
-import AvailableDatasetsSelect from '../../components/available-datasets-select/AvailableDatasetsSelect'
-import AvailableOrganisationUnitsTree from '../../components/available-organisation-units-tree/AvailableOrganisationUnitsTree'
-import { sections, OUTLIER_DETECTION_SECTION_KEY } from '../sections.conf'
-import AlertBar from '../../components/alert-bar/AlertBar'
-import PageHelper from '../../components/page-helper/PageHelper'
-
-let pageInfo = {}
-for (let i = 0; i < sections.length; i++) {
- const section = sections[i]
- if (section.key === OUTLIER_DETECTION_SECTION_KEY) {
- pageInfo = section.info
- break
- }
-}
-
-const ownShallow = () => {
- return shallow(
- ,
- {
- context: {
- updateAppState: jest.fn(),
- },
- disableLifecycleMethods: true,
- }
- )
-}
-
-/* Mocks */
-jest.mock('d2-ui/lib/org-unit-tree/OrgUnitTree.component', () => 'OrgUnitTree')
-jest.mock(
- 'd2-ui/lib/feedback-snackbar/FeedbackSnackbarTypes',
- () => 'FeedbackSnackbarTypes'
-)
-
-describe('Test rendering:', () => {
- let wrapper
- beforeEach(() => {
- wrapper = ownShallow()
- })
-
- it('Renders without crashing', () => {
- ownShallow()
- })
-
- it('Should show correct title.', () => {
- expect(wrapper.find('h1')).toHaveLength(1)
- expect(wrapper.find('h1').text()).toBe(
- `Outlier Detection`
- )
- })
-
- it('Renders an IconButton', () => {
- expect(wrapper.find(IconButton)).toHaveLength(1)
- })
-
- it('Should have an "AlertBar" component.', () => {
- expect(wrapper.find(AlertBar)).toHaveLength(1)
- })
-
- it('Should have an "PageHelper" component.', () => {
- expect(wrapper.find(PageHelper)).toHaveLength(1)
- })
-
- it('Should render an "AvailableDatasetsSelect" component.', () => {
- expect(wrapper.find(AvailableDatasetsSelect)).toHaveLength(1)
- })
-
- it('Should render an "AvailableOrganisationUnitsTree" component.', () => {
- expect(wrapper.find(AvailableOrganisationUnitsTree)).toHaveLength(1)
- })
-
- it('Renders a "Start date" - DatePicker.', () => {
- expect(wrapper.find(DatePicker).length).toBe(2)
- expect(
- wrapper
- .find(DatePicker)
- .at(0)
- .props().floatingLabelText
- ).toBe('Start date')
- })
-
- it('Renders a "End date" - DatePicker.', () => {
- expect(wrapper.find(DatePicker).length).toBe(2)
- expect(
- wrapper
- .find(DatePicker)
- .at(1)
- .props().floatingLabelText
- ).toBe('End date')
- })
-
- it('Renders an input to choose Algorithm.', () => {
- expect(wrapper.find('#algorithm').length).toBe(1)
- })
-
- it('Renders an input to choose Threshold.', () => {
- expect(wrapper.find('#threshold').length).toBe(1)
- })
-
- it('Renders an input to choose Max Results.', () => {
- expect(wrapper.find('#max-results').length).toBe(1)
- })
-
- it('Should not render a OutlierAnalysisTable when has no elements.', () => {
- const elements = []
- wrapper.setState({
- elements,
- showTable: elements && elements.length > 0,
- })
- expect(wrapper.find(OutlierAnalyisTable)).toHaveLength(0)
- expect(wrapper.state('showTable')).toBeFalsy()
- })
-
- it('Should show "OutlierAnalysisTable" component and back icon when has elements.', () => {
- const elements = ['one', 'two', 'three']
- wrapper.setState({
- elements,
- showTable: elements && elements.length > 0,
- csvQueryStr: 'test',
- })
- expect(wrapper.find(IconButton)).toHaveLength(1)
- expect(wrapper.find(IconButton).props().style.display).toBe('inline')
-
- expect(wrapper.find(OutlierAnalyisTable)).toHaveLength(1)
- // expect(
- // wrapper
- // .find(OutlierAnalyisTable)
- // .parent()
- // .props().style.display
- // ).toBe('block')
- // expect(wrapper.state('showTable')).toBeTruthy()
- })
-
- it('Renders a disabled "Start" RaisedButton when loading info.', () => {
- wrapper.setState({
- loading: true,
- endDate: new Date(),
- startDate: new Date(),
- organisationUnitId: 'TestOrganisationUnitId',
- dataSetIds: ['id1', 'id2', 'id3'],
- standardDeviation: DEFAULT_STANDARD_DEVIATION,
- })
- expect(wrapper.find(RaisedButton)).toHaveLength(1)
- expect(wrapper.find(RaisedButton).props().disabled).toBeTruthy()
- expect(wrapper.instance().isActionDisabled()).toBeTruthy()
- })
-
- it('Renders a disabled "Start" RaisedButton when no filter selected.', () => {
- wrapper.setState({
- endDate: null,
- startDate: null,
- organisationUnitId: null,
- dataSetIds: null,
- standardDeviation: DEFAULT_STANDARD_DEVIATION,
- })
- expect(wrapper.find(RaisedButton)).toHaveLength(1)
- expect(wrapper.find(RaisedButton).props().disabled).toBeTruthy()
- expect(wrapper.instance().isActionDisabled()).toBeTruthy()
- })
-
- it('Renders a enabled "Start" RaisedButton when filters selected.', () => {
- wrapper.setState({
- loading: false,
- endDate: new Date(),
- startDate: new Date(),
- organisationUnitIds: ['id1'],
- dataSetIds: ['id1', 'id2', 'id3'],
- alghortim: DEFAULT_ALGORITHM,
- threshold: DEFAULT_THRESHOLD,
- })
- expect(wrapper.find(RaisedButton)).toHaveLength(1)
- expect(wrapper.find(RaisedButton).props().disabled).toBeFalsy()
- expect(wrapper.instance().isActionDisabled()).toBeFalsy()
- })
-})
-
-describe('Test actions:', () => {
- it('Should call dataSetsOnChange function when Available Datasets Select changes.', () => {
- const spy = spyOn(
- OutlierDetection.prototype,
- 'dataSetsOnChange'
- ).and.callThrough()
- const wrapper = ownShallow()
- wrapper.setState({
- dataSetIds: [],
- })
- wrapper.find(AvailableDatasetsSelect).simulate('change', {
- target: {
- selectedOptions: [{ value: 'id1' }, { value: 'id2' }],
- },
- })
- expect(spy).toHaveBeenCalledWith({
- target: {
- selectedOptions: [{ value: 'id1' }, { value: 'id2' }],
- },
- })
- expect(wrapper.state('dataSetIds')).toMatchObject(['id1', 'id2'])
- })
-
- it('Should call organisationUnitOnChange function when Available Organisation Units Tree changes.', () => {
- const spy = spyOn(
- OutlierDetection.prototype,
- 'organisationUnitOnChange'
- ).and.callThrough()
- const wrapper = ownShallow()
- wrapper.setState({
- organisationUnitIds: [],
- })
- const testOrganisationUnitIds = ['ID_TEST']
- wrapper
- .find(AvailableOrganisationUnitsTree)
- .simulate('change', testOrganisationUnitIds)
- expect(spy).toHaveBeenCalledWith(testOrganisationUnitIds)
- expect(wrapper.state('organisationUnitIds')).toBe(
- testOrganisationUnitIds
- )
- })
-
- it('Should call startDateOnChange function when Start date DatePicker changes.', () => {
- const spy = spyOn(
- OutlierDetection.prototype,
- 'startDateOnChange'
- ).and.callThrough()
- const wrapper = ownShallow()
- const testStartDate = new Date()
- wrapper.setState({
- startDate: null,
- })
- wrapper
- .find(DatePicker)
- .at(0)
- .simulate('change', null, testStartDate)
- expect(spy).toHaveBeenCalledWith(null, testStartDate)
- expect(wrapper.state('startDate')).toMatchObject(testStartDate)
- })
-
- it('Should call endDateOnChange function when End date DatePicker changes.', () => {
- const spy = spyOn(
- OutlierDetection.prototype,
- 'endDateOnChange'
- ).and.callThrough()
- const wrapper = ownShallow()
- const testEndDate = new Date()
- wrapper.setState({
- endDate: null,
- })
- wrapper
- .find(DatePicker)
- .at(1)
- .simulate('change', null, testEndDate)
- expect(spy).toHaveBeenCalledWith(null, testEndDate)
- expect(wrapper.state('endDate')).toMatchObject(testEndDate)
- })
-
- it('Should call thresholdOnChange function when Threshold SelectField changes.', () => {
- const spy = spyOn(
- OutlierDetection.prototype,
- 'thresholdOnChange'
- ).and.callThrough()
- const wrapper = ownShallow()
- const testThreshold = 4.0
- wrapper.setState({
- threshold: DEFAULT_THRESHOLD,
- })
- wrapper.find('#threshold').simulate('change', null, null, testThreshold)
- expect(spy).toHaveBeenCalledWith(null, null, testThreshold)
- expect(wrapper.state('threshold')).toBe(testThreshold)
- })
-
- it('Should call back method when IconButton (back) is clicked', () => {
- const spy = spyOn(OutlierDetection.prototype, 'back')
- const wrapper = ownShallow()
- wrapper.find(IconButton).simulate('click')
- expect(spy).toHaveBeenCalled()
- })
-
- it('Standard Dev Outlier Analysis calls start method when RaisedButton is clicked', () => {
- const spy = spyOn(OutlierDetection.prototype, 'start')
- const wrapper = ownShallow()
- wrapper.find(RaisedButton).simulate('click')
- expect(spy).toHaveBeenCalled()
- })
-
- it('Should update state when back button is clicked', () => {
- const wrapper = ownShallow()
- wrapper.setState({ showTable: true })
- wrapper.find(IconButton).simulate('click')
- expect(wrapper.state('showTable')).toBe(false)
- })
-})
diff --git a/src/pages/outlier-detection/constants.js b/src/pages/outlier-detection/constants.js
new file mode 100644
index 00000000..361de5b2
--- /dev/null
+++ b/src/pages/outlier-detection/constants.js
@@ -0,0 +1,5 @@
+export const Z_SCORE = 'Z_SCORE'
+export const DEFAULT_THRESHOLD = 3.0
+export const DEFAULT_ALGORITHM = Z_SCORE
+export const DEFAULT_MAX_RESULTS = 500
+export const DEFAULT_SORT_BY = Z_SCORE
diff --git a/src/pages/sections.conf.js b/src/pages/sections.conf.js
index ae83eca6..d122ca2f 100644
--- a/src/pages/sections.conf.js
+++ b/src/pages/sections.conf.js
@@ -1,7 +1,7 @@
import i18n from '@dhis2/d2-i18n'
-import ValidationRulesAnalysis from './validation-rules-analysis/ValidationRulesAnalysis'
-import OutlierDetection from './outlier-detection/OutlierDetection'
import FollowUpAnalysis from './follow-up-analysis/FollowUpAnalysis'
+import OutlierDetection from './outlier-detection/OutlierDetection'
+import ValidationRulesAnalysis from './validation-rules-analysis/ValidationRulesAnalysis'
export const VALIDATION_RULES_ANALYSIS_SECTION_KEY = 'validationRulesAnalysis'
export const OUTLIER_DETECTION_SECTION_KEY = 'outlierDetection'
diff --git a/src/pages/validation-rules-analysis/Form.js b/src/pages/validation-rules-analysis/Form.js
new file mode 100644
index 00000000..a8dc01f7
--- /dev/null
+++ b/src/pages/validation-rules-analysis/Form.js
@@ -0,0 +1,117 @@
+import { useAlert } from '@dhis2/app-runtime'
+import i18n from '@dhis2/d2-i18n'
+import { Button, Checkbox } from '@dhis2/ui'
+import DatePicker from 'material-ui/DatePicker'
+import React from 'react'
+import AvailableOrganisationUnitsTree from '../../components/available-organisation-units-tree/AvailableOrganisationUnitsTree'
+import ValidationRuleGroupsSelect from '../../components/validation-rule-groups-select/ValidationRuleGroupsSelect'
+import cssPageStyles from '../Page.module.css'
+import jsPageStyles from '../PageStyles'
+
+/* eslint-disable react/prop-types */
+
+const ValidateButton = ({ onClick, disabled }) => {
+ const validationPassedAlert = useAlert(
+ i18n.t('Validation passed successfully'),
+ {
+ success: true,
+ }
+ )
+ const errorAlert = useAlert(
+ ({ error }) => {
+ error?.message ||
+ i18n.t('An unexpected error happened during analysis')
+ },
+ { critical: true }
+ )
+ const handleClick = async () => {
+ try {
+ const result = await onClick()
+ if (result === 'VALIDATION_PASSED') {
+ validationPassedAlert.show()
+ }
+ } catch (error) {
+ errorAlert.show({ error })
+ }
+ }
+
+ return (
+
+ )
+}
+
+const Form = ({
+ onSubmit,
+ submitDisabled,
+ onOrganisationUnitChange,
+ startDate,
+ onStartDateChange,
+ endDate,
+ onEndDateChange,
+ onValidationRuleGroupChange,
+ sendNotfications,
+ onSendNotificationsChange,
+ persistNewResults,
+ onPersistNewResultsChange,
+}) => (
+ <>
+
+
+
+ {i18n.t('Parent organisation unit')}
+
+
+
+
+
+
+ >
+)
+
+export default Form
diff --git a/src/pages/validation-rules-analysis/ValidationRulesAnalysis.js b/src/pages/validation-rules-analysis/ValidationRulesAnalysis.js
index d7f7bf0f..da385fc9 100644
--- a/src/pages/validation-rules-analysis/ValidationRulesAnalysis.js
+++ b/src/pages/validation-rules-analysis/ValidationRulesAnalysis.js
@@ -1,26 +1,18 @@
-import React from 'react'
-import classNames from 'classnames'
-import { Card, CardText } from 'material-ui/Card'
-import RaisedButton from 'material-ui/RaisedButton'
-import DatePicker from 'material-ui/DatePicker'
-import Checkbox from 'material-ui/Checkbox'
+import i18n from '@dhis2/d2-i18n'
+import { Card } from '@dhis2/ui'
import { FontIcon, IconButton } from 'material-ui'
-import { SUCCESS } from 'd2-ui/lib/feedback-snackbar/FeedbackSnackbarTypes'
-import Page from '../Page'
+import React from 'react'
import AlertBar from '../../components/alert-bar/AlertBar'
-import ValidationRuleGroupsSelect, {
- ALL_VALIDATION_RULE_GROUPS_ID,
-} from '../../components/validation-rule-groups-select/ValidationRuleGroupsSelect'
-import AvailableOrganisationUnitsTree from '../../components/available-organisation-units-tree/AvailableOrganisationUnitsTree'
import PageHelper from '../../components/page-helper/PageHelper'
-import { getDocsKeyForSection } from '../sections.conf'
-import i18n from '@dhis2/d2-i18n'
-import jsPageStyles from '../PageStyles'
-import cssPageStyles from '../Page.module.css'
-import ValidationRulesAnalysisTable from './validation-rules-analysis-table/ValidationRulesAnalysisTable'
-import { apiConf } from '../../server.conf'
+import { ALL_VALIDATION_RULE_GROUPS_ID } from '../../components/validation-rule-groups-select/ValidationRuleGroupsSelect'
import { convertDateToApiDateFormat } from '../../helpers/dates'
import threeMonthsAgo from '../../helpers/threeMonthsAgo'
+import { apiConf } from '../../server.conf'
+import Page from '../Page'
+import cssPageStyles from '../Page.module.css'
+import { getDocsKeyForSection } from '../sections.conf'
+import Form from './Form'
+import ValidationRulesAnalysisTable from './validation-rules-analysis-table/ValidationRulesAnalysisTable'
class ValidationRulesAnalysis extends Page {
static STATE_PROPERTIES = ['loading', 'elements', 'showTable']
@@ -34,46 +26,27 @@ class ValidationRulesAnalysis extends Page {
endDate: new Date(),
organisationUnitId: null,
validationRuleGroupId: ALL_VALIDATION_RULE_GROUPS_ID,
- notification: false,
- persist: false,
+ sendNotfications: false,
+ persistNewResults: false,
elements: [],
loading: false,
}
-
- this.validate = this.validate.bind(this)
- this.back = this.back.bind(this)
-
- this.startDateOnChange = this.startDateOnChange.bind(this)
- this.endDateOnChange = this.endDateOnChange.bind(this)
- this.organisationUnitOnChange = this.organisationUnitOnChange.bind(this)
- this.validationRuleGroupOnChange = this.validationRuleGroupOnChange.bind(
- this
- )
- this.updateSendNotifications = this.updateSendNotifications.bind(this)
- this.updatePersistNewResults = this.updatePersistNewResults.bind(this)
}
- componentWillReceiveProps(nextProps) {
+ UNSAFE_componentWillReceiveProps(nextProps) {
const nextState = {}
Object.keys(nextProps).forEach(property => {
- if (
- nextProps.hasOwnProperty(property) &&
- ValidationRulesAnalysis.STATE_PROPERTIES.includes(property)
- ) {
+ if (ValidationRulesAnalysis.STATE_PROPERTIES.includes(property)) {
nextState[property] = nextProps[property]
}
})
- if (nextState !== {}) {
- this.setState(nextState)
- }
+ this.setState(nextState)
}
static generateElementKey = e =>
- `${e.validationRuleId}-${e.periodId}-${e.organisationUnitId}-${
- e.attributeOptionComboId
- }`
+ `${e.validationRuleId}-${e.periodId}-${e.organisationUnitId}-${e.attributeOptionComboId}`
static convertElementFromApiResponse = e => ({
key: ValidationRulesAnalysis.generateElementKey(e),
@@ -91,96 +64,81 @@ class ValidationRulesAnalysis extends Page {
rightValue: e.rightSideValue,
})
- validate() {
- const api = this.context.d2.Api.getApi()
-
- if (this.isFormValid()) {
- const request = {
- startDate: convertDateToApiDateFormat(this.state.startDate),
- endDate: convertDateToApiDateFormat(this.state.endDate),
- ou: this.state.organisationUnitId,
- notification: this.state.notification,
- persist: this.state.persist,
- }
+ validate = async () => {
+ if (!this.isFormValid()) {
+ return
+ }
- if (
- this.state.validationRuleGroupId !==
- ALL_VALIDATION_RULE_GROUPS_ID
- ) {
- request.vrg = this.state.validationRuleGroupId
- }
+ const request = {
+ startDate: convertDateToApiDateFormat(this.state.startDate),
+ endDate: convertDateToApiDateFormat(this.state.endDate),
+ ou: this.state.organisationUnitId,
+ notification: this.state.notification,
+ persist: this.state.persist,
+ }
+ if (
+ this.state.validationRuleGroupId !== ALL_VALIDATION_RULE_GROUPS_ID
+ ) {
+ request.vrg = this.state.validationRuleGroupId
+ }
- this.context.updateAppState({
- pageState: {
- loading: true,
- },
- })
+ this.context.updateAppState({
+ pageState: {
+ loading: true,
+ },
+ })
- api.post(apiConf.endpoints.validationRulesAnalysis, { ...request })
- .then(response => {
- if (this.isPageMounted()) {
- const elements = response.map(
- ValidationRulesAnalysis.convertElementFromApiResponse
- )
- const feedback =
- elements && elements.length > 0
- ? {
- showSnackbar: false,
- }
- : {
- showSnackbar: true,
- snackbarConf: {
- type: SUCCESS,
- message: i18n.t(
- 'Validation passed successfully'
- ),
- },
- }
- this.context.updateAppState({
- ...feedback,
- pageState: {
- loading: false,
- elements,
- showTable: elements && elements.length > 0,
- },
- })
- }
- })
- .catch(() => {
- this.manageError()
- })
+ const api = this.context.d2.Api.getApi()
+ const response = await api.post(
+ apiConf.endpoints.validationRulesAnalysis,
+ { ...request }
+ )
+ if (!this.isPageMounted()) {
+ return
}
+
+ const elements = response.map(
+ ValidationRulesAnalysis.convertElementFromApiResponse
+ )
+ this.context.updateAppState({
+ pageState: {
+ loading: false,
+ elements,
+ showTable: elements.length > 0,
+ },
+ })
+ return elements.length === 0 ? 'VALIDATION_PASSED' : null
}
- back() {
+ back = () => {
this.setState({ showTable: false })
this.context.updateAppState({
pageState: { showTable: false },
})
}
- startDateOnChange(event, date) {
+ handleStartDateChange = (event, date) => {
this.setState({ startDate: new Date(date) })
}
- endDateOnChange(event, date) {
+ handleEndDateChange = (event, date) => {
this.setState({ endDate: new Date(date) })
}
- organisationUnitOnChange(organisationUnitId) {
+ handleOrganisationUnitChange = organisationUnitId => {
this.setState({ organisationUnitId })
}
- validationRuleGroupOnChange(event, index, value) {
+ handleValidationRuleGroupChange = (event, index, value) => {
this.setState({ validationRuleGroupId: value })
}
- updateSendNotifications(event, checked) {
- this.setState({ notification: checked })
+ handleSendNotificationsChange = ({ checked }) => {
+ this.setState({ sendNotfications: checked })
}
- updatePersistNewResults(event, checked) {
- this.setState({ persist: checked })
+ handlePersistNewResultsChange = ({ checked }) => {
+ this.setState({ persistNewResults: checked })
}
showAlertBar() {
@@ -206,7 +164,7 @@ class ValidationRulesAnalysis extends Page {
render() {
return (
-
+
- {i18n.t('Validation Rule Analysis')}
+ {i18n.t('Validation Rule Analysis')}
-
+
-
-
-
-
-
- {i18n.t('Parent organisation unit')}
-
-
-
-
-
-
+ {!this.state.showTable && (
+
-
-
+ )}
+ {this.state.showTable && (
-
+ )}
)
diff --git a/src/pages/validation-rules-analysis/ValidationRulesAnalysis.test.js b/src/pages/validation-rules-analysis/ValidationRulesAnalysis.test.js
deleted file mode 100644
index 389def6d..00000000
--- a/src/pages/validation-rules-analysis/ValidationRulesAnalysis.test.js
+++ /dev/null
@@ -1,306 +0,0 @@
-/* eslint-disable */
-import React from 'react'
-import { shallow } from 'enzyme'
-import ValidationRulesAnalysis from './ValidationRulesAnalysis'
-import {
- sections,
- VALIDATION_RULES_ANALYSIS_SECTION_KEY,
-} from '../sections.conf'
-import { Checkbox, DatePicker, IconButton, RaisedButton } from 'material-ui'
-import ValidationRuleGroupsSelect from '../../components/validation-rule-groups-select/ValidationRuleGroupsSelect'
-import ValidationRulesAnalysisTable from './validation-rules-analysis-table/ValidationRulesAnalysisTable'
-import AvailableOrganisationUnitsTree from '../../components/available-organisation-units-tree/AvailableOrganisationUnitsTree'
-import AlertBar from '../../components/alert-bar/AlertBar'
-import PageHelper from '../../components/page-helper/PageHelper'
-
-let pageInfo = {}
-for (let i = 0; i < sections.length; i++) {
- const section = sections[i]
- if (section.key === VALIDATION_RULES_ANALYSIS_SECTION_KEY) {
- pageInfo = section.info
- break
- }
-}
-
-jest.mock(
- 'd2-ui/lib/feedback-snackbar/FeedbackSnackbarTypes',
- () => 'FeedbackSnackbarTypes'
-)
-jest.mock('d2-ui/lib/org-unit-tree/OrgUnitTree.component', () => 'OrgUnitTree')
-
-const ownShallow = () => {
- return shallow(
- ,
- {
- context: {
- updateAppState: jest.fn(),
- },
- disableLifecycleMethods: true,
- }
- )
-}
-
-describe('Test rendering:', () => {
- let wrapper
- beforeEach(() => {
- wrapper = ownShallow()
- })
-
- it('Should render without crashing', () => {
- ownShallow()
- })
-
- it('Should show correct title.', () => {
- expect(wrapper.find('h1')).toHaveLength(1)
- expect(wrapper.find('h1').text()).toBe(
- `Validation Rule Analysis`
- )
- })
-
- it('Should have an "AlertBar" component.', () => {
- expect(wrapper.find(AlertBar)).toHaveLength(1)
- })
-
- it('Should have an "PageHelper" component.', () => {
- expect(wrapper.find(PageHelper)).toHaveLength(1)
- })
-
- it('Should render an "AvailableOrganisationUnitsTree" component.', () => {
- expect(wrapper.find(AvailableOrganisationUnitsTree)).toHaveLength(1)
- })
-
- it('Renders a "Start Date" - DatePicker.', () => {
- expect(
- wrapper
- .find(DatePicker)
- .at(0)
- .props().floatingLabelText
- ).toBe('Start Date')
- })
-
- it('Renders a "End Date" - DatePicker.', () => {
- expect(
- wrapper
- .find(DatePicker)
- .at(1)
- .props().floatingLabelText
- ).toBe('End Date')
- })
-
- it('Should render a "Validation Rule Group" - Select.', () => {
- expect(wrapper.find(ValidationRuleGroupsSelect)).toHaveLength(1)
- })
-
- it('Should render a Checkbox to choose "Send notifications".', () => {
- expect(
- wrapper
- .find(Checkbox)
- .at(0)
- .props().label
- ).toBe('Send Notifications')
- })
-
- it('Should render a Checkbox to choose "Persist new results".', () => {
- expect(
- wrapper
- .find(Checkbox)
- .at(1)
- .props().label
- ).toBe('Persist new results')
- })
-
- it('Should render a disabled "Validate" button.', () => {
- wrapper.setState({
- organisationUnitId: null,
- startDate: null,
- endDate: null,
- })
- expect(wrapper.find(RaisedButton)).toHaveLength(1)
- expect(wrapper.find(RaisedButton).props().disabled).toBeTruthy()
- expect(wrapper.instance().isActionDisabled()).toBeTruthy()
- })
-
- it('Should render an active "Validate" button.', () => {
- wrapper.setState({
- organisationUnitId: 'TestOrganisationUnitId',
- startDate: new Date(),
- endDate: new Date(),
- })
- expect(wrapper.find(RaisedButton)).toHaveLength(1)
- expect(wrapper.find(RaisedButton).props().disabled).toBeFalsy()
- expect(wrapper.instance().isActionDisabled()).toBeFalsy()
- })
-
- it('Should not show "ValidationRulesAnalysisTable" component when has no elements.', () => {
- const elements = []
- wrapper.setState({
- elements,
- showTable: elements && elements.length > 0,
- })
- expect(wrapper.find(IconButton)).toHaveLength(1)
- expect(wrapper.find(IconButton).props().style.display).toBe('none')
- expect(wrapper.find(ValidationRulesAnalysisTable)).toHaveLength(1)
- expect(
- wrapper
- .find(ValidationRulesAnalysisTable)
- .parent()
- .props().style.display
- ).toBe('none')
- expect(wrapper.state('showTable')).toBeFalsy()
- })
-
- it('Should show "ValidationRulesAnalysisTable" component and back icon when has elements.', () => {
- const elements = ['one', 'two', 'three']
- wrapper.setState({
- elements,
- showTable: elements && elements.length > 0,
- })
- expect(wrapper.find(IconButton)).toHaveLength(1)
- expect(wrapper.find(IconButton).props().style.display).toBe('inline')
- expect(wrapper.find(ValidationRulesAnalysisTable)).toHaveLength(1)
- expect(
- wrapper
- .find(ValidationRulesAnalysisTable)
- .parent()
- .props().style.display
- ).toBe('block')
- expect(wrapper.state('showTable')).toBeTruthy()
- })
-})
-
-describe('Test actions:', () => {
- it('Should call organisationUnitOnChange function when Available Organisation Units Tree changes.', () => {
- const spy = spyOn(
- ValidationRulesAnalysis.prototype,
- 'organisationUnitOnChange'
- ).and.callThrough()
- const wrapper = ownShallow()
- wrapper.setState({
- organisationUnitId: null,
- })
- wrapper
- .find(AvailableOrganisationUnitsTree)
- .simulate('change', 'TestOrganisationUnitId')
- expect(spy).toHaveBeenCalledWith('TestOrganisationUnitId')
- expect(wrapper.state('organisationUnitId')).toBe(
- 'TestOrganisationUnitId'
- )
- })
-
- it('Should call startDateOnChange function when Start Date DatePicker changes.', () => {
- const spy = spyOn(
- ValidationRulesAnalysis.prototype,
- 'startDateOnChange'
- ).and.callThrough()
- const wrapper = ownShallow()
- const testStartDate = new Date()
- wrapper.setState({
- startDate: null,
- })
- wrapper
- .find(DatePicker)
- .at(0)
- .simulate('change', null, testStartDate)
- expect(spy).toHaveBeenCalledWith(null, testStartDate)
- expect(wrapper.state('startDate')).toMatchObject(testStartDate)
- })
-
- it('Should call endDateOnChange function when End Date DatePicker changes.', () => {
- const spy = spyOn(
- ValidationRulesAnalysis.prototype,
- 'endDateOnChange'
- ).and.callThrough()
- const wrapper = ownShallow()
- const testEndDate = new Date()
- wrapper.setState({
- endDate: null,
- })
- wrapper
- .find(DatePicker)
- .at(1)
- .simulate('change', null, testEndDate)
- expect(spy).toHaveBeenCalledWith(null, testEndDate)
- expect(wrapper.state('endDate')).toMatchObject(testEndDate)
- })
-
- it('Should call validationRuleGroupOnChange function when ValidationRuleGroupsSelect changes.', () => {
- const spy = spyOn(
- ValidationRulesAnalysis.prototype,
- 'validationRuleGroupOnChange'
- ).and.callThrough()
- const wrapper = ownShallow()
- wrapper.setState({
- validationRuleGroupId: null,
- })
- wrapper
- .find(ValidationRuleGroupsSelect)
- .at(0)
- .simulate('change', null, null, 'TestValidationRuleGroupId')
- expect(spy).toHaveBeenCalledWith(
- null,
- null,
- 'TestValidationRuleGroupId'
- )
- expect(wrapper.state('validationRuleGroupId')).toBe(
- 'TestValidationRuleGroupId'
- )
- })
-
- it('Should call updateSendNotifications when "Send notifications" checkbox change.', () => {
- const spy = spyOn(
- ValidationRulesAnalysis.prototype,
- 'updateSendNotifications'
- ).and.callThrough()
- const wrapper = ownShallow()
- wrapper.setState({
- notification: null,
- })
- wrapper
- .find(Checkbox)
- .at(0)
- .simulate('check', null, true)
- expect(spy).toHaveBeenCalledWith(null, true)
- expect(wrapper.state('notification')).toBeTruthy()
- })
-
- it('Should call updatePersistNewResults when "Persist new results" checkbox change.', () => {
- const spy = spyOn(
- ValidationRulesAnalysis.prototype,
- 'updatePersistNewResults'
- ).and.callThrough()
- const wrapper = ownShallow()
- wrapper.setState({
- persist: null,
- })
- wrapper
- .find(Checkbox)
- .at(1)
- .simulate('check', null, true)
- expect(spy).toHaveBeenCalledWith(null, true)
- expect(wrapper.state('persist')).toBeTruthy()
- })
-
- it('Should call validate function when Validate button is clicked.', () => {
- const spy = spyOn(ValidationRulesAnalysis.prototype, 'validate')
- const wrapper = ownShallow()
- wrapper.find(RaisedButton).simulate('click')
- expect(spy).toHaveBeenCalled()
- })
-
- it('Calls back method when IconButton (back) is clicked', () => {
- const spy = spyOn(ValidationRulesAnalysis.prototype, 'back')
- const wrapper = ownShallow()
- wrapper.find(IconButton).simulate('click')
- expect(spy).toHaveBeenCalled()
- })
-
- it('Update state when back button is clicked', () => {
- const wrapper = ownShallow()
- wrapper.setState({ showTable: true })
- wrapper.find(IconButton).simulate('click')
- expect(wrapper.state('showTable')).toBe(false)
- })
-})
diff --git a/src/pages/validation-rules-analysis/validation-rules-analysis-table/ValidationRulesAnalysisTable.js b/src/pages/validation-rules-analysis/validation-rules-analysis-table/ValidationRulesAnalysisTable.js
index 367bf76f..3a06d36e 100644
--- a/src/pages/validation-rules-analysis/validation-rules-analysis-table/ValidationRulesAnalysisTable.js
+++ b/src/pages/validation-rules-analysis/validation-rules-analysis-table/ValidationRulesAnalysisTable.js
@@ -1,5 +1,4 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
+import i18n from '@dhis2/d2-i18n'
import classNames from 'classnames'
import {
Table,
@@ -9,14 +8,15 @@ import {
TableRow,
TableRowColumn,
} from 'material-ui'
-import FormattedNumber from '../../../components/formatters/FormattedNumber'
+import PropTypes from 'prop-types'
+import React, { PureComponent } from 'react'
import DownloadAs from '../../../components/download-as/DownloadAs'
+import FormattedNumber from '../../../components/formatters/FormattedNumber'
+import TableCellContent from '../../../components/table/TableCellContent'
+import { apiConf } from '../../../server.conf'
import cssPageStyles from '../../Page.module.css'
-import styles from './ValidationRulesAnalysisTable.module.css'
-import i18n from '@dhis2/d2-i18n'
import ValidationRulesDetails from '../validation-rules-details/ValidationRulesDetails'
-import { apiConf } from '../../../server.conf'
-import TableCellContent from '../../../components/table/TableCellContent'
+import styles from './ValidationRulesAnalysisTable.module.css'
class ValidationRulesAnalysisTable extends PureComponent {
static propTypes = {
diff --git a/src/pages/validation-rules-analysis/validation-rules-analysis-table/ValidationRulesAnalysisTable.test.js b/src/pages/validation-rules-analysis/validation-rules-analysis-table/ValidationRulesAnalysisTable.test.js
deleted file mode 100644
index 91e65095..00000000
--- a/src/pages/validation-rules-analysis/validation-rules-analysis-table/ValidationRulesAnalysisTable.test.js
+++ /dev/null
@@ -1,138 +0,0 @@
-/* eslint-disable */
-import React from 'react'
-import { mount, shallow } from 'enzyme'
-import ValidationRulesAnalysisTable from '../validation-rules-analysis-table/ValidationRulesAnalysisTable'
-import { MuiThemeProvider, TableRow, TableRowColumn } from 'material-ui'
-import ValidationRulesDetails from '../validation-rules-details/ValidationRulesDetails'
-import DownloadAs from '../../../components/download-as/DownloadAs'
-
-jest.mock(
- 'd2-ui/lib/feedback-snackbar/FeedbackSnackbarTypes',
- () => 'FeedbackSnackbarTypes'
-)
-jest.mock('d2-ui/lib/org-unit-tree/OrgUnitTree.component', () => 'OrgUnitTree')
-
-const rulesElements = [
- {
- key: 'kone',
- organisation: 'Ngieyehun MCHP',
- importance: 'MEDIUM',
- leftValue: 10,
- operator: '>',
- rightValue: 10,
- period: 'January 2017',
- periodId: '201701',
- validationRule: 'Malaria outbrek',
- validationRuleId: 'kgh54Xb9LSE',
- organisationUnitId: 'OrgUnitkgh54Xb9LSE',
- attributeOptionCombo: 'default',
- attributeOptionComboId: 'HllvX50cXC0',
- },
- {
- key: 'ktwo',
- organisation: 'Mattru on the Rail MCHP',
- importance: 'MEDIUM',
- leftValue: 14,
- operator: '>',
- rightValue: 9,
- period: 'February 2017',
- periodId: '201702',
- validationRule: 'Malaria outbrek',
- validationRuleId: 'kgh54Xb9LSS',
- organisationUnitId: 'OrgUnitkgh54Xb9LSS',
- attributeOptionCombo: 'default',
- attributeOptionComboId: 'HllvX50cXC0',
- },
-]
-
-const rulesElementsWithAttrOptCombos = [
- {
- attributeOptionCombo: 'default',
- attributeOptionComboId: 'HllvX50cXC0',
- importance: 'MEDIUM',
- key: 'SxwKDzs5Rlb-202010-rZxk3S0qN63-HllvX50cXC0',
- leftValue: 11,
- operator: '==',
- organisation: 'Bo Govt. Hosp.',
- organisationUnitId: 'rZxk3S0qN63',
- period: 'October 2020',
- periodId: '202010',
- rightValue: 5,
- validationRule: 'ART stage 1 = stage 2',
- validationRuleId: 'SxwKDzs5Rlb',
- },
- {
- key: 'SxwKDzs5Rlb-202010-DiszpKrYNg8-vp1qSLVMSc3',
- validationRuleId: 'SxwKDzs5Rlb',
- attributeOptionCombo:
- 'African Medical and Research Foundation, Provide access to basic education',
- attributeOptionComboId: 'vp1qSLVMSc3',
- organisation: 'Ngelehun CHC',
- organisationUnitId: 'DiszpKrYNg8',
- period: 'October 2020',
- periodId: '202010',
- importance: 'MEDIUM',
- validationRule: 'ART stage 1 = stage 2',
- leftValue: 23,
- operator: '==',
- rightValue: 0,
- },
-]
-
-const ownShallow = () => {
- return shallow(, {
- disableLifecycleMethods: true,
- })
-}
-
-const withAttrOptCombos = () => {
- return shallow(
- ,
- {
- disableLifecycleMethods: true,
- }
- )
-}
-describe('Test rendering:', () => {
- let wrapper
- beforeEach(() => {
- wrapper = ownShallow()
- })
-
- it('ValidationRulesAnalysisTable renders without crashing.', () => {
- ownShallow()
- })
-
- it('Should render correct number of rows.', () => {
- expect(wrapper.find(TableRow).length).toBe(3) // Two elements plus header row
- })
-
- it('Should render correct number of columns.', () => {
- expect(
- wrapper
- .find(TableRow)
- .at(1)
- .find(TableRowColumn).length
- ).toBe(8) // First row after header
- })
-
- it('Should render "ValidationRulesDetails" for each row.', () => {
- expect(wrapper.find(ValidationRulesDetails).length).toBe(2)
- })
-
- it('Render "DownloadAs" component.', () => {
- expect(wrapper.find(DownloadAs).length).toBe(2)
- })
-
- it('Should render attribute option combo column if attribute opt. combo other than default is present', () => {
- expect(
- withAttrOptCombos()
- .find(TableRow)
- .at(0)
- .childAt(1)
- .prop('title')
- ).toBe('Attribute Option Combination')
- })
-})
diff --git a/src/pages/validation-rules-analysis/validation-rules-details/ValidationRulesDetails.js b/src/pages/validation-rules-analysis/validation-rules-details/ValidationRulesDetails.js
index ac985ca1..62e22b2f 100644
--- a/src/pages/validation-rules-analysis/validation-rules-details/ValidationRulesDetails.js
+++ b/src/pages/validation-rules-analysis/validation-rules-details/ValidationRulesDetails.js
@@ -1,24 +1,24 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { Dialog, FlatButton, FontIcon } from 'material-ui'
-import classNames from 'classnames'
import i18n from '@dhis2/d2-i18n'
-import Page from '../../Page'
-import jsPageStyles from '../../PageStyles'
-import cssPageStyles from '../../Page.module.css'
-import styles from './ValidationRulesDetails.module.css'
+import classNames from 'classnames'
+import { Dialog, FlatButton, FontIcon } from 'material-ui'
+import PropTypes from 'prop-types'
+import React from 'react'
import FormattedNumber from '../../../components/formatters/FormattedNumber'
import { apiConf } from '../../../server.conf'
+import Page from '../../Page'
+import cssPageStyles from '../../Page.module.css'
+import jsPageStyles from '../../PageStyles'
import ValidationRulesAnalysis from '../ValidationRulesAnalysis'
+import styles from './ValidationRulesDetails.module.css'
class ValidationRulesDetails extends Page {
static STATE_PROPERTIES = ['loading']
static propTypes = {
- validationRuleId: PropTypes.string.isRequired,
- periodId: PropTypes.string.isRequired,
- organisationUnitId: PropTypes.string.isRequired,
attributeOptionComboId: PropTypes.string.isRequired,
+ organisationUnitId: PropTypes.string.isRequired,
+ periodId: PropTypes.string.isRequired,
+ validationRuleId: PropTypes.string.isRequired,
}
constructor() {
@@ -38,29 +38,22 @@ class ValidationRulesDetails extends Page {
this.handleClose = this.handleClose.bind(this)
}
- componentWillReceiveProps(nextProps) {
+ UNSAFE_componentWillReceiveProps(nextProps) {
const nextState = {}
Object.keys(nextProps).forEach(property => {
- if (
- nextProps.hasOwnProperty(property) &&
- ValidationRulesAnalysis.STATE_PROPERTIES.includes(property)
- ) {
+ if (ValidationRulesAnalysis.STATE_PROPERTIES.includes(property)) {
nextState[property] = nextProps[property]
}
})
- if (nextState !== {}) {
- this.setState(nextState)
- }
+ this.setState(nextState)
}
loadDetails() {
if (!this.state.loading) {
const api = this.context.d2.Api.getApi()
- const requestRule = `${apiConf.endpoints.validationRules}/${
- this.props.validationRuleId
- }`
+ const requestRule = `${apiConf.endpoints.validationRules}/${this.props.validationRuleId}`
const requestExpression =
`${apiConf.endpoints.validationRulesExpression}` +
`?validationRuleId=${this.props.validationRuleId}` +
@@ -96,11 +89,9 @@ class ValidationRulesDetails extends Page {
const dialogActions = [
,
]
@@ -185,9 +176,7 @@ class ValidationRulesDetails extends Page {
return (