diff --git a/cypress/integration/interpretations.cy.js b/cypress/integration/interpretations.cy.js
index 7662150da..4a1e54abe 100644
--- a/cypress/integration/interpretations.cy.js
+++ b/cypress/integration/interpretations.cy.js
@@ -67,4 +67,33 @@ context('Interpretations', () => {
deleteMap()
})
+
+ it('opens and closes the interpretation panel', () => {
+ cy.intercept({ method: 'POST', url: /dataStatistics/ }).as(
+ 'postDataStatistics'
+ )
+ cy.visit(
+ '/?id=ZBjCfSaLSqD&interpretationId=yKqhXZdeJ6a',
+ EXTENDED_TIMEOUT
+ ) //ANC: LLITN coverage district and facility
+
+ cy.wait('@postDataStatistics')
+ .its('response.statusCode')
+ .should('eq', 201)
+
+ cy.getByDataTest('interpretation-modal')
+ .find('h1')
+ .contains(
+ 'Viewing interpretation: ANC: LLITN coverage district and facility'
+ )
+ .should('be.visible')
+
+ cy.getByDataTest('interpretation-modal')
+ .findByDataTest('dhis2-modal-close-button')
+ .click()
+
+ cy.getByDataTest('interpretation-modal').should('not.exist')
+
+ cy.getByDataTest('interpretations-list').should('be.visible')
+ })
})
diff --git a/cypress/integration/layers/thematiclayer.cy.js b/cypress/integration/layers/thematiclayer.cy.js
index 4dacacc0b..d9966fc7d 100644
--- a/cypress/integration/layers/thematiclayer.cy.js
+++ b/cypress/integration/layers/thematiclayer.cy.js
@@ -6,7 +6,7 @@ import {
expectContextMenuOptions,
} from '../../elements/map_context_menu.js'
import { ThematicLayer } from '../../elements/thematic_layer.js'
-import { CURRENT_YEAR, getApiBaseUrl } from '../../support/util.js'
+import { CURRENT_YEAR } from '../../support/util.js'
const INDICATOR_NAME = 'VCCT post-test counselling rate'
@@ -136,83 +136,4 @@ context('Thematic Layers', () => {
{ name: VIEW_PROFILE },
])
})
-
- // TODO - update demo database with calculations instead of creating on the fly
- it('adds a thematic layer with a calculation', () => {
- const timestamp = new Date().toUTCString().slice(-24, -4)
- const calculationName = `map calc ${timestamp}`
-
- // add a calculation
- cy.request('POST', `${getApiBaseUrl()}/api/expressionDimensionItems`, {
- name: calculationName,
- shortName: calculationName,
- expression: '#{fbfJHSPpUQD}/2',
- }).then((response) => {
- expect(response.status).to.eq(201)
-
- const calculationUid = response.body.response.uid
-
- // open thematic dialog
- cy.getByDataTest('add-layer-button').click()
- cy.getByDataTest('addlayeritem-thematic').click()
-
- // choose "Calculation" in item type
- cy.getByDataTest('thematic-layer-value-type-select').click()
- cy.contains('Calculations').click()
-
- // assert that the label on the Calculation select is "Calculation"
- cy.getByDataTest('calculationselect-label').contains('Calculation')
-
- // click to open the calculation select
- cy.getByDataTest('calculationselect').click()
-
- // check search box exists "Type to filter options"
- cy.getByDataTest('dhis2-uicore-popper')
- .find('input[type="text"]')
- .should('have.attr', 'placeholder', 'Type to filter options')
-
- // search for something that doesn't exist
- cy.getByDataTest('dhis2-uicore-popper')
- .find('input[type="text"]')
- .type('foo')
-
- cy.getByDataTest('dhis2-uicore-select-menu-menuwrapper')
- .contains('No options found')
- .should('be.visible')
-
- // try search for something that exists
- cy.getByDataTest('dhis2-uicore-popper')
- .find('input[type="text"]')
- .clear()
-
- cy.getByDataTest('dhis2-uicore-popper')
- .find('input[type="text"]')
- .type(calculationName)
-
- cy.getByDataTest('dhis2-uicore-select-menu-menuwrapper')
- .contains(calculationName)
- .should('be.visible')
-
- // select the calculation and close dialog
- cy.contains(calculationName).click()
-
- cy.getByDataTest('dhis2-uicore-modalactions')
- .contains('Add layer')
- .click()
-
- // check the layer card title
- cy.getByDataTest('layercard')
- .contains(calculationName, { timeout: 50000 })
- .should('be.visible')
-
- // check the map canvas is displayed
- cy.get('canvas.maplibregl-canvas').should('be.visible')
-
- // delete the calculation
- cy.request(
- 'DELETE',
- `${getApiBaseUrl()}/api/expressionDimensionItems/${calculationUid}`
- )
- })
- })
})
diff --git a/cypress/integration/smoke.cy.js b/cypress/integration/smoke.cy.js
index 5a7ad16e3..be0d0f82d 100644
--- a/cypress/integration/smoke.cy.js
+++ b/cypress/integration/smoke.cy.js
@@ -38,4 +38,46 @@ context('Smoke Test', () => {
Layer.validateCardTitle('ANC 1 Coverage')
cy.get('canvas.maplibregl-canvas').should('be.visible')
})
+
+ it('loads with map id and interpretationid lowercase', () => {
+ cy.intercept({ method: 'POST', url: /dataStatistics/ }).as(
+ 'postDataStatistics'
+ )
+ cy.visit(
+ '/?id=ZBjCfSaLSqD&interpretationid=yKqhXZdeJ6a',
+ EXTENDED_TIMEOUT
+ ) //ANC: LLITN coverage district and facility
+
+ cy.wait('@postDataStatistics')
+ .its('response.statusCode')
+ .should('eq', 201)
+
+ cy.getByDataTest('interpretation-modal')
+ .find('h1')
+ .contains(
+ 'Viewing interpretation: ANC: LLITN coverage district and facility'
+ )
+ .should('be.visible')
+ })
+
+ it('loads with map id and interpretationId uppercase', () => {
+ cy.intercept({ method: 'POST', url: /dataStatistics/ }).as(
+ 'postDataStatistics'
+ )
+ cy.visit(
+ '/?id=ZBjCfSaLSqD&interpretationId=yKqhXZdeJ6a',
+ EXTENDED_TIMEOUT
+ ) //ANC: LLITN coverage district and facility
+
+ cy.wait('@postDataStatistics')
+ .its('response.statusCode')
+ .should('eq', 201)
+
+ cy.getByDataTest('interpretation-modal')
+ .find('h1')
+ .contains(
+ 'Viewing interpretation: ANC: LLITN coverage district and facility'
+ )
+ .should('be.visible')
+ })
})
diff --git a/i18n/cs.po b/i18n/cs.po
index 4e7ea0aad..74f0ce52e 100644
--- a/i18n/cs.po
+++ b/i18n/cs.po
@@ -24,15 +24,6 @@ msgstr "Mapa \"{{- name}}\" je uložena."
msgid "Failed to save map: {{message}}"
msgstr "Nepodařilo se uložit mapu: {{message}}"
-msgid "Calculation"
-msgstr "Výpočet"
-
-msgid "No calculations found"
-msgstr "Nenalezeny žádné výpočty"
-
-msgid "Calculations can be created in the Data Visualizer app."
-msgstr "Výpočty lze vytvářet v aplikaci Data Visualizer."
-
msgid "Classification"
msgstr "Klasifikace"
@@ -444,9 +435,6 @@ msgstr "Položka dat události je povinná"
msgid "Program indicator is required"
msgstr "Je vyžadován indikátor programu"
-msgid "Calculation is required"
-msgstr "Je vyžadován výpočet"
-
msgid "Period is required"
msgstr "Období je vyžadováno"
@@ -462,9 +450,6 @@ msgstr "Položky dat události"
msgid "Program indicators"
msgstr "Ukazatele programu"
-msgid "Calculations"
-msgstr "Výpočty"
-
msgid "Item type"
msgstr "Typ položky"
diff --git a/i18n/en.pot b/i18n/en.pot
index 86649bf58..fbce2a3d2 100644
--- a/i18n/en.pot
+++ b/i18n/en.pot
@@ -17,15 +17,6 @@ msgstr "Map \"{{- name}}\" is saved."
msgid "Failed to save map: {{message}}"
msgstr "Failed to save map: {{message}}"
-msgid "Calculation"
-msgstr "Calculation"
-
-msgid "No calculations found"
-msgstr "No calculations found"
-
-msgid "Calculations can be created in the Data Visualizer app."
-msgstr "Calculations can be created in the Data Visualizer app."
-
msgid "Classification"
msgstr "Classification"
@@ -430,9 +421,6 @@ msgstr "Event data item is required"
msgid "Program indicator is required"
msgstr "Program indicator is required"
-msgid "Calculation is required"
-msgstr "Calculation is required"
-
msgid "Period is required"
msgstr "Period is required"
@@ -448,9 +436,6 @@ msgstr "Event data items"
msgid "Program indicators"
msgstr "Program indicators"
-msgid "Calculations"
-msgstr "Calculations"
-
msgid "Item type"
msgstr "Item type"
diff --git a/i18n/es.po b/i18n/es.po
index da70c867b..68929d7f4 100644
--- a/i18n/es.po
+++ b/i18n/es.po
@@ -36,15 +36,6 @@ msgstr "El mapa \"{{- name}}\" está guardado."
msgid "Failed to save map: {{message}}"
msgstr "Error al guardar el mapa: {{message}}"
-msgid "Calculation"
-msgstr "Cálculo"
-
-msgid "No calculations found"
-msgstr ""
-
-msgid "Calculations can be created in the Data Visualizer app."
-msgstr ""
-
msgid "Classification"
msgstr "Clasificación"
@@ -454,9 +445,6 @@ msgstr "Se requiere el dato de evento"
msgid "Program indicator is required"
msgstr "Se requiere indicador de programa"
-msgid "Calculation is required"
-msgstr ""
-
msgid "Period is required"
msgstr "Período requerido"
@@ -472,9 +460,6 @@ msgstr "Datos de eventos"
msgid "Program indicators"
msgstr "Indicadores de programa"
-msgid "Calculations"
-msgstr "Cálculos"
-
msgid "Item type"
msgstr "Item type"
diff --git a/i18n/zh.po b/i18n/zh.po
index 8195b7435..8d9826663 100644
--- a/i18n/zh.po
+++ b/i18n/zh.po
@@ -19,7 +19,7 @@ msgstr ""
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "Untitled map, {{date}}"
-msgstr "无标题地图,{{日期}}"
+msgstr "无标题地图,{{date}}"
msgid "Map \"{{- name}}\" is saved."
msgstr "地图“{{- name}}”已保存。"
@@ -27,15 +27,6 @@ msgstr "地图“{{- name}}”已保存。"
msgid "Failed to save map: {{message}}"
msgstr "无法保存地图:{{message}}"
-msgid "Calculation"
-msgstr ""
-
-msgid "No calculations found"
-msgstr ""
-
-msgid "Calculations can be created in the Data Visualizer app."
-msgstr ""
-
msgid "Classification"
msgstr "分类"
@@ -438,9 +429,6 @@ msgstr "需要事件数据项"
msgid "Program indicator is required"
msgstr "需要项目指标"
-msgid "Calculation is required"
-msgstr ""
-
msgid "Period is required"
msgstr "需要期间"
@@ -456,9 +444,6 @@ msgstr "事件数据项"
msgid "Program indicators"
msgstr "项目指标"
-msgid "Calculations"
-msgstr ""
-
msgid "Item type"
msgstr "条目类型"
@@ -1276,7 +1261,7 @@ msgid "You don't have access to this layer data"
msgstr "您无权访问此图层数据"
msgid "The event filter is not supported"
-msgstr ""
+msgstr "不支持事件过滤器"
msgid "An unknown error occurred while reading layer data"
msgstr "读取图层数据时发生未知错误"
diff --git a/package.json b/package.json
index 66e6d4cd0..92113ed13 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
"patch-package": "^6.5.1",
"redux-devtools": "^3.7.0",
"redux-mock-store": "^1.5.4",
- "start-server-and-test": "^1.15.4"
+ "start-server-and-test": "^2.0.1"
},
"dependencies": {
"@dhis2/analytics": "^26.0.17",
diff --git a/src/components/app/App.js b/src/components/app/App.js
index 762d13b8a..7e8f8ca55 100644
--- a/src/components/app/App.js
+++ b/src/components/app/App.js
@@ -7,6 +7,7 @@ import { useDispatch } from 'react-redux'
import { tSetAnalyticalObject } from '../../actions/analyticalObject.js'
import { removeBingBasemaps, setBingMapsApiKey } from '../../actions/basemap.js'
import { tSetExternalLayers } from '../../actions/externalLayers.js'
+import { setInterpretation } from '../../actions/interpretations.js'
import { tOpenMap } from '../../actions/map.js'
import { CURRENT_AO_KEY } from '../../util/analyticalObject.js'
import { getUrlParameter } from '../../util/requests.js'
@@ -33,6 +34,15 @@ const App = () => {
} else if (getUrlParameter('currentAnalyticalObject') === 'true') {
await dispatch(tSetAnalyticalObject(currentAO))
}
+
+ // analytics interpretation component uses camelcase
+ const interpretationId =
+ getUrlParameter('interpretationid') ||
+ getUrlParameter('interpretationId')
+
+ if (interpretationId) {
+ dispatch(setInterpretation(interpretationId))
+ }
}
if (!isEmpty(systemSettings)) {
diff --git a/src/components/app/DetailsPanel.js b/src/components/app/DetailsPanel.js
index ddb88d10e..a5dee646a 100644
--- a/src/components/app/DetailsPanel.js
+++ b/src/components/app/DetailsPanel.js
@@ -9,17 +9,14 @@ import styles from './styles/DetailsPanel.module.css'
const DetailsPanel = ({ interpretationsRenderCount }) => {
const detailsPanelOpen = useSelector((state) => state.ui.rightPanelOpen)
const viewOrgUnitProfile = useSelector((state) => state.orgUnitProfile)
+ const interpretationId = useSelector((state) => state.interpretation?.id)
const getContent = () => {
- if (!detailsPanelOpen) {
- return null
+ if (interpretationId || (detailsPanelOpen && !viewOrgUnitProfile)) {
+ return
}
- return viewOrgUnitProfile ? (
-
- ) : (
-
- )
+ return detailsPanelOpen ? : null
}
return (
diff --git a/src/components/app/__tests__/Detailspanel.spec.js b/src/components/app/__tests__/Detailspanel.spec.js
new file mode 100644
index 000000000..6d6110eef
--- /dev/null
+++ b/src/components/app/__tests__/Detailspanel.spec.js
@@ -0,0 +1,142 @@
+import { render } from '@testing-library/react'
+import React from 'react'
+import { Provider } from 'react-redux'
+import configureMockStore from 'redux-mock-store'
+import DetailsPanel from '../DetailsPanel.js'
+
+const mockStore = configureMockStore()
+
+jest.mock(
+ '../../interpretations/Interpretations.js',
+ () =>
+ function MockInterpretations() {
+ return
Interpretations
+ }
+)
+
+jest.mock(
+ '../../orgunits/OrgUnitProfile.js',
+ () =>
+ function MockOrgUnitProfile() {
+ return Org Unit Profile
+ }
+)
+
+describe('DetailsPanel', () => {
+ test('renders InterpretationsPanel when has interpretationId, has orgUnitProfile and panel is open', () => {
+ const store = {
+ interpretation: { id: 'abc123' },
+ orgUnitProfile: 'xyzpdq',
+ ui: { rightPanelOpen: true },
+ }
+
+ const { container } = render(
+
+
+
+ )
+ expect(container).toMatchSnapshot()
+ })
+ test('renders InterpretationsPanel when has interpretationId, has orgUnitProfile and panel is closed', () => {
+ const store = {
+ interpretation: { id: 'abc123' },
+ orgUnitProfile: 'xyzpdq',
+ ui: { rightPanelOpen: false },
+ }
+
+ const { container } = render(
+
+
+
+ )
+ expect(container).toMatchSnapshot()
+ })
+ test('renders InterpretationsPanel when has interpretationId, no orgUnitProfile and panel is open', () => {
+ const store = {
+ interpretation: { id: 'abc123' },
+ orgUnitProfile: null,
+ ui: { rightPanelOpen: true },
+ }
+
+ const { container } = render(
+
+
+
+ )
+ expect(container).toMatchSnapshot()
+ })
+
+ test('renders InterpretationsPanel when has interpretationId, no orgUnitProfile and panel is closed', () => {
+ const store = {
+ interpretation: { id: 'abc123' },
+ orgUnitProfile: null,
+ ui: { rightPanelOpen: false },
+ }
+
+ const { container } = render(
+
+
+
+ )
+ expect(container).toMatchSnapshot()
+ })
+
+ test('renders OrgUnitProfile when no interpretationId, has orgUnitProfile and panel is open', () => {
+ const store = {
+ interpretation: {},
+ orgUnitProfile: 'xyzpdq',
+ ui: { rightPanelOpen: true },
+ }
+
+ const { container } = render(
+
+
+
+ )
+ expect(container).toMatchSnapshot()
+ })
+
+ test('renders null when no interpretationId, has orgUnitProfile, and panel closed', () => {
+ const store = {
+ interpretation: {},
+ orgUnitProfile: 'xyzpdq',
+ ui: { rightPanelOpen: false },
+ }
+
+ const { container } = render(
+
+
+
+ )
+ expect(container).toMatchSnapshot()
+ })
+ test('renders InterpretationsPanel when no interpretationId, no orgUnitProfile and panel open', () => {
+ const store = {
+ interpretation: {},
+ orgUnitProfile: null,
+ ui: { rightPanelOpen: true },
+ }
+
+ const { container } = render(
+
+
+
+ )
+ expect(container).toMatchSnapshot()
+ })
+
+ test('renders null when no interpretationId, no orgUnitProfile, and panel closed', () => {
+ const store = {
+ interpretation: {},
+ orgUnitProfile: null,
+ ui: { rightPanelOpen: false },
+ }
+
+ const { container } = render(
+
+
+
+ )
+ expect(container).toMatchSnapshot()
+ })
+})
diff --git a/src/components/app/__tests__/__snapshots__/Detailspanel.spec.js.snap b/src/components/app/__tests__/__snapshots__/Detailspanel.spec.js.snap
new file mode 100644
index 000000000..b45d254ce
--- /dev/null
+++ b/src/components/app/__tests__/__snapshots__/Detailspanel.spec.js.snap
@@ -0,0 +1,89 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`DetailsPanel renders InterpretationsPanel when has interpretationId, has orgUnitProfile and panel is closed 1`] = `
+
+`;
+
+exports[`DetailsPanel renders InterpretationsPanel when has interpretationId, has orgUnitProfile and panel is open 1`] = `
+
+`;
+
+exports[`DetailsPanel renders InterpretationsPanel when has interpretationId, no orgUnitProfile and panel is closed 1`] = `
+
+`;
+
+exports[`DetailsPanel renders InterpretationsPanel when has interpretationId, no orgUnitProfile and panel is open 1`] = `
+
+`;
+
+exports[`DetailsPanel renders InterpretationsPanel when no interpretationId, no orgUnitProfile and panel open 1`] = `
+
+`;
+
+exports[`DetailsPanel renders OrgUnitProfile when no interpretationId, has orgUnitProfile and panel is open 1`] = `
+
+`;
+
+exports[`DetailsPanel renders null when no interpretationId, has orgUnitProfile, and panel closed 1`] = `
+
+`;
+
+exports[`DetailsPanel renders null when no interpretationId, no orgUnitProfile, and panel closed 1`] = `
+
+`;
diff --git a/src/components/calculations/CalculationSelect.js b/src/components/calculations/CalculationSelect.js
deleted file mode 100644
index 8bdc94ad6..000000000
--- a/src/components/calculations/CalculationSelect.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import { useDataQuery } from '@dhis2/app-runtime'
-import i18n from '@dhis2/d2-i18n'
-import PropTypes from 'prop-types'
-import React from 'react'
-import { SelectField, Help } from '../core/index.js'
-import { useUserSettings } from '../UserSettingsProvider.js'
-import styles from './styles/CalculationSelect.module.css'
-
-// Load all calculations
-const CALCULATIONS_QUERY = {
- calculations: {
- resource: 'expressionDimensionItems',
- params: ({ nameProperty }) => ({
- fields: ['id', `${nameProperty}~rename(name)`],
- paging: false,
- }),
- },
-}
-
-const CalculationSelect = ({ calculation, className, errorText, onChange }) => {
- const { nameProperty } = useUserSettings()
- const { loading, error, data } = useDataQuery(CALCULATIONS_QUERY, {
- variables: { nameProperty },
- })
-
- const items = data?.calculations.expressionDimensionItems
- const value = calculation?.id
-
- return (
-
- onChange(dataItem, 'calculation')}
- className={className}
- emptyText={i18n.t('No calculations found')}
- errorText={
- error?.message ||
- (!calculation && errorText ? errorText : null)
- }
- filterable={true}
- dataTest="calculationselect"
- />
-
- {i18n.t(
- 'Calculations can be created in the Data Visualizer app.'
- )}
-
-
- )
-}
-
-CalculationSelect.propTypes = {
- onChange: PropTypes.func.isRequired,
- calculation: PropTypes.object,
- className: PropTypes.string,
- errorText: PropTypes.string,
-}
-
-export default CalculationSelect
diff --git a/src/components/calculations/styles/CalculationSelect.module.css b/src/components/calculations/styles/CalculationSelect.module.css
deleted file mode 100644
index d8cf160d2..000000000
--- a/src/components/calculations/styles/CalculationSelect.module.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.calculationSelect {
- margin-bottom: var(--spacers-dp16);
-}
diff --git a/src/components/core/SelectField.js b/src/components/core/SelectField.js
index fa21e24c9..ecfed175d 100644
--- a/src/components/core/SelectField.js
+++ b/src/components/core/SelectField.js
@@ -17,7 +17,6 @@ import styles from './styles/InputField.module.css'
const SelectField = (props) => {
const {
dense = true,
- emptyText,
errorText,
helpText,
warning,
@@ -26,7 +25,6 @@ const SelectField = (props) => {
prefix,
loading,
multiple,
- filterable,
disabled,
onChange,
className,
@@ -67,14 +65,12 @@ const SelectField = (props) => {
label={label}
prefix={prefix}
selected={!isLoading ? selected : undefined}
- filterable={filterable}
disabled={disabled}
loading={isLoading}
error={!!errorText}
warning={!!warning}
validationText={warning ? warning : errorText}
helpText={helpText}
- empty={emptyText}
onChange={onSelectChange}
dataTest={dataTest}
>
@@ -92,9 +88,7 @@ SelectField.propTypes = {
dataTest: PropTypes.string,
dense: PropTypes.bool,
disabled: PropTypes.bool,
- emptyText: PropTypes.string, // If set, shows empty text when no options
errorText: PropTypes.string, // If set, shows the error message below the SelectField
- filterable: PropTypes.bool,
helpText: PropTypes.string, // If set, shows the help text below the SelectField
items: PropTypes.arrayOf(
PropTypes.shape({
diff --git a/src/components/edit/thematic/ThematicDialog.js b/src/components/edit/thematic/ThematicDialog.js
index 77b089376..d074437c0 100644
--- a/src/components/edit/thematic/ThematicDialog.js
+++ b/src/components/edit/thematic/ThematicDialog.js
@@ -36,7 +36,6 @@ import {
} from '../../../util/analytics.js'
import { isPeriodAvailable } from '../../../util/periods.js'
import { getStartEndDateError } from '../../../util/time.js'
-import CalculationSelect from '../../calculations/CalculationSelect.js'
import NumericLegendStyle from '../../classification/NumericLegendStyle.js'
import { Tab, Tabs } from '../../core/index.js'
import DataElementGroupSelect from '../../dataElement/DataElementGroupSelect.js'
@@ -255,7 +254,6 @@ class ThematicDialog extends Component {
dataElementError,
dataSetError,
programError,
- calculationError,
eventDataItemError,
programIndicatorError,
periodTypeError,
@@ -409,14 +407,6 @@ class ThematicDialog extends Component {
/>
),
]}
- {valueType === dimConf.calculation.objectName && (
-
- )}
@@ -618,14 +608,6 @@ class ThematicDialog extends Component {
}
}
- if (valueType === dimConf.calculation.objectName && !dataItem) {
- return this.setErrorState(
- 'calculationError',
- i18n.t('Calculation is required'),
- 'data'
- )
- }
-
if (!period && periodType !== START_END_DATES) {
return this.setErrorState(
'periodError',
diff --git a/src/components/edit/thematic/ValueTypeSelect.js b/src/components/edit/thematic/ValueTypeSelect.js
index 0726f9b86..018b87ab8 100644
--- a/src/components/edit/thematic/ValueTypeSelect.js
+++ b/src/components/edit/thematic/ValueTypeSelect.js
@@ -1,29 +1,11 @@
import i18n from '@dhis2/d2-i18n'
import PropTypes from 'prop-types'
-import React, { useMemo } from 'react'
+import React from 'react'
import { dimConf } from '../../../constants/dimension.js'
import { SelectField } from '../../core/index.js'
-const getValueTypes = () => [
- { id: dimConf.indicator.objectName, name: i18n.t('Indicator') },
- { id: dimConf.dataElement.objectName, name: i18n.t('Data element') },
- { id: dimConf.dataSet.objectName, name: i18n.t('Reporting rates') },
- {
- id: dimConf.eventDataItem.objectName,
- name: i18n.t('Event data items'),
- },
- {
- id: dimConf.programIndicator.objectName,
- name: i18n.t('Program indicators'),
- },
- {
- id: dimConf.calculation.objectName,
- name: i18n.t('Calculations'),
- },
-]
-
-const ValueTypeSelect = ({ value, onChange, className }) => {
- const items = useMemo(() => getValueTypes(), [])
+const ValueTypeSelect = (props) => {
+ const { value, onChange, className } = props
// If value type is data element operand, make it data element
const type =
@@ -31,6 +13,21 @@ const ValueTypeSelect = ({ value, onChange, className }) => {
? dimConf.dataElement.objectName
: value
+ // TODO: Avoid creating on each render (needs to be created after i18next contains translations
+ const items = [
+ { id: dimConf.indicator.objectName, name: i18n.t('Indicator') },
+ { id: dimConf.dataElement.objectName, name: i18n.t('Data element') },
+ { id: dimConf.dataSet.objectName, name: i18n.t('Reporting rates') },
+ {
+ id: dimConf.eventDataItem.objectName,
+ name: i18n.t('Event data items'),
+ },
+ {
+ id: dimConf.programIndicator.objectName,
+ name: i18n.t('Program indicators'),
+ },
+ ]
+
return (
{
value={type}
onChange={(valueType) => onChange(valueType.id)}
className={className}
- dataTest="thematic-layer-value-type-select"
/>
)
}
diff --git a/src/components/interpretations/Interpretations.js b/src/components/interpretations/Interpretations.js
index ef7cb361c..f3cf14087 100644
--- a/src/components/interpretations/Interpretations.js
+++ b/src/components/interpretations/Interpretations.js
@@ -1,9 +1,7 @@
import PropTypes from 'prop-types'
import React, { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
-import { setInterpretation } from '../../actions/interpretations.js'
import { openInterpretationsPanel } from '../../actions/ui.js'
-import { getUrlParameter } from '../../util/requests.js'
import InterpretationsPanel from './InterpretationsPanel.js'
const Interpretations = ({ renderCount }) => {
@@ -15,12 +13,7 @@ const Interpretations = ({ renderCount }) => {
useEffect(() => {
if (isMapLoaded) {
- const urlInterpretationId = getUrlParameter('interpretationid')
-
- if (urlInterpretationId) {
- dispatch(setInterpretation(urlInterpretationId))
- dispatch(openInterpretationsPanel())
- }
+ dispatch(openInterpretationsPanel())
}
}, [isMapLoaded, dispatch])
diff --git a/src/components/layers/overlays/OverlayCard.js b/src/components/layers/overlays/OverlayCard.js
index cfd1e86e3..7c1405dff 100644
--- a/src/components/layers/overlays/OverlayCard.js
+++ b/src/components/layers/overlays/OverlayCard.js
@@ -105,10 +105,7 @@ const OverlayCard = ({
await set(currentAO)
// Open it in another app
- window.open(
- `${baseUrl}/${APP_URLS[type]}/#/currentAnalyticalObject`,
- '_blank'
- )
+ window.location.href = `${baseUrl}/${APP_URLS[type]}/#/currentAnalyticalObject`
}
: undefined
}
diff --git a/src/components/orgunits/OrgUnitData.js b/src/components/orgunits/OrgUnitData.js
index 4108d34d1..bce650e02 100644
--- a/src/components/orgunits/OrgUnitData.js
+++ b/src/components/orgunits/OrgUnitData.js
@@ -23,7 +23,7 @@ const ORGUNIT_PROFILE_QUERY = {
// Only YEARLY period type is supported in first version
const periodType = 'YEARLY'
const currentYear = String(new Date().getFullYear())
-const periods = getFixedPeriodsByType({ periodType, currentYear })
+const periods = getFixedPeriodsByType(periodType, currentYear)
const defaultPeriod = filterFuturePeriods(periods)[0] || periods[0]
/*
diff --git a/src/components/periods/PeriodSelect.js b/src/components/periods/PeriodSelect.js
index f8261ac88..f020e3ce4 100644
--- a/src/components/periods/PeriodSelect.js
+++ b/src/components/periods/PeriodSelect.js
@@ -7,8 +7,7 @@ import {
} from '@dhis2/ui'
import cx from 'classnames'
import PropTypes from 'prop-types'
-import React, { useState, useMemo, useCallback, useEffect } from 'react'
-import usePrevious from '../../hooks/usePrevious.js'
+import React, { Component } from 'react'
import {
getFixedPeriodsByType,
filterFuturePeriods,
@@ -17,118 +16,124 @@ import { getYear } from '../../util/time.js'
import { SelectField } from '../core/index.js'
import styles from './styles/PeriodSelect.module.css'
-const PeriodSelect = ({
- onChange,
- className,
- errorText,
- firstDate,
- lastDate,
- period,
- periodType,
-}) => {
- const [year, setYear] = useState(getYear(period?.startDate || lastDate))
- const prevYear = usePrevious(year)
-
- // Set periods when periodType or year changes
- /* eslint-disable react-hooks/exhaustive-deps */
- const periods = useMemo(
- () =>
- periodType
- ? getFixedPeriodsByType({
- periodType,
- year,
- firstDate,
- lastDate,
- })
- : [period], // saved map period (not included in depency array by design)
- [periodType, year, firstDate, lastDate]
- )
- /* eslint-enable react-hooks/exhaustive-deps */
-
- // Increment/decrement year
- const changeYear = useCallback(
- (change) => {
- const newYear = year + change
-
- if (
- (!firstDate || newYear >= getYear(firstDate)) &&
- (!lastDate || newYear <= getYear(lastDate))
- ) {
- setYear(newYear)
- }
- },
- [year, firstDate, lastDate]
- )
-
- // Autoselect most recent period
- useEffect(() => {
- if (!period) {
- onChange(filterFuturePeriods(periods)[0] || periods[0])
+class PeriodSelect extends Component {
+ static propTypes = {
+ onChange: PropTypes.func.isRequired,
+ className: PropTypes.string,
+ errorText: PropTypes.string,
+ period: PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ startDate: PropTypes.string,
+ }),
+ periodType: PropTypes.string,
+ }
+
+ state = {
+ year: null,
+ periods: null,
+ }
+
+ componentDidMount() {
+ this.setPeriods()
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ const { periodType, period, onChange } = this.props
+ const { year, periods } = this.state
+
+ if (periodType !== prevProps.periodType) {
+ this.setPeriods()
+ } else if (periods && !period) {
+ onChange(filterFuturePeriods(periods)[0] || periods[0]) // Autoselect most recent period
+ }
+
+ // Change period if year is changed (but keep period index)
+ if (period && prevState.periods && year !== prevState.year) {
+ const periodIndex = prevState.periods.findIndex(
+ (item) => item.id === period.id
+ )
+ onChange(periods[periodIndex])
+ }
+ }
+
+ render() {
+ const { periodType, period, onChange, className, errorText } =
+ this.props
+ const { periods } = this.state
+
+ if (!periods) {
+ return null
}
- }, [period, periods, year, onChange])
- // Keep the same period position when year changes
- useEffect(() => {
- if (period && !periods.some((p) => p.id === period.id)) {
- const periodId = period.id.replace(prevYear, year)
+ const value =
+ period && periods.some((p) => p.id === period.id) ? period.id : null
+
+ return (
+
+
+ {periodType && (
+
+
+ }
+ onClick={this.previousYear}
+ dataTest="button-previous-year"
+ />
+
+
+ }
+ onClick={this.nextYear}
+ dataTest="button-next-year"
+ />
+
+
+ )}
+
+ )
+ }
+
+ setPeriods() {
+ const { periodType, period } = this.props
+ const year = this.state.year || getYear(period && period.startDate)
+ let periods
- onChange(periods.find((p) => p.id === periodId))
+ if (periodType) {
+ periods = getFixedPeriodsByType(periodType, year)
+ } else if (period) {
+ periods = [period] // If period is loaded in favorite
}
- }, [period, periods, year, prevYear, onChange])
- if (!periods) {
- return null
+ this.setState({ periods, year })
}
- const value =
- period && periods.some((p) => p.id === period.id) ? period.id : null
-
- return (
-
-
- {periodType && (
-
-
- }
- onClick={() => changeYear(-1)}
- dataTest="button-previous-year"
- />
-
-
- }
- onClick={() => changeYear(1)}
- dataTest="button-next-year"
- />
-
-
- )}
-
- )
-}
+ nextYear = () => {
+ this.changeYear(1)
+ }
+
+ previousYear = () => {
+ this.changeYear(-1)
+ }
-PeriodSelect.propTypes = {
- onChange: PropTypes.func.isRequired,
- className: PropTypes.string,
- errorText: PropTypes.string,
- firstDate: PropTypes.string,
- lastDate: PropTypes.string,
- period: PropTypes.shape({
- id: PropTypes.string.isRequired,
- startDate: PropTypes.string,
- }),
- periodType: PropTypes.string,
+ changeYear = (change) => {
+ const { periodType } = this.props
+ const year = this.state.year + change
+
+ this.setState({
+ year,
+ periods: getFixedPeriodsByType(periodType, year),
+ })
+ }
}
export default PeriodSelect
diff --git a/src/constants/dimension.js b/src/constants/dimension.js
index 3834de2b0..16b6d48ae 100644
--- a/src/constants/dimension.js
+++ b/src/constants/dimension.js
@@ -64,12 +64,6 @@ export const dimConf = {
objectName: 'pi',
itemType: 'PROGRAM_INDICATOR',
},
- calculation: {
- value: 'expressionDimensionItems',
- dimensionName: 'dx',
- objectName: 'ed', // Created by Bjorn, don't seem to be in use when the map is saved
- itemType: 'EXPRESSION_DIMENSION_ITEM',
- },
period: {
id: 'period',
value: 'period',
diff --git a/src/util/periods.js b/src/util/periods.js
index cfc89b3e8..2fdd0f826 100644
--- a/src/util/periods.js
+++ b/src/util/periods.js
@@ -6,43 +6,25 @@ import { periodTypes, periodGroups } from '../constants/periods.js'
const getYearOffsetFromNow = (year) => year - new Date(Date.now()).getFullYear()
-const filterPeriods = (periods, firstDate, lastDate) =>
- periods.filter(
- (p) =>
- (!firstDate || p.startDate >= firstDate) &&
- (!lastDate || p.endDate <= lastDate)
- )
-
export const getPeriodTypes = (includeRelativePeriods, hiddenPeriods = []) =>
periodTypes(includeRelativePeriods).filter(
({ group }) => !hiddenPeriods.includes(group)
)
-export const getFixedPeriodsByType = ({
- periodType,
- year,
- firstDate,
- lastDate,
-}) => {
+export const getFixedPeriodsByType = (periodType, year) => {
const period = getFixedPeriodsOptionsById(periodType)
const forceDescendingForYearTypes = !!periodType.match(/^FY|YEARLY/)
const offset = getYearOffsetFromNow(year)
- let periods = period?.getPeriods({ offset, reversePeriods: true })
-
- if (!periods) {
- return null
+ const periods = period?.getPeriods({ offset, reversePeriods: true }) || null
+ if (periods && forceDescendingForYearTypes) {
+ // TODO: the reverse() is a workaround for a bug in the analytics
+ // getPeriods function that no longer correctly reverses the order
+ // for YEARLY and FY period types
+ return periods.reverse()
}
-
- if (firstDate || lastDate) {
- periods = filterPeriods(periods, firstDate, lastDate)
- }
-
- // TODO: the reverse() is a workaround for a bug in the analytics
- // getPeriods function that no longer correctly reverses the order
- // for YEARLY and FY period types
- return forceDescendingForYearTypes ? periods.reverse() : periods
+ return periods
}
export const getRelativePeriods = (hiddenPeriods = []) =>
@@ -66,7 +48,7 @@ export const getPeriodNames = () => ({
}, {}),
})
-export const filterFuturePeriods = (periods, date) => {
- const now = new Date(date || Date.now())
+export const filterFuturePeriods = (periods) => {
+ const now = new Date(Date.now())
return periods.filter(({ startDate }) => new Date(startDate) < now)
}
diff --git a/yarn.lock b/yarn.lock
index 11fbd9bda..eb6b06666 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -14376,10 +14376,10 @@ stackframe@^1.3.4:
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310"
integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==
-start-server-and-test@^1.15.4:
- version "1.15.5"
- resolved "https://registry.yarnpkg.com/start-server-and-test/-/start-server-and-test-1.15.5.tgz#5c9103bd87c06678fc62658fbe97d09501714011"
- integrity sha512-o3EmkX0++GV+qsvIJ/OKWm3w91fD8uS/bPQVPrh/7loaxkpXSuAIHdnmN/P/regQK9eNAK76aBJcHt+OSTk+nA==
+start-server-and-test@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/start-server-and-test/-/start-server-and-test-2.0.1.tgz#e110e0b5a54c80963f65b9689c2c320ab8ac9025"
+ integrity sha512-8PFo4DLLLCDMuS51/BEEtE1m9CAXw1LNVtZSS1PzkYQh6Qf9JUwM4huYeSoUumaaoAyuwYBwCa9OsrcpMqcOdQ==
dependencies:
arg "^5.0.2"
bluebird "3.7.2"