Skip to content

Commit

Permalink
feat: prefix time dimension ids with program id when tracked entity o…
Browse files Browse the repository at this point in the history
…utput type (#480)

Implements https://dhis2.atlassian.net/browse/DHIS2-16023

Description
Time dimensions need to be prefixed with the programId when using TE output type.

* adding time dimensions from different programs to layout
   - they get suffixed with the program name
* analytics query works - correct data is displayed
* saving LL with time dimensions saves successfully
* opening a saved LL with time dimensions - layout is empty but table
   shows correctly (metadata issue to be solved with backend changes)

The failing cypress tests are the same failures as the target branch
  • Loading branch information
jenniferarnesen authored Jan 24, 2024
1 parent b5a8c86 commit e0398d0
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 40 deletions.
9 changes: 7 additions & 2 deletions src/actions/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,14 +164,19 @@ export const tSetUiProgram =
(dispatch, getState) => {
const state = getState()
dispatch(acClearUiProgram())
if (sGetUiInputType(state) !== OUTPUT_TYPE_TRACKED_ENTITY) {
const inputType = sGetUiInputType(state)
if (inputType !== OUTPUT_TYPE_TRACKED_ENTITY) {
dispatch(tClearUiProgramRelatedDimensions())
}
program &&
dispatch(
acUpdateUiProgramId(program.id, {
...getProgramAsMetadata(program),
...getDynamicTimeDimensionsMetadata(program, stage),
...getDynamicTimeDimensionsMetadata(
program,
stage,
inputType
),
})
)
stage && dispatch(acUpdateUiProgramStageId(stage.id))
Expand Down
22 changes: 18 additions & 4 deletions src/components/Dialogs/PeriodDimension/PeriodDimension.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,14 @@ import {
SYSTEM_SETTINGS_HIDE_BIMONTHLY_PERIODS,
} from '../../../modules/systemSettings.js'
import { USER_SETTINGS_UI_LOCALE } from '../../../modules/userSettings.js'
import {
extractDimensionIdParts,
formatDimensionId,
} from '../../../modules/utils.js'
import {
sGetDimensionIdsFromLayout,
sGetUiItemsByDimension,
sGetUiInputType,
} from '../../../reducers/ui.js'
import DimensionModal from '../DimensionModal.js'
import styles from './PeriodDimension.module.css'
Expand Down Expand Up @@ -118,18 +123,27 @@ export const PeriodDimension = ({ dimension, onClose }) => {
: OPTION_PRESETS
)

const outputType = useSelector(sGetUiInputType)

const { programId } = extractDimensionIdParts(dimension.id, outputType)

const updatePeriodDimensionItems = (items) => {
const { uiItems, metadata } = items.reduce(
(acc, item) => {
acc.uiItems.push(item.id)
const id = formatDimensionId({
dimensionId: item.id,
programId,
outputType,
})
acc.uiItems.push(id)

if (isStartEndDate(item.id)) {
acc.metadata[item.id] = {
id: item.id,
acc.metadata[id] = {
id,
name: formatStartEndDate(item.id),
}
} else {
acc.metadata[item.id] = item
acc.metadata[id] = { ...item, id }
}

return acc
Expand Down
85 changes: 85 additions & 0 deletions src/modules/__tests__/metadata.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { getDynamicTimeDimensionsMetadata } from '../metadata.js'

describe('getDynamicTimeDimensionsMetadata', () => {
it('should return correct dynamic time dimensions metadata when inputType is OUTPUT_TYPE_TRACKED_ENTITY', () => {
const program = {
id: 'programId',
displayEnrollmentDateLabel: 'Custom Enrollment Date',
displayIncidentDateLabel: 'Custom Incident Date',
}
const stage = { displayExecutionDateLabel: 'Stage Execution Date' }
const inputType = 'TRACKED_ENTITY_INSTANCE' // OUTPUT_TYPE_TRACKED_ENTITY

const result = getDynamicTimeDimensionsMetadata(
program,
stage,
inputType
)

const expected = {
'programId.eventDate': {
id: 'programId.eventDate',
dimensionType: 'PERIOD',
name: 'Stage Execution Date',
},
'programId.enrollmentDate': {
id: 'programId.enrollmentDate',
dimensionType: 'PERIOD',
name: 'Custom Enrollment Date',
},
'programId.incidentDate': {
id: 'programId.incidentDate',
dimensionType: 'PERIOD',
name: 'Custom Incident Date',
},
'programId.scheduledDate': {
id: 'programId.scheduledDate',
dimensionType: 'PERIOD',
name: 'Scheduled date',
},
}

expect(result).toEqual(expected)
})

it('should return correct dynamic time dimensions metadata when inputType is not OUTPUT_TYPE_TRACKED_ENTITY', () => {
const program = {
id: 'programId',
displayEnrollmentDateLabel: 'Program Enrollment Date',
displayIncidentDateLabel: 'Custom Incident Date',
}
const stage = { displayExecutionDateLabel: 'Stage Execution Date' }
const inputType = 'OTHER_TYPE'

const result = getDynamicTimeDimensionsMetadata(
program,
stage,
inputType
)

const expected = {
eventDate: {
id: 'eventDate',
dimensionType: 'PERIOD',
name: 'Stage Execution Date',
},
enrollmentDate: {
id: 'enrollmentDate',
dimensionType: 'PERIOD',
name: 'Program Enrollment Date',
},
incidentDate: {
id: 'incidentDate',
dimensionType: 'PERIOD',
name: 'Custom Incident Date',
},
scheduledDate: {
id: 'scheduledDate',
dimensionType: 'PERIOD',
name: 'Scheduled date',
},
}

expect(result).toEqual(expected)
})
})
34 changes: 17 additions & 17 deletions src/modules/__tests__/timeDimensions.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe('ER > Dimensions > getTimeDimensionName', () => {
}
Object.values(getTimeDimensions()).forEach((dimension) => {
expect(getTimeDimensionName(dimension, program, stage)).toEqual(
dimension.name
dimension.defaultName
)
})
})
Expand All @@ -57,16 +57,16 @@ describe('ER > Dimensions > getTimeDimensionName', () => {

expect(
getTimeDimensionName(eventDateDimension, program, stage)
).toEqual(stage.displayExecutionDateLabel)
).toEqual('le event date')
expect(
getTimeDimensionName(enrollmentDateDimension, program, stage)
).toEqual(enrollmentDateDimension.name)
).toEqual('Enrollment date')
expect(
getTimeDimensionName(incidentDateDimension, program, stage)
).toEqual(incidentDateDimension.name)
).toEqual('Incident date')
expect(
getTimeDimensionName(scheduledDateDimension, program, stage)
).toEqual(scheduledDateDimension.name)
).toEqual('Scheduled date')
})
it('uses displayEnrollmentDateLabel from program for enrollment date', () => {
const program = {
Expand All @@ -85,16 +85,16 @@ describe('ER > Dimensions > getTimeDimensionName', () => {

expect(
getTimeDimensionName(eventDateDimension, program, stage)
).toEqual(eventDateDimension.name)
).toEqual('Event date')
expect(
getTimeDimensionName(enrollmentDateDimension, program, stage)
).toEqual(program.displayEnrollmentDateLabel)
).toEqual('le enrollment date')
expect(
getTimeDimensionName(incidentDateDimension, program, stage)
).toEqual(incidentDateDimension.name)
).toEqual('Incident date')
expect(
getTimeDimensionName(scheduledDateDimension, program, stage)
).toEqual(scheduledDateDimension.name)
).toEqual('Scheduled date')
})
it('uses displayDueDateLabel from stage for scheduled date', () => {
const program = {
Expand All @@ -113,16 +113,16 @@ describe('ER > Dimensions > getTimeDimensionName', () => {

expect(
getTimeDimensionName(eventDateDimension, program, stage)
).toEqual(eventDateDimension.name)
).toEqual('Event date')
expect(
getTimeDimensionName(enrollmentDateDimension, program, stage)
).toEqual(enrollmentDateDimension.name)
).toEqual('Enrollment date')
expect(
getTimeDimensionName(incidentDateDimension, program, stage)
).toEqual(incidentDateDimension.name)
).toEqual('Incident date')
expect(
getTimeDimensionName(scheduledDateDimension, program, stage)
).toEqual(stage.displayDueDateLabel)
).toEqual('le due date')
})
it('uses displayIncidentDateLabel from program for incident date', () => {
const program = {
Expand All @@ -141,16 +141,16 @@ describe('ER > Dimensions > getTimeDimensionName', () => {

expect(
getTimeDimensionName(eventDateDimension, program, stage)
).toEqual(eventDateDimension.name)
).toEqual('Event date')
expect(
getTimeDimensionName(enrollmentDateDimension, program, stage)
).toEqual(enrollmentDateDimension.name)
).toEqual('Enrollment date')
expect(
getTimeDimensionName(incidentDateDimension, program, stage)
).toEqual(program.displayIncidentDateLabel)
).toEqual('le incident date')
expect(
getTimeDimensionName(scheduledDateDimension, program, stage)
).toEqual(scheduledDateDimension.name)
).toEqual('Scheduled date')
})
})

