diff --git a/src/AppWrapper.js b/src/AppWrapper.js
index df06cbd256..75990f8817 100644
--- a/src/AppWrapper.js
+++ b/src/AppWrapper.js
@@ -1,21 +1,81 @@
-import { useConfig, useDataEngine } from '@dhis2/app-runtime'
-import { D2Shim } from '@dhis2/app-runtime-adapter-d2'
+import { CachedDataQueryProvider } from '@dhis2/analytics'
+import { useDataEngine } from '@dhis2/app-runtime'
import { DataStoreProvider } from '@dhis2/app-service-datastore'
import React from 'react'
import { Provider as ReduxProvider } from 'react-redux'
import thunk from 'redux-thunk'
-import { App } from './components/App.js'
-import UserSettingsProvider, {
- UserSettingsCtx,
-} from './components/UserSettingsProvider.js'
+import App from './components/App.js'
import configureStore from './configureStore.js'
import metadataMiddleware from './middleware/metadata.js'
import { USER_DATASTORE_NAMESPACE } from './modules/currentAnalyticalObject.js'
-import history from './modules/history.js'
+import { systemSettingsKeys } from './modules/systemSettings.js'
+import {
+ USER_SETTINGS_DISPLAY_PROPERTY,
+ DERIVED_USER_SETTINGS_DISPLAY_NAME_PROPERTY,
+} from './modules/userSettings.js'
import './locales/index.js'
+const query = {
+ currentUser: {
+ resource: 'me',
+ params: {
+ fields: 'id,username,displayName~rename(name),settings',
+ },
+ },
+ systemSettings: {
+ resource: 'systemSettings',
+ params: {
+ key: systemSettingsKeys,
+ },
+ },
+ rootOrgUnits: {
+ resource: 'organisationUnits',
+ params: {
+ fields: 'id,displayName,name',
+ userDataViewFallback: true,
+ paging: false,
+ },
+ },
+ orgUnitLevels: {
+ resource: 'organisationUnitLevels',
+ // TODO how to handle passing params like this?
+ params: ({ displayNameProp = 'displayName' } = {}) => ({
+ fields: `id,level,${displayNameProp}~rename(displayName),name`,
+ paging: false,
+ }),
+ },
+}
+
+const providerDataTransformation = ({
+ currentUser,
+ systemSettings,
+ rootOrgUnits,
+ orgUnitLevels,
+}) => {
+ const displayNameProperty =
+ currentUser.settings[USER_SETTINGS_DISPLAY_PROPERTY] === 'name'
+ ? 'displayName'
+ : 'displayShortName'
+
+ return {
+ currentUser: {
+ ...currentUser,
+ settings: {
+ uiLocale: currentUser.settings.keyUiLocale,
+ displayProperty:
+ currentUser.settings[USER_SETTINGS_DISPLAY_PROPERTY],
+ displayNameProperty,
+ [DERIVED_USER_SETTINGS_DISPLAY_NAME_PROPERTY]:
+ displayNameProperty,
+ },
+ },
+ systemSettings,
+ rootOrgUnits: rootOrgUnits.organisationUnits,
+ orgUnitLevels: orgUnitLevels.organisationUnitLevels,
+ }
+}
+
const AppWrapper = () => {
- const { baseUrl } = useConfig()
const engine = useDataEngine()
const store = configureStore([
thunk.withExtraArgument(engine),
@@ -26,43 +86,15 @@ const AppWrapper = () => {
window.store = store
}
- const schemas = ['visualization', 'organisationUnit', 'userGroup']
- const d2Config = {
- schemas,
- }
-
return (
-
-
- {({ userSettings }) => {
- return userSettings?.uiLocale ? (
-
- {({ d2 }) => {
- if (!d2) {
- // TODO: Handle errors in d2 initialization
- return null
- } else {
- return (
-
- )
- }
- }}
-
- ) : null
- }}
-
-
+
+
+
)
diff --git a/src/actions/current.js b/src/actions/current.js
index f950cf47e3..d7db8efce4 100644
--- a/src/actions/current.js
+++ b/src/actions/current.js
@@ -11,7 +11,7 @@ export const acSetCurrent = (value) => ({
value,
})
-export const acClear = () => ({
+export const acClearCurrent = () => ({
type: CLEAR_CURRENT,
})
diff --git a/src/actions/index.js b/src/actions/index.js
index 6e525c9d0e..eb3da5cd51 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -129,15 +129,15 @@ export const clearAll =
dispatch(fromLoader.acClearLoadError())
}
- dispatch(fromVisualization.acClear())
- dispatch(fromCurrent.acClear())
+ dispatch(fromVisualization.acClearVisualization())
+ dispatch(fromCurrent.acClearCurrent())
const rootOrganisationUnits = sGetRootOrgUnits(getState())
const relativePeriod = sGetRelativePeriod(getState())
const digitGroupSeparator = sGetSettingsDigitGroupSeparator(getState())
dispatch(
- fromUi.acClear({
+ fromUi.acClearUi({
rootOrganisationUnits,
relativePeriod,
digitGroupSeparator,
diff --git a/src/actions/ui.js b/src/actions/ui.js
index 5d1f04e8c4..890d8335ea 100644
--- a/src/actions/ui.js
+++ b/src/actions/ui.js
@@ -34,7 +34,7 @@ export const acSetUi = (value) => ({
value,
})
-export const acClear = (value) => ({
+export const acClearUi = (value) => ({
type: CLEAR_UI,
value,
})
diff --git a/src/actions/visualization.js b/src/actions/visualization.js
index 86a80ce800..c4ff28ed9e 100644
--- a/src/actions/visualization.js
+++ b/src/actions/visualization.js
@@ -14,6 +14,6 @@ export const acSetVisualization = (visualization) => {
}
}
-export const acClear = () => ({
+export const acClearVisualization = () => ({
type: CLEAR_VISUALIZATION,
})
diff --git a/src/components/App.js b/src/components/App.js
index 4a7f88efb7..339c3da348 100644
--- a/src/components/App.js
+++ b/src/components/App.js
@@ -1,4 +1,4 @@
-import { apiFetchOrganisationUnitLevels, Toolbar } from '@dhis2/analytics'
+import { useCachedDataQuery, Toolbar } from '@dhis2/analytics'
import { useSetting } from '@dhis2/app-service-datastore'
import i18n from '@dhis2/d2-i18n'
import {
@@ -10,17 +10,28 @@ import {
ButtonStrip,
Button,
} from '@dhis2/ui'
-import PropTypes from 'prop-types'
-import React, { Component } from 'react'
-import { connect } from 'react-redux'
-import * as fromActions from '../actions/index.js'
+import React, { useEffect, useCallback, useRef, useState } from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+import {
+ acClearCurrent,
+ acSetCurrentFromUi,
+ tSetCurrentFromUi,
+} from '../actions/current.js'
+import { tSetDimensions } from '../actions/dimensions.js'
+import { clearAll, tDoLoadVisualization } from '../actions/index.js'
+import { acAddMetadata } from '../actions/metadata.js'
+import { acAddSettings } from '../actions/settings.js'
+import { acAddParentGraphMap, acSetUiFromVisualization } from '../actions/ui.js'
+import { acReceivedUser, tLoadUserAuthority } from '../actions/user.js'
+import { acClearVisualization } from '../actions/visualization.js'
import { Snackbar } from '../components/Snackbar/Snackbar.js'
import { USER_DATASTORE_CURRENT_AO_KEY } from '../modules/currentAnalyticalObject.js'
import history from '../modules/history.js'
import defaultMetadata from '../modules/metadata.js'
import { getParentGraphMapFromVisualization } from '../modules/ui.js'
import { STATE_DIRTY, getVisualizationState } from '../modules/visualization.js'
-import * as fromReducers from '../reducers/index.js'
+import { sGetCurrent } from '../reducers/current.js'
+import { sGetUi } from '../reducers/ui.js'
import { sGetVisualization } from '../reducers/visualization.js'
import { default as DetailsPanel } from './DetailsPanel/DetailsPanel.js'
import DimensionsPanel from './DimensionsPanel/DimensionsPanel.js'
@@ -35,374 +46,331 @@ import { VisualizationTypeSelector } from './VisualizationTypeSelector/Visualiza
import './App.css'
import './scrollbar.css'
-export class UnconnectedApp extends Component {
- unlisten = null
-
- apiObjectName = 'visualization'
-
- interpretationsUnitRef = React.createRef()
+const App = () => {
+ const [currentAO] = useSetting(USER_DATASTORE_CURRENT_AO_KEY)
- onInterpretationUpdate = () =>
- this.interpretationsUnitRef.current?.refresh()
+ const [previousLocation, setPreviousLocation] = useState(null)
+ const [initialLoadIsComplete, setInitialLoadIsComplete] = useState(false)
+ const [locationToConfirm, setLocationToConfirm] = useState(false)
- state = {
- previousLocation: null,
- initialLoadIsComplete: false,
- locationToConfirm: false,
+ const dispatch = useDispatch()
- ouLevels: null,
- }
-
- fetchOuLevels = async () => {
- const ouLevels = await apiFetchOrganisationUnitLevels(
- this.props.dataEngine
- )
+ const current = useSelector(sGetCurrent)
+ const ui = useSelector(sGetUi)
+ const visualization = useSelector(sGetVisualization)
- this.setState({ ouLevels: ouLevels })
- }
+ const { currentUser, systemSettings, orgUnitLevels, rootOrgUnits } =
+ useCachedDataQuery()
- /**
- * The following cases require a fetch/refetch of the AO
- * - enter a new url (causing a page load)
- * - file->open (same or different AO)
- * - file->saveAs
- */
- refetch = (location) => {
- if (!this.state.previousLocation) {
- return true
- }
-
- const id = location.pathname.slice(1).split('/')[0]
- const prevId = this.state.previousLocation.slice(1).split('/')[0]
-
- if (
- id !== prevId ||
- this.state.previousLocation === location.pathname
- ) {
- return true
- }
-
- return false
+ const interpretationsUnitRef = useRef()
+ const onInterpretationUpdate = () => {
+ interpretationsUnitRef.current.refresh()
}
- parseLocation = (location) => {
+ const parseLocation = (location) => {
const pathParts = location.pathname.slice(1).split('/')
const id = pathParts[0]
const interpretationId = pathParts[2]
return { id, interpretationId }
}
- loadVisualization = async (location) => {
- if (location.pathname.length > 1) {
- // /currentAnalyticalObject
- // /${id}/
- // /${id}/interpretation/${interpretationId}
- const { id } = this.parseLocation(location)
-
- const urlContainsCurrentAOKey = id === USER_DATASTORE_CURRENT_AO_KEY
-
- if (urlContainsCurrentAOKey) {
- this.props.addParentGraphMap(
- getParentGraphMapFromVisualization(this.props.currentAO)
- )
+ const loadVisualization = useCallback(
+ (location) => {
+ /**
+ * The following cases require a fetch/refetch of the AO
+ * - enter a new url (causing a page load)
+ * - file->open (same or different AO)
+ * - file->saveAs
+ */
+ const isRefetchNeeded = (location) => {
+ if (!previousLocation) {
+ return true
+ }
- // clear visualization and current
- // to avoid leave them "dirty" when navigating to
- // /currentAnalyticalObject from a previously saved AO
- this.props.clearVisualization()
- this.props.clearCurrent()
+ const id = location.pathname.slice(1).split('/')[0]
+ const prevId = previousLocation.slice(1).split('/')[0]
- this.props.setUiFromVisualization(this.props.currentAO)
- this.props.setCurrentFromUi()
- }
+ if (id !== prevId || previousLocation === location.pathname) {
+ return true
+ }
- if (!urlContainsCurrentAOKey && this.refetch(location)) {
- await this.props.setVisualization({
- id,
- ouLevels: this.state.ouLevels,
- })
+ return false
}
- } else {
- this.props.clearAll()
- }
- this.setState({ initialLoadIsComplete: true })
- this.setState({ previousLocation: location.pathname })
- }
- componentDidMount = async () => {
- const { d2, userSettings } = this.props
+ if (location.pathname.length > 1) {
+ // /currentAnalyticalObject
+ // /${id}/
+ // /${id}/interpretation/${interpretationId}
+ const { id } = parseLocation(location)
+
+ const urlContainsCurrentAOKey =
+ id === USER_DATASTORE_CURRENT_AO_KEY
+
+ if (urlContainsCurrentAOKey) {
+ dispatch(
+ acAddParentGraphMap(
+ getParentGraphMapFromVisualization(currentAO)
+ )
+ )
+
+ // clear visualization and current
+ // to avoid leave them "dirty" when navigating to
+ // /currentAnalyticalObject from a previously saved AO
+ dispatch(acClearVisualization())
+ dispatch(acClearCurrent())
+
+ dispatch(acSetUiFromVisualization(currentAO))
+ dispatch(tSetCurrentFromUi())
+ }
- await this.props.addSettings(userSettings)
- this.props.setUser(d2.currentUser)
- this.props.loadUserAuthority(APPROVAL_LEVEL_OPTION_AUTH)
- this.props.setDimensions()
+ if (!urlContainsCurrentAOKey && isRefetchNeeded(location)) {
+ dispatch(
+ tDoLoadVisualization({
+ id,
+ ouLevels: orgUnitLevels,
+ })
+ )
+ }
+ } else {
+ dispatch(clearAll()) // XXX
+ }
+ setInitialLoadIsComplete(true)
+ setPreviousLocation(location.pathname)
+ },
+ [
+ currentAO,
+ dispatch,
+ orgUnitLevels,
+ previousLocation,
+ setPreviousLocation,
+ ]
+ )
+
+ useEffect(
+ () => {
+ dispatch(
+ // XXX see how to write this better
+ acAddSettings({
+ ...systemSettings,
+ uiLocale: currentUser.settings.uiLocale,
+ displayProperty: currentUser.settings.displayProperty,
+ displayNameProperty:
+ currentUser.settings.displayNameProperty,
+ rootOrganisationUnits: rootOrgUnits,
+ })
+ )
+ dispatch(tLoadUserAuthority('ALL'))
+ dispatch(tLoadUserAuthority(APPROVAL_LEVEL_OPTION_AUTH))
+ dispatch(acReceivedUser(currentUser))
+ dispatch(tSetDimensions())
+
+ const metaData = { ...defaultMetadata() }
+
+ rootOrgUnits.forEach((rootOrgUnit) => {
+ if (rootOrgUnit.id) {
+ metaData[rootOrgUnit.id] = {
+ ...rootOrgUnit,
+ path: `/${rootOrgUnit.id}`,
+ }
+ }
+ })
- await this.fetchOuLevels()
+ dispatch(acAddMetadata(metaData))
- const rootOrgUnits = this.props.settings.rootOrganisationUnits
+ loadVisualization(history.location)
- const metaData = { ...defaultMetadata() }
+ const unlisten = history.listen(({ location }) => {
+ const isSaving = location.state?.isSaving
+ const isOpening = location.state?.isOpening
+ const isResetting = location.state?.isResetting
+ const isModalOpening = location.state?.isModalOpening
+ const isModalClosing = location.state?.isModalClosing
+ const isValidLocationChange =
+ previousLocation !== location.pathname &&
+ !isModalOpening &&
+ !isModalClosing
- rootOrgUnits.forEach((rootOrgUnit) => {
- if (rootOrgUnit.id) {
- metaData[rootOrgUnit.id] = {
- ...rootOrgUnit,
- path: `/${rootOrgUnit.id}`,
- }
- }
- })
-
- this.props.addMetadata(metaData)
-
- this.loadVisualization(this.props.location)
-
- this.unlisten = history.listen(({ location }) => {
- const isSaving = location.state?.isSaving
- const isOpening = location.state?.isOpening
- const isResetting = location.state?.isResetting
- /*
- const isModalOpening = location.state?.isModalOpening
- const isModalClosing = location.state?.isModalClosing
- const isValidLocationChange =
- this.state.previousLocation !== location.pathname &&
- !isModalOpening &&
- !isModalClosing
-*/
- if (
- // currently editing
- getVisualizationState(
- this.props.visualization,
- this.props.current
- ) === STATE_DIRTY &&
- // wanting to navigate elsewhere
- this.state.previousLocation !== location.pathname &&
- // not saving
- !isSaving
- ) {
- this.setState({ locationToConfirm: location })
- } else {
if (
- isSaving ||
- isOpening ||
- isResetting ||
- this.state.previousLocation !== location.pathname
+ // currently editing
+ getVisualizationState(visualization, current) ===
+ STATE_DIRTY &&
+ // wanting to navigate elsewhere
+ previousLocation !== location.pathname &&
+ // not saving
+ !isSaving
) {
- this.loadVisualization(location)
+ setLocationToConfirm(location)
+ } else {
+ if (
+ isSaving ||
+ isOpening ||
+ isResetting ||
+ isValidLocationChange
+ ) {
+ loadVisualization(location)
+ }
+
+ setLocationToConfirm(null)
}
+ })
- this.setState({ locationToConfirm: null })
- }
- })
-
- document.body.addEventListener(
- 'keyup',
- (e) =>
- e.key === 'Enter' &&
- e.ctrlKey === true &&
- this.props.setCurrentFromUi()
- )
-
- window.addEventListener('beforeunload', (event) => {
- if (
- getVisualizationState(
- this.props.visualization,
- this.props.current
- ) === STATE_DIRTY
- ) {
- event.preventDefault()
- event.returnValue = i18n.t('You have unsaved changes.')
- }
- })
- }
-
- componentWillUnmount() {
- if (this.unlisten) {
- this.unlisten()
- }
- }
+ document.body.addEventListener(
+ 'keyup',
+ (e) =>
+ e.key === 'Enter' &&
+ e.ctrlKey === true &&
+ dispatch(acSetCurrentFromUi(ui))
+ )
- getChildContext() {
- return {
- baseUrl: this.props.baseUrl,
- i18n,
- d2: this.props.d2,
- dataEngine: this.props.dataEngine,
- }
- }
-
- render() {
- return (
- <>
-
-
-
-
-
-
-
-
-
+ window.addEventListener('beforeunload', (event) => {
+ if (
+ getVisualizationState(visualization, current) ===
+ STATE_DIRTY
+ ) {
+ event.preventDefault()
+ event.returnValue = i18n.t('You have unsaved changes.')
+ }
+ })
+
+ return () => unlisten && unlisten()
+ },
+ [
+ // current,
+ // currentUser,
+ // dispatch,
+ // loadVisualization,
+ // previousLocation,
+ // rootOrgUnits,
+ // systemSettings,
+ // ui,
+ // visualization,
+ ]
+ )
+
+ // TODO continue from here
+
+ // this.unlisten = history.listen(({ location }) => {
+ // const isSaving = location.state?.isSaving
+ // const isOpening = location.state?.isOpening
+ // const isResetting = location.state?.isResetting
+ // /*
+ // const isModalOpening = location.state?.isModalOpening
+ // const isModalClosing = location.state?.isModalClosing
+ // const isValidLocationChange =
+ // this.state.previousLocation !== location.pathname &&
+ // !isModalOpening &&
+ // !isModalClosing
+ //*/
+ // if (
+ // // currently editing
+ // getVisualizationState(
+ // this.props.visualization,
+ // this.props.current
+ // ) === STATE_DIRTY &&
+ // // wanting to navigate elsewhere
+ // this.state.previousLocation !== location.pathname &&
+ // // not saving
+ // !isSaving
+ // ) {
+ // this.setState({ locationToConfirm: location })
+ // } else {
+ // if (
+ // isSaving ||
+ // isOpening ||
+ // isResetting ||
+ // this.state.previousLocation !== location.pathname
+ // ) {
+ // this.loadVisualization(location)
+ // }
+ //
+ // this.setState({ locationToConfirm: null })
+ // }
+ // })
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
- {this.state.initialLoadIsComplete && (
-
- )}
- {this.props.current && (
-
- )}
-
+
+
-
- {this.props.ui.rightSidebarOpen && this.props.current && (
-
-
+
+ {initialLoadIsComplete && }
+ {current && (
+
+ )}
- )}
-
+
+
+ {ui.rightSidebarOpen && current && (
+
+
+
+ )}
- {this.state.locationToConfirm && (
-
-
- {i18n.t('Discard unsaved changes?')}
-
-
- {i18n.t(
- 'Are you sure you want to leave this visualization? Any unsaved changes will be lost.'
- )}
-
-
-
-
-
-
-
-
-
- )}
-
-
- >
- )
- }
-}
-
-const mapStateToProps = (state) => ({
- settings: fromReducers.fromSettings.sGetSettings(state),
- current: fromReducers.fromCurrent.sGetCurrent(state),
- ui: fromReducers.fromUi.sGetUi(state),
- visualization: sGetVisualization(state),
- snackbar: fromReducers.fromSnackbar.sGetSnackbar(state),
-})
-
-const mapDispatchToProps = {
- setCurrentFromUi: fromActions.fromCurrent.tSetCurrentFromUi,
- clearVisualization: fromActions.fromVisualization.acClear,
- clearCurrent: fromActions.fromCurrent.acClear,
- setUiFromVisualization: fromActions.fromUi.acSetUiFromVisualization,
- addParentGraphMap: fromActions.fromUi.acAddParentGraphMap,
- clearSnackbar: fromActions.fromSnackbar.acClearSnackbar,
- addSettings: fromActions.fromSettings.tAddSettings,
- setUser: fromActions.fromUser.acReceivedUser,
- loadUserAuthority: fromActions.fromUser.tLoadUserAuthority,
- setDimensions: fromActions.fromDimensions.tSetDimensions,
- addMetadata: fromActions.fromMetadata.acAddMetadata,
- setVisualization: fromActions.tDoLoadVisualization,
- clearAll: fromActions.clearAll,
-}
-
-UnconnectedApp.contextTypes = {
- store: PropTypes.object,
-}
-
-UnconnectedApp.childContextTypes = {
- d2: PropTypes.object,
- dataEngine: PropTypes.object,
- baseUrl: PropTypes.string,
- i18n: PropTypes.object,
-}
-
-UnconnectedApp.propTypes = {
- addMetadata: PropTypes.func,
- addParentGraphMap: PropTypes.func,
- addSettings: PropTypes.func,
- baseUrl: PropTypes.string,
- clearAll: PropTypes.func,
- clearCurrent: PropTypes.func,
- clearVisualization: PropTypes.func,
- current: PropTypes.object,
- currentAO: PropTypes.object,
- d2: PropTypes.object,
- dataEngine: PropTypes.object,
- loadUserAuthority: PropTypes.func,
- location: PropTypes.object,
- setCurrentFromUi: PropTypes.func,
- setDimensions: PropTypes.func,
- setUiFromVisualization: PropTypes.func,
- setUser: PropTypes.func,
- setVisualization: PropTypes.func,
- settings: PropTypes.object,
- ui: PropTypes.object,
- userSettings: PropTypes.object,
- visualization: PropTypes.object,
-}
-
-const withCurrentAO = (Component) => {
- return function WrappedComponent(props) {
- const [currentAO] = useSetting(USER_DATASTORE_CURRENT_AO_KEY)
-
- return
- }
+
+ {locationToConfirm && (
+
+
+ {i18n.t('Discard unsaved changes?')}
+
+
+ {i18n.t(
+ 'Are you sure you want to leave this visualization? Any unsaved changes will be lost.'
+ )}
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ >
+ )
}
-export const App = connect(
- mapStateToProps,
- mapDispatchToProps
-)(withCurrentAO(UnconnectedApp))
+export default App
diff --git a/src/components/DetailsPanel/DetailsPanel.js b/src/components/DetailsPanel/DetailsPanel.js
index 604a69f72c..6807404d38 100644
--- a/src/components/DetailsPanel/DetailsPanel.js
+++ b/src/components/DetailsPanel/DetailsPanel.js
@@ -1,4 +1,8 @@
-import { AboutAOUnit, InterpretationsUnit } from '@dhis2/analytics'
+import {
+ AboutAOUnit,
+ InterpretationsUnit,
+ useCachedDataQuery,
+} from '@dhis2/analytics'
import PropTypes from 'prop-types'
import { stringify } from 'query-string'
import React from 'react'
@@ -18,30 +22,27 @@ const navigateToOpenModal = (interpretationId, initialFocus) => {
)
}
-const DetailsPanel = (
- { interpretationsUnitRef, visualization, disabled },
- context
-) => (
-
-
-
- navigateToOpenModal(interpretationId)
- }
- onReplyIconClick={(interpretationId) =>
- navigateToOpenModal(interpretationId, true)
- }
- disabled={disabled}
- />
-
-)
+const DetailsPanel = ({ interpretationsUnitRef, visualization, disabled }) => {
+ const { currentUser } = useCachedDataQuery()
-DetailsPanel.contextTypes = {
- d2: PropTypes.object,
+ return (
+
+
+
+ navigateToOpenModal(interpretationId)
+ }
+ onReplyIconClick={(interpretationId) =>
+ navigateToOpenModal(interpretationId, true)
+ }
+ disabled={disabled}
+ />
+
+ )
}
DetailsPanel.propTypes = {
diff --git a/src/components/DimensionsPanel/Dialogs/DialogManager.js b/src/components/DimensionsPanel/Dialogs/DialogManager.js
index e3844578d0..ace1d08308 100644
--- a/src/components/DimensionsPanel/Dialogs/DialogManager.js
+++ b/src/components/DimensionsPanel/Dialogs/DialogManager.js
@@ -57,11 +57,6 @@ import {
} from '../../../modules/ui.js'
import { sGetDimensions } from '../../../reducers/dimensions.js'
import { sGetMetadata } from '../../../reducers/metadata.js'
-import {
- sGetRootOrgUnits,
- sGetSettings,
- sGetSettingsDisplayNameProperty,
-} from '../../../reducers/settings.js'
import {
sGetUiItems,
sGetUiItemsByDimension,
@@ -125,7 +120,7 @@ export class DialogManager extends Component {
fetchRecommended = debounce(async () => {
const ids = await apiFetchRecommendedIds(
- this.context.dataEngine,
+ this.props.dataEngine,
this.props.dxIds,
this.props.ouIds
)
@@ -529,12 +524,9 @@ export class DialogManager extends Component {
}
}
-DialogManager.contextTypes = {
- dataEngine: PropTypes.object,
-}
-
DialogManager.propTypes = {
changeDialog: PropTypes.func.isRequired,
+ dataEngine: PropTypes.object.isRequired,
dimensionIdsInLayout: PropTypes.array.isRequired,
ouIds: PropTypes.array.isRequired,
setRecommendedIds: PropTypes.func.isRequired,
@@ -563,16 +555,13 @@ DialogManager.defaultProps = {
}
const mapStateToProps = (state) => ({
- displayNameProperty: sGetSettingsDisplayNameProperty(state),
dialogId: sGetUiActiveModalDialog(state),
dimensions: sGetDimensions(state),
metadata: sGetMetadata(state),
parentGraphMap: sGetUiParentGraphMap(state),
dxIds: sGetUiItemsByDimension(state, DIMENSION_ID_DATA),
ouIds: sGetUiItemsByDimension(state, DIMENSION_ID_ORGUNIT),
- rootOrgUnits: sGetRootOrgUnits(state),
selectedItems: sGetUiItems(state),
- settings: sGetSettings(state),
type: sGetUiType(state),
getAxisIdByDimensionId: (dimensionId) =>
sGetAxisIdByDimensionId(state, dimensionId),
diff --git a/src/components/DimensionsPanel/DimensionsPanel.js b/src/components/DimensionsPanel/DimensionsPanel.js
index 3fbb4408df..fe1fbc8333 100644
--- a/src/components/DimensionsPanel/DimensionsPanel.js
+++ b/src/components/DimensionsPanel/DimensionsPanel.js
@@ -3,7 +3,9 @@ import {
DIMENSION_ID_ASSIGNED_CATEGORIES,
DIMENSION_ID_DATA,
VIS_TYPE_SCATTER,
+ useCachedDataQuery,
} from '@dhis2/analytics'
+import { useDataEngine } from '@dhis2/app-runtime'
import { Layer, Popper } from '@dhis2/ui'
import PropTypes from 'prop-types'
import React, { useState } from 'react'
@@ -14,6 +16,7 @@ import {
acRemoveUiLayoutDimensions,
} from '../../actions/ui.js'
import { ITEM_ATTRIBUTE_VERTICAL } from '../../modules/ui.js'
+import { DERIVED_USER_SETTINGS_DISPLAY_NAME_PROPERTY } from '../../modules/userSettings.js'
import * as fromReducers from '../../reducers/index.js'
import { default as DialogManager } from './Dialogs/DialogManager.js'
import { default as DndDimensionsPanel } from './DndDimensionsPanel.js'
@@ -29,10 +32,13 @@ export const Dimensions = ({
ui,
onDimensionClick,
}) => {
+ const dataEngine = useDataEngine()
const [menuIsOpen, setMenuIsOpen] = useState(false)
const [dimensionId, setDimensionId] = useState(null)
const [ref, setRef] = useState()
+ const { rootOrgUnits, systemSettings, currentUser } = useCachedDataQuery()
+
const toggleMenu = () => {
if (menuIsOpen) {
setDimensionId(null)
@@ -89,7 +95,16 @@ export const Dimensions = ({
)}
-
+
)
}
diff --git a/src/components/DownloadMenu/ModalDownloadDropdown.module.css b/src/components/DownloadMenu/ModalDownloadDropdown.module.css
index 55c5ecaf22..99816df267 100644
--- a/src/components/DownloadMenu/ModalDownloadDropdown.module.css
+++ b/src/components/DownloadMenu/ModalDownloadDropdown.module.css
@@ -1,4 +1,3 @@
.container {
margin-top: var(--spacers-dp12);
- margin-bottom: var(--spacers-dp16);
}
diff --git a/src/components/InterpretationModal/InterpretationModal.js b/src/components/InterpretationModal/InterpretationModal.js
index 8a60ca4965..be5c945fb7 100644
--- a/src/components/InterpretationModal/InterpretationModal.js
+++ b/src/components/InterpretationModal/InterpretationModal.js
@@ -1,4 +1,7 @@
-import { InterpretationModal as AnalyticsInterpretationModal } from '@dhis2/analytics'
+import {
+ InterpretationModal as AnalyticsInterpretationModal,
+ useCachedDataQuery,
+} from '@dhis2/analytics'
import PropTypes from 'prop-types'
import React, { useState, useEffect } from 'react'
import { useSelector } from 'react-redux'
@@ -10,7 +13,8 @@ import {
removeInterpretationQueryParams,
} from './interpretationIdQueryParam.js'
-const InterpretationModal = ({ onInterpretationUpdate }, context) => {
+const InterpretationModal = ({ onInterpretationUpdate }) => {
+ const { currentUser } = useCachedDataQuery()
const { interpretationId, initialFocus } = useInterpretationQueryParams()
const [isVisualizationLoading, setIsVisualizationLoading] = useState(false)
const visualization = useSelector(sGetCurrent)
@@ -21,7 +25,7 @@ const InterpretationModal = ({ onInterpretationUpdate }, context) => {
return interpretationId ? (
{
) : null
}
-InterpretationModal.contextTypes = {
- d2: PropTypes.object,
-}
-
InterpretationModal.propTypes = {
onInterpretationUpdate: PropTypes.func.isRequired,
}
diff --git a/src/components/MenuBar/MenuBar.js b/src/components/MenuBar/MenuBar.js
index add7d03de8..e515d2b6ef 100644
--- a/src/components/MenuBar/MenuBar.js
+++ b/src/components/MenuBar/MenuBar.js
@@ -1,9 +1,10 @@
import {
FileMenu,
HoverMenuBar,
+ UpdateButton,
VIS_TYPE_GROUP_ALL,
VIS_TYPE_GROUP_CHARTS,
- UpdateButton,
+ useCachedDataQuery,
} from '@dhis2/analytics'
import i18n from '@dhis2/d2-i18n'
import PropTypes from 'prop-types'
@@ -49,7 +50,9 @@ const getOnSaveAs = (props) => (details) =>
const getOnDelete = (props) => () => props.onDeleteVisualization()
const getOnError = (props) => (error) => props.onError(error)
-const UnconnectedMenuBar = ({ dataTest, ...props }, context) => {
+const UnconnectedMenuBar = ({ dataTest, ...props }) => {
+ const { currentUser } = useCachedDataQuery()
+
const filterVisTypesByVersion = useVisTypesFilterByVersion()
const filterVisTypes = [
@@ -72,7 +75,7 @@ const UnconnectedMenuBar = ({ dataTest, ...props }, context) => {
/>
({
current: sGetCurrent(state),
visualization: sGetVisualization(state),
diff --git a/src/components/UserSettingsProvider.js b/src/components/UserSettingsProvider.js
deleted file mode 100644
index 5575535ebb..0000000000
--- a/src/components/UserSettingsProvider.js
+++ /dev/null
@@ -1,57 +0,0 @@
-import { useDataEngine } from '@dhis2/app-runtime'
-import PropTypes from 'prop-types'
-import React, { useContext, useState, useEffect, createContext } from 'react'
-
-export const userSettingsQuery = {
- resource: 'userSettings',
- params: {
- key: ['keyUiLocale', 'keyAnalysisDisplayProperty'],
- },
-}
-
-export const UserSettingsCtx = createContext({})
-
-const UserSettingsProvider = ({ children }) => {
- const [settings, setSettings] = useState([])
- const engine = useDataEngine()
-
- useEffect(() => {
- async function fetchData() {
- const { userSettings } = await engine.query({
- userSettings: userSettingsQuery,
- })
-
- const { keyAnalysisDisplayProperty, keyUiLocale, ...rest } =
- userSettings
-
- setSettings({
- ...rest,
- displayProperty: keyAnalysisDisplayProperty,
- displayNameProperty:
- keyAnalysisDisplayProperty === 'name'
- ? 'displayName'
- : 'displayShortName',
- uiLocale: keyUiLocale,
- })
- }
- fetchData()
- }, [engine])
-
- return (
-
- {children}
-
- )
-}
-
-UserSettingsProvider.propTypes = {
- children: PropTypes.node,
-}
-
-export default UserSettingsProvider
-
-export const useUserSettings = () => useContext(UserSettingsCtx)
diff --git a/src/components/Visualization/Visualization.js b/src/components/Visualization/Visualization.js
index f25e90ee08..a5d336db4c 100644
--- a/src/components/Visualization/Visualization.js
+++ b/src/components/Visualization/Visualization.js
@@ -2,6 +2,7 @@ import {
DIMENSION_ID_DATA,
VIS_TYPE_OUTLIER_TABLE,
VIS_TYPE_PIVOT_TABLE,
+ useCachedDataQuery,
} from '@dhis2/analytics'
import debounce from 'lodash-es/debounce'
import PropTypes from 'prop-types'
diff --git a/src/components/VisualizationTypeSelector/VisualizationTypeSelector.js b/src/components/VisualizationTypeSelector/VisualizationTypeSelector.js
index 0d00f2ea43..8f913dadeb 100644
--- a/src/components/VisualizationTypeSelector/VisualizationTypeSelector.js
+++ b/src/components/VisualizationTypeSelector/VisualizationTypeSelector.js
@@ -1,4 +1,5 @@
import { visTypeDisplayNames, ToolbarSidebar } from '@dhis2/analytics'
+import { useConfig } from '@dhis2/app-runtime'
import { useSetting } from '@dhis2/app-service-datastore'
import i18n from '@dhis2/d2-i18n'
import { Divider, Popper, Layer } from '@dhis2/ui'
@@ -27,11 +28,15 @@ import VisualizationTypeListItem from './VisualizationTypeListItem.js'
export const MAPS_APP_URL = 'dhis-web-maps'
-const UnconnectedVisualizationTypeSelector = (
- { visualizationType, ui, setUi, onItemClick, current, metadata },
- context
-) => {
- const baseUrl = context.baseUrl
+const UnconnectedVisualizationTypeSelector = ({
+ visualizationType,
+ ui,
+ setUi,
+ onItemClick,
+ current,
+ metadata,
+}) => {
+ const { baseUrl } = useConfig()
const filterVisTypesByVersion = useVisTypesFilterByVersion()
const [, /* actual value not used */ { set }] = useSetting(
@@ -150,10 +155,6 @@ UnconnectedVisualizationTypeSelector.propTypes = {
onItemClick: PropTypes.func,
}
-UnconnectedVisualizationTypeSelector.contextTypes = {
- baseUrl: PropTypes.string,
-}
-
const mapStateToProps = (state) => ({
visualizationType: sGetUiType(state),
current: sGetCurrent(state),
diff --git a/src/modules/fields/baseFields.js b/src/modules/fields/baseFields.js
index a96ab2f166..2210edc112 100644
--- a/src/modules/fields/baseFields.js
+++ b/src/modules/fields/baseFields.js
@@ -100,7 +100,6 @@ export const fieldsByType = {
getFieldObject('filters'),
getFieldObject('hideSubtitle', { option: true }),
getFieldObject('hideTitle', { option: true }),
- getFieldObject('href'), // required for translations via analytics FileMenu and d2-ui TranslationDialog
getFieldObject('id'),
getFieldObject('interpretations'),
getFieldObject('itemOrganisationUnitGroups', { excluded: true }),
diff --git a/src/modules/systemSettings.js b/src/modules/systemSettings.js
new file mode 100644
index 0000000000..f58126c0cf
--- /dev/null
+++ b/src/modules/systemSettings.js
@@ -0,0 +1,23 @@
+const SYSTEM_SETTING_DATE_FORMAT = 'keyDateFormat'
+const SYSTEM_SETTINGS_RELATIVE_PERIOD = 'keyAnalysisRelativePeriod'
+export const SYSTEM_SETTINGS_DIGIT_GROUP_SEPARATOR =
+ 'keyAnalysisDigitGroupSeparator'
+export const SYSTEM_SETTINGS_HIDE_DAILY_PERIODS = 'keyHideDailyPeriods'
+export const SYSTEM_SETTINGS_HIDE_WEEKLY_PERIODS = 'keyHideWeeklyPeriods'
+export const SYSTEM_SETTINGS_HIDE_BIWEEKLY_PERIODS = 'keyHideBiWeeklyPeriods'
+export const SYSTEM_SETTINGS_HIDE_MONTHLY_PERIODS = 'keyHideMonthlyPeriods'
+export const SYSTEM_SETTINGS_HIDE_BIMONTHLY_PERIODS = 'keyHideBiMonthlyPeriods'
+export const SYSTEM_SETTINGS_IGNORE_ANALYTICS_APPROVAL_YEAR_THRESHOLD =
+ 'keyIgnoreAnalyticsApprovalYearThreshold'
+
+export const systemSettingsKeys = [
+ SYSTEM_SETTING_DATE_FORMAT,
+ SYSTEM_SETTINGS_RELATIVE_PERIOD,
+ SYSTEM_SETTINGS_DIGIT_GROUP_SEPARATOR,
+ SYSTEM_SETTINGS_HIDE_DAILY_PERIODS,
+ SYSTEM_SETTINGS_HIDE_WEEKLY_PERIODS,
+ SYSTEM_SETTINGS_HIDE_BIWEEKLY_PERIODS,
+ SYSTEM_SETTINGS_HIDE_MONTHLY_PERIODS,
+ SYSTEM_SETTINGS_HIDE_BIMONTHLY_PERIODS,
+ SYSTEM_SETTINGS_IGNORE_ANALYTICS_APPROVAL_YEAR_THRESHOLD,
+]
diff --git a/src/modules/userSettings.js b/src/modules/userSettings.js
new file mode 100644
index 0000000000..cb4899d9c6
--- /dev/null
+++ b/src/modules/userSettings.js
@@ -0,0 +1,5 @@
+export const USER_SETTINGS_UI_LOCALE = 'keyUiLocale'
+export const USER_SETTINGS_DISPLAY_PROPERTY = 'keyAnalysisDisplayProperty'
+
+export const DERIVED_USER_SETTINGS_DISPLAY_NAME_PROPERTY =
+ 'DERIVED_USER_SETTINGS_DISPLAY_NAME_PROPERTY'
diff --git a/src/reducers/user.js b/src/reducers/user.js
index aea4ab1b4a..951af8ea97 100644
--- a/src/reducers/user.js
+++ b/src/reducers/user.js
@@ -4,15 +4,13 @@ export const SET_USER_AUTHORITY = 'SET_USER_AUTHORITY'
export const DEFAULT_USER = {
id: '',
username: '',
- uiLocale: '',
- isSuperuser: false,
authorities: {},
}
export default (state = DEFAULT_USER, action) => {
switch (action.type) {
case RECEIVED_USER: {
- return fromD2ToUserObj(action.value)
+ return formatUserObject(action.value)
}
case SET_USER_AUTHORITY: {
return {
@@ -28,12 +26,10 @@ export default (state = DEFAULT_USER, action) => {
}
}
-function fromD2ToUserObj(d2Object) {
+function formatUserObject(userObject) {
return {
- id: d2Object.id,
- username: d2Object.username,
- uiLocale: d2Object.settings.keyUiLocale,
- isSuperuser: d2Object.authorities.has('ALL'),
+ id: userObject.id,
+ username: userObject.username,
}
}
@@ -43,6 +39,5 @@ export const sGetUser = (state) => state.user
export const sGetUserId = (state) => sGetUser(state).id
export const sGetUsername = (state) => sGetUser(state).username
-export const sGetIsSuperuser = (state) => sGetUser(state).isSuperuser
-export const sGetUiLocale = (state) => sGetUser(state).uiLocale
+export const sGetIsSuperuser = (state) => sGetUser(state).authoritie.has('ALL')
export const sGetUserAuthorities = (state) => sGetUser(state).authorities