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

chore: fixed period select refactor #2958

Merged
merged 13 commits into from
Sep 15, 2023
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 @@ -136,4 +136,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 @@ -17,6 +17,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 @@ -421,6 +430,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 @@ -436,6 +448,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'
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 @@ -255,6 +256,7 @@ class ThematicDialog extends Component {
dataElementError,
dataSetError,
programError,
calculationError,
eventDataItemError,
programIndicatorError,
periodTypeError,
Expand Down Expand Up @@ -408,6 +410,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 @@ -609,6 +619,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
40 changes: 22 additions & 18 deletions src/components/edit/thematic/ValueTypeSelect.js
Original file line number Diff line number Diff line change
@@ -1,40 +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="thematic-layer-value-type-select"
/>
)
}
Expand Down
5 changes: 4 additions & 1 deletion src/components/layers/overlays/OverlayCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,10 @@ const OverlayCard = ({
await set(currentAO)

// Open it in another app
window.location.href = `${baseUrl}/${APP_URLS[type]}/#/currentAnalyticalObject`
window.open(
`${baseUrl}/${APP_URLS[type]}/#/currentAnalyticalObject`,
'_blank'
)
}
: undefined
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/orgunits/OrgUnitData.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]

/*
Expand Down
Loading
Loading