Expand Down
17 changes: 14 additions & 3 deletions src/modules/metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import i18n from '@dhis2/d2-i18n'
import { getMainDimensions, getCreatedDimension } from './mainDimensions.js'
import { getProgramDimensions } from './programDimensions.js'
import { getTimeDimensions, getTimeDimensionName } from './timeDimensions.js'
import { formatDimensionId } from './utils.js'
import { OUTPUT_TYPE_TRACKED_ENTITY, getStatusNames } from './visualization.js'

const formatObject = (object) =>
Expand Down Expand Up @@ -70,10 +71,20 @@ export const getProgramAsMetadata = (program) => ({
[program.id]: program,
})

export const getDynamicTimeDimensionsMetadata = (program, stage) => ({
export const getDynamicTimeDimensionsMetadata = (
program,
stage,
inputType
) => ({
...Object.values(getTimeDimensions()).reduce((acc, dimension) => {
acc[dimension.id] = {
id: dimension.id,
const id = formatDimensionId({
dimensionId: dimension.id,
programId: program?.id,
outputType: inputType,
})

acc[id] = {
id,
dimensionType: dimension.dimensionType,
name: getTimeDimensionName(dimension, program, stage),
}
Expand Down
12 changes: 6 additions & 6 deletions src/modules/timeDimensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,43 +21,43 @@ export const getTimeDimensions = () => ({
[DIMENSION_ID_EVENT_DATE]: {
id: DIMENSION_ID_EVENT_DATE,
dimensionType: DIMENSION_TYPE_PERIOD,
name: i18n.t('Event date'),
defaultName: i18n.t('Event date'),
nameParentProperty: NAME_PARENT_PROPERTY_STAGE,
nameProperty: 'displayExecutionDateLabel',
},
[DIMENSION_ID_ENROLLMENT_DATE]: {
id: DIMENSION_ID_ENROLLMENT_DATE,
dimensionType: DIMENSION_TYPE_PERIOD,
name: i18n.t('Enrollment date'),
defaultName: i18n.t('Enrollment date'),
nameParentProperty: NAME_PARENT_PROPERTY_PROGRAM,
nameProperty: 'displayEnrollmentDateLabel',
},
[DIMENSION_ID_INCIDENT_DATE]: {
id: DIMENSION_ID_INCIDENT_DATE,
dimensionType: DIMENSION_TYPE_PERIOD,
name: i18n.t('Incident date'),
defaultName: i18n.t('Incident date'),
nameParentProperty: NAME_PARENT_PROPERTY_PROGRAM,
nameProperty: 'displayIncidentDateLabel',
},
[DIMENSION_ID_SCHEDULED_DATE]: {
id: DIMENSION_ID_SCHEDULED_DATE,
dimensionType: DIMENSION_TYPE_PERIOD,
name: i18n.t('Scheduled date'),
defaultName: i18n.t('Scheduled date'),
nameParentProperty: NAME_PARENT_PROPERTY_STAGE,
nameProperty: 'displayDueDateLabel',
},
})

export const getTimeDimensionName = (dimension, program, stage) => {
if (!dimension.nameParentProperty || !program) {
return dimension.name
return dimension.defaultName
}
const name =
dimension.nameParentProperty === NAME_PARENT_PROPERTY_PROGRAM
? program[dimension.nameProperty]
: stage?.[dimension.nameProperty]

return name || dimension.name
return name || dimension.defaultName
}

export const getHiddenTimeDimensions = (inputType, program, stage) => {
Expand Down
32 changes: 24 additions & 8 deletions src/reducers/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from '../modules/programDimensions.js'
import { getHiddenTimeDimensions } from '../modules/timeDimensions.js'
import { getAdaptedUiByType, getUiFromVisualization } from '../modules/ui.js'
import { formatDimensionId, extractDimensionIdParts } from '../modules/utils.js'
import {
OUTPUT_TYPE_EVENT,
OUTPUT_TYPE_TRACKED_ENTITY,
Expand Down Expand Up @@ -489,17 +490,24 @@ export const useProgramDimensions = () => {
const programId = useSelector(sGetUiProgramId)
const programStageId = useSelector(sGetUiProgramStageId)

const getId = (dimensionId) =>
formatDimensionId({
dimensionId,
programId,
outputType: inputType,
})

const eventDateDim = useSelector((state) =>
sGetMetadataById(state, DIMENSION_ID_EVENT_DATE)
sGetMetadataById(state, getId(DIMENSION_ID_EVENT_DATE))
)
const enrollmentDateDim = useSelector((state) =>
sGetMetadataById(state, DIMENSION_ID_ENROLLMENT_DATE)
sGetMetadataById(state, getId(DIMENSION_ID_ENROLLMENT_DATE))
)
const incidentDateDim = useSelector((state) =>
sGetMetadataById(state, DIMENSION_ID_INCIDENT_DATE)
sGetMetadataById(state, getId(DIMENSION_ID_INCIDENT_DATE))
)
const scheduledDateDim = useSelector((state) =>
sGetMetadataById(state, DIMENSION_ID_SCHEDULED_DATE)
sGetMetadataById(state, getId(DIMENSION_ID_SCHEDULED_DATE))
)

return useMemo(() => {
Expand All @@ -520,10 +528,18 @@ export const useProgramDimensions = () => {
? [scheduledDateDim]
: []),
incidentDateDim,
].filter(
(dimension) =>
!!dimension && !hiddenTimeDimensions.includes(dimension.id)
)
].filter((dimension) => {
// TODO this filter used to return !!dimension && !hiddenTimeDimensions.includes(dimension.id)
// is there a reason the dimension is checked for existence?
if (!dimension) {
return false
}
const { dimensionId } = extractDimensionIdParts(
dimension.id,
inputType
)
return !hiddenTimeDimensions.includes(dimensionId)
})

const programDimensions = Object.values(
getProgramDimensions(
Expand Down

0 comments on commit e0398d0

Please sign in to comment.