Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: custom calculations #3088

Draft
wants to merge 55 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
d157c30
fix: remove fallback coordinate field (DHIS2-8165) (#2575)
turban Apr 24, 2023
00fae56
feat(plugin): send installation status [DHIS2-15097] (#2580)
KaiVandivier May 2, 2023
23efb6b
fix: bumps cli-app-scripts to 10.3.8 for LIBS-499 fix (#2592)
KaiVandivier May 5, 2023
1ff538b
fix(translations): sync translations from transifex (dev)
dhis2-bot Jun 6, 2023
eb2a3bb
docs: move docs to app (#2650)
janhenrikoverland Jun 9, 2023
3fafd45
fix(translations): sync translations from transifex (dev)
dhis2-bot Jun 10, 2023
cd34381
fix(translations): sync translations from transifex (dev)
dhis2-bot Jun 14, 2023
e40dc13
fix(translations): sync translations from transifex (dev)
dhis2-bot Jun 17, 2023
09412e0
docs: maps download and value labels (#2691)
turban Jun 19, 2023
d7172d4
fix(translations): sync translations from transifex (dev)
dhis2-bot Jun 21, 2023
bd9237a
fix(translations): sync translations from transifex (dev)
dhis2-bot Jun 23, 2023
6de79b1
fix: Set rendering strategy to single if not relative period (#2703)
turban Jun 26, 2023
09de660
chore: increase default timeout for DOM activity (#2720)
jenniferarnesen Jun 26, 2023
fd5696d
chore: use 50 second timeout for card title to appear (#2730)
jenniferarnesen Jun 27, 2023
3ba7831
chore: dependency upgrades (#2722)
turban Jun 27, 2023
491548b
fix: keep layer visibility when period is changed (#2705)
turban Jun 27, 2023
9fa4abd
Don't show north arrow for split view maps (#2704)
turban Jun 27, 2023
32578ec
fix: set period for each split view map (#2721)
turban Jun 27, 2023
578a75a
chore: remove target-branch from dependabot config (#2731)
jenniferarnesen Jun 27, 2023
e7d43a8
fix: upgrade landcover dataset (#2732)
turban Jun 28, 2023
b1d349e
chore: increase cypress extended timeout (#2744)
jenniferarnesen Jun 28, 2023
99ffcc0
chore: @dhis2/analytic upgrade
turban Jun 28, 2023
2ad6cfc
feat: custom calculations
turban Jun 28, 2023
1a2daf4
chore: code cleaning
turban Jun 28, 2023
a7af8ef
chore: code cleaning
turban Jun 28, 2023
567cb6b
feat: filterable calculations select
turban Jun 28, 2023
bf5e0b4
fix(translations): sync translations from transifex (dev)
dhis2-bot Jun 29, 2023
d9fc748
fix(translations): sync translations from transifex (dev)
dhis2-bot Jul 4, 2023
523eca7
chore: disable nightly scheduled test run (#2782)
jenniferarnesen Jul 4, 2023
948c375
chore: linting fix
turban Aug 7, 2023
91b8c11
chore: PeriodTypeSelect as functional component (#2926)
turban Aug 8, 2023
f20648f
chore: add cypress test for calculation - not enabled yet
jenniferarnesen Aug 8, 2023
eb62d84
chore: dependency upgrades (#2930)
turban Aug 9, 2023
dbdcc2f
chore: RenderingStategy as functional component and usePrevious hook …
turban Aug 9, 2023
5fbf249
chore: BooleanStyle as functional component (#2921)
turban Aug 9, 2023
20adb8e
chore(deps-dev): bump @dhis2/cli-app-scripts from 10.3.9 to 10.3.10 (…
dependabot[bot] Aug 22, 2023
91e7eca
fix: create and delete the calculation on-the-fly for the cypress test
jenniferarnesen Aug 29, 2023
1bcb83a
Merge branch 'dev' into feat/DHIS2-15474
turban Aug 30, 2023
d4f2567
Merge branch 'dev' into feat/DHIS2-15474
jenniferarnesen Aug 31, 2023
1ef9571
Merge branch 'dev' into feat/DHIS2-15474
turban Aug 31, 2023
4f518c7
chore: custom calculations help text
turban Aug 31, 2023
d23b4f1
chore: add missing translation string
turban Aug 31, 2023
f605636
Revert "chore: custom calculations help text"
turban Aug 31, 2023
5ac7552
Merge branch 'feat/DHIS2-15474' of https://github.com/dhis2/maps-app …
turban Aug 31, 2023
ab46406
Revert "Revert "chore: custom calculations help text""
turban Aug 31, 2023
525353e
chore: keep translations
turban Aug 31, 2023
cdc32b2
chore: keep translation strings
turban Aug 31, 2023
0e3f333
chore: revert @dhis2/analytics version
turban Aug 31, 2023
f40bbcb
fix: open as chart should open in new tab (DHIS2-15794) (#2956)
jenniferarnesen Sep 11, 2023
2e555fd
chore: merge with dev
turban Sep 12, 2023
d0e871e
Merge branch 'dev' into feat/DHIS2-15474
jenniferarnesen Dec 20, 2023
c6d9102
Merge branch 'dev' into feat/DHIS2-15474
jenniferarnesen Dec 21, 2023
f5c032b
Merge branch 'dev' into feat/DHIS2-15474
jenniferarnesen Mar 19, 2024
dfc8576
Merge branch 'dev' into feat/DHIS2-15474
janhenrikoverland Apr 15, 2024
1f7652a
Merge branch 'master' into feat/DHIS2-15474
jenniferarnesen Aug 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cypress/elements/thematic_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Layer } from './layer.js'

export class ThematicLayer extends Layer {
selectItemType(itemType) {
cy.getByDataTest('itemtypeselect').click()
cy.getByDataTest('thematic-layer-value-type-select').click()
cy.contains(itemType).click()

return this
Expand Down
81 changes: 80 additions & 1 deletion cypress/integration/layers/thematiclayer.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
expectContextMenuOptions,
} from '../../elements/map_context_menu.js'
import { ThematicLayer } from '../../elements/thematic_layer.js'
import { CURRENT_YEAR } from '../../support/util.js'
import { CURRENT_YEAR, getApiBaseUrl } from '../../support/util.js'

const INDICATOR_NAME = 'VCCT post-test counselling rate'

Expand Down Expand Up @@ -138,4 +138,83 @@ 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}`
)
})
})
})
15 changes: 15 additions & 0 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ 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"

Expand Down Expand Up @@ -473,6 +482,9 @@ 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"

Expand All @@ -488,6 +500,9 @@ msgstr "Event data items"
msgid "Program indicators"
msgstr "Program indicators"

msgid "Calculations"
msgstr "Calculations"

msgid "Item type"
msgstr "Item type"

Expand Down
62 changes: 62 additions & 0 deletions src/components/calculations/CalculationSelect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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'

Check failure on line 6 in src/components/calculations/CalculationSelect.js

View workflow job for this annotation

GitHub Actions / lint

Unable to resolve path to module '../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 (
<div className={styles.calculationSelect}>
<SelectField
label={i18n.t('Calculation')}
loading={loading}
items={items}
value={value}
onChange={(dataItem) => onChange(dataItem, 'calculation')}
className={className}
emptyText={i18n.t('No calculations found')}
errorText={
error?.message ||
(!calculation && errorText ? errorText : null)
}
filterable={true}
dataTest="calculationselect"
/>
<Help>
{i18n.t(
'Calculations can be created in the Data Visualizer app.'
)}
</Help>
</div>
)
}

CalculationSelect.propTypes = {
onChange: PropTypes.func.isRequired,
calculation: PropTypes.object,
className: PropTypes.string,
errorText: PropTypes.string,
}

export default CalculationSelect
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.calculationSelect {
margin-bottom: var(--spacers-dp16);
}
6 changes: 6 additions & 0 deletions src/components/core/SelectField.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import styles from './styles/InputField.module.css'
const SelectField = (props) => {
const {
dense = true,
emptyText,
errorText,
helpText,
warning,
Expand All @@ -25,6 +26,7 @@ const SelectField = (props) => {
prefix,
loading,
multiple,
filterable,
disabled,
onChange,
className,
Expand Down Expand Up @@ -65,12 +67,14 @@ 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}
>
Expand All @@ -88,7 +92,9 @@ 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({
Expand Down
18 changes: 18 additions & 0 deletions src/components/edit/thematic/ThematicDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ 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'
Expand Down Expand Up @@ -257,6 +258,7 @@ class ThematicDialog extends Component {
dataElementError,
dataSetError,
programError,
calculationError,
eventDataItemError,
programIndicatorError,
periodTypeError,
Expand Down Expand Up @@ -410,6 +412,14 @@ class ThematicDialog extends Component {
/>
),
]}
{valueType === dimConf.calculation.objectName && (
<CalculationSelect
calculation={dataItem}
onChange={setDataItem}
className={styles.select}
errorText={calculationError}
/>
)}
<AggregationTypeSelect className={styles.select} />
<CompletedOnlyCheckbox valueType={valueType} />
</div>
Expand Down Expand Up @@ -613,6 +623,14 @@ 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',
Expand Down
41 changes: 22 additions & 19 deletions src/components/edit/thematic/ValueTypeSelect.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,44 @@
import i18n from '@dhis2/d2-i18n'
import PropTypes from 'prop-types'
import React from 'react'
import React, { useMemo } from 'react'
import { dimConf } from '../../../constants/dimension.js'
import { SelectField } from '../../core/index.js'

const ValueTypeSelect = (props) => {
const { value, onChange, className } = props
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(), [])

// If value type is data element operand, make it data element
const type =
value === dimConf.operand.objectName
? 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 (
<SelectField
label={i18n.t('Item type')}
items={items}
value={type}
onChange={(valueType) => onChange(valueType.id)}
className={className}
dataTest="itemtypeselect"
dataTest="thematic-layer-value-type-select"
/>
)
}
Expand Down
6 changes: 6 additions & 0 deletions src/constants/dimension.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ 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',
Expand Down
Loading