Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
getDimensionBlockReason,
type DimensionBlockReason,
} from '@modules/dimension/blocking'
import { resolveDimensionTetId, resolveTetId } from '@modules/layout'
import { resolveDimensionTetId, resolveLayoutContext } from '@modules/layout'
import {
clearMultiSelection,
getMultiSelectedDimensionIds,
Expand Down Expand Up @@ -205,7 +205,7 @@ export const useOnDragEnd = (): OnDragEndFn => {
const storeState = store.getState()
const visType = getVisUiConfigVisualizationType(storeState)
const customValue = getVisUiConfigCustomValue(storeState)
const layoutTetId = resolveTetId(
const { tetId: layoutTetId } = resolveLayoutContext(
getVisUiConfigLayoutAllDimensionIds(storeState),
metadataStore
)
Expand Down
27 changes: 4 additions & 23 deletions src/components/dimension-modal/dimension-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { LayoutDimension } from '@components/layout-panel/axis/chip'
import { useLayoutDimensions } from '@components/layout-panel/use-layout-dimensions'
import { useDimensionSuffix } from '@components/layout-panel/use-layout-dimensions'
import i18n from '@dhis2/d2-i18n'
import {
Modal,
Expand All @@ -18,12 +18,9 @@ import { isDimensionInLayout } from '@modules/layout'
import { isDimensionMetadataItem } from '@modules/metadata/item-guards'
import { tUpdateCurrentVisFromVisUiConfig } from '@store/thunks'
import { getUiActiveDimensionModal } from '@store/ui-slice'
import {
getVisUiConfigLayout,
getVisUiConfigLayoutAllDimensionIds,
} from '@store/vis-ui-config-slice'
import { getVisUiConfigLayout } from '@store/vis-ui-config-slice'
import type { DimensionMetadataItem } from '@types'
import { useCallback, useMemo, type FC } from 'react'
import { useCallback, type FC } from 'react'
import { AddToLayoutButton } from './add-to-layout-button'
import { ConditionsModalContent } from './conditions-modal-content/conditions-modal-content'
import { DynamicDimensionModalContent } from './dynamic-dimension-modal-content/dynamic-dimension-modal-content'
Expand Down Expand Up @@ -64,30 +61,14 @@ export const DimensionModal: FC<DimensionModalProps> = ({ onClose }) => {

const dispatch = useAppDispatch()
const layout = useAppSelector(getVisUiConfigLayout)
const layoutDimensionIds = useAppSelector(
getVisUiConfigLayoutAllDimensionIds
)
const dimensionId = useAppSelector(
getUiActiveDimensionModal
) as LayoutDimension['id']
const dimension = useDimensionMetadataItem(dimensionId)

const isInLayout = isDimensionInLayout(layout, dimensionId)

const suffixDimensionIds = useMemo(
() =>
layoutDimensionIds.includes(dimensionId)
? layoutDimensionIds
: [...layoutDimensionIds, dimensionId],
[layoutDimensionIds, dimensionId]
)
const layoutDimensions = useLayoutDimensions({
dimensionIds: suffixDimensionIds,
})
const suffix = useMemo(
() => layoutDimensions.find(({ id }) => id === dimensionId)?.suffix,
[layoutDimensions, dimensionId]
)
const suffix = useDimensionSuffix(dimensionId)

const modalTitle = suffix
? `${dimension?.name} · ${suffix}`
Expand Down
75 changes: 75 additions & 0 deletions src/components/layout-panel/__tests__/layout-panel.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,81 @@ describe('<LayoutPanel />', () => {
cy.getByDataTest('update-buttons').should('be.visible')
})

it('suffixes chips using the whole layout, not just their own axis', () => {
const crossAxisMetadata = {
p1: {
id: 'p1',
name: 'Program1',
programType: 'WITH_REGISTRATION',
},
p1s1: {
id: 'p1s1',
name: 'P1 Stage1',
repeatable: false,
hideDueDate: false,
program: { id: 'p1' },
},
p1s2: {
id: 'p1s2',
name: 'P1 Stage2',
repeatable: false,
hideDueDate: false,
program: { id: 'p1' },
},
'p1s1.weight': {
id: 'p1s1.weight',
name: 'Weight',
dimensionType: 'DATA_ELEMENT',
valueType: 'NUMBER',
},
'p1s2.weight': {
id: 'p1s2.weight',
name: 'Weight',
dimensionType: 'DATA_ELEMENT',
valueType: 'NUMBER',
},
}

const layoutPanelMockOptions = createMockOptions(
{
dimensionSelection: {
...mockOptions.partialStore?.preloadedState
.dimensionSelection,
dataSourceId: 'test-id',
},
visUiConfig: {
...visUiConfigInitialState,
visualizationType: 'LINE_LIST',
outputType: 'EVENT',
layout: {
columns: ['p1s1.weight'],
rows: [],
filters: ['p1s2.weight'],
},
},
},
crossAxisMetadata
)

cy.mount(
<MockAppWrapper {...layoutPanelMockOptions}>
<LayoutPanel />
</MockAppWrapper>
)

// Both dimensions are named "Weight" but sit in different axes. On its
// own, each axis has just one stage, so a per-axis check would add no
// suffix. Looking at the whole layout shows both stages, so both chips
// get one.
cy.getByDataTest('axis-columns')
.findByDataTest('chip-suffix')
.should('have.text', '· P1 Stage1')

cy.getByDataTest('axis-filters')
.findByDataTest('chip-suffix')
.should('have.text', '· P1 Stage2')
})

it('renders the LINE_LIST update buttons in the order tracked entity, enrollment, event', () => {
const layoutPanelMockOptions = createMockOptions({
dimensionSelection: {
Expand Down
143 changes: 95 additions & 48 deletions src/components/layout-panel/__tests__/use-layout-dimensions.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import {
initialState as visUiConfigInitialState,
visUiConfigSlice,
} from '@store/vis-ui-config-slice'
import { renderHookWithAppWrapper } from '@test-utils/app-wrapper'
import { describe, it, expect } from 'vitest'
import { useLayoutDimensions } from '../use-layout-dimensions'
import {
useDimensionSuffix,
useDimensionsWithSuffixes,
} from '../use-layout-dimensions'

const baseMetadata = {
p1: {
Expand Down Expand Up @@ -36,7 +43,7 @@ const baseMetadata = {
},
}

describe('useLayoutDimensions', () => {
describe('useDimensionsWithSuffixes', () => {
describe('LayoutDimension shape', () => {
it('populates id, name, dimensionId, programId, programStageId, dimensionType, optionSet, valueType from store metadata', async () => {
const metadata = {
Expand All @@ -50,14 +57,11 @@ describe('useLayoutDimensions', () => {
},
}
const { result } = await renderHookWithAppWrapper(
() =>
useLayoutDimensions({
dimensionIds: ['p1s1.d1'],
}),
() => useDimensionsWithSuffixes(['p1s1.d1']),
{ metadata }
)
expect(result.current).toEqual([
{
expect(result.current).toEqual({
'p1s1.d1': {
id: 'p1s1.d1',
name: 'Dimension1',
dimensionId: 'd1',
Expand All @@ -68,7 +72,7 @@ describe('useLayoutDimensions', () => {
valueType: 'TEXT',
suffix: undefined,
},
])
})
})

it('exposes programId/programStageId enriched by the store from a 3-segment compound ID', async () => {
Expand All @@ -82,13 +86,10 @@ describe('useLayoutDimensions', () => {
},
}
const { result } = await renderHookWithAppWrapper(
() =>
useLayoutDimensions({
dimensionIds: ['p1.p1s1.d1'],
}),
() => useDimensionsWithSuffixes(['p1.p1s1.d1']),
{ metadata }
)
const [dim] = result.current
const dim = result.current['p1.p1s1.d1']
expect(dim.dimensionId).toBe('d1')
expect(dim.programId).toBe('p1')
expect(dim.programStageId).toBe('p1s1')
Expand Down Expand Up @@ -130,24 +131,18 @@ describe('useLayoutDimensions', () => {

it('returns no suffix when layout has only one program and one stage', async () => {
const { result } = await renderHookWithAppWrapper(
() =>
useLayoutDimensions({
dimensionIds: ['p1s1.d1'],
}),
() => useDimensionsWithSuffixes(['p1s1.d1']),
{ metadata: dimMetadata }
)
expect(result.current[0].suffix).toBeUndefined()
expect(result.current['p1s1.d1'].suffix).toBeUndefined()
})

it('applies stage-name suffix when layout has multiple stages from one program', async () => {
const { result } = await renderHookWithAppWrapper(
() =>
useLayoutDimensions({
dimensionIds: ['p1s1.d1', 'p1s2.d2'],
}),
() => useDimensionsWithSuffixes(['p1s1.d1', 'p1s2.d2']),
{ metadata: dimMetadata }
)
expect(result.current.map((d) => d.suffix)).toEqual([
expect(Object.values(result.current).map((d) => d.suffix)).toEqual([
'P1 Stage1',
'P1 Stage2',
])
Expand All @@ -156,12 +151,10 @@ describe('useLayoutDimensions', () => {
it('applies program-name suffix to a stage-bound dim when layout has multiple programs but only one stage', async () => {
const { result } = await renderHookWithAppWrapper(
() =>
useLayoutDimensions({
dimensionIds: ['p1s1.d1', 'p2.enrollmentDate'],
}),
useDimensionsWithSuffixes(['p1s1.d1', 'p2.enrollmentDate']),
{ metadata: dimMetadata }
)
expect(result.current.map((d) => d.suffix)).toEqual([
expect(Object.values(result.current).map((d) => d.suffix)).toEqual([
'Program1',
'Program2',
])
Expand All @@ -179,13 +172,10 @@ describe('useLayoutDimensions', () => {
},
}
const { result } = await renderHookWithAppWrapper(
() =>
useLayoutDimensions({
dimensionIds: ['p1s1.d1', 'p2s1.d1'],
}),
() => useDimensionsWithSuffixes(['p1s1.d1', 'p2s1.d1']),
{ metadata }
)
expect(result.current.map((d) => d.suffix)).toEqual([
expect(Object.values(result.current).map((d) => d.suffix)).toEqual([
'Program1, P1 Stage1',
'Program2, P1 Stage1',
])
Expand All @@ -206,16 +196,14 @@ describe('useLayoutDimensions', () => {
}
const { result } = await renderHookWithAppWrapper(
() =>
useLayoutDimensions({
dimensionIds: [
'tet1.enrollmentOu',
'p1.enrollmentDate',
'p2.enrollmentDate',
],
}),
useDimensionsWithSuffixes([
'tet1.enrollmentOu',
'p1.enrollmentDate',
'p2.enrollmentDate',
]),
{ metadata }
)
expect(result.current.map((d) => d.suffix)).toEqual([
expect(Object.values(result.current).map((d) => d.suffix)).toEqual([
undefined,
'Program1',
'Program2',
Expand All @@ -233,15 +221,74 @@ describe('useLayoutDimensions', () => {
}
const { result } = await renderHookWithAppWrapper(
() =>
useLayoutDimensions({
dimensionIds: ['p1s1.d1', 'p1s2.d2', 'lastUpdated'],
}),
useDimensionsWithSuffixes([
'p1s1.d1',
'p1s2.d2',
'lastUpdated',
]),
{ metadata }
)
const lastUpdated = result.current.find(
(d) => d.id === 'lastUpdated'
)
expect(lastUpdated?.suffix).toBeUndefined()
expect(result.current['lastUpdated'].suffix).toBeUndefined()
})
})
})

describe('useDimensionSuffix', () => {
const dimMetadata = {
...baseMetadata,
'p1s1.d1': {
id: 'p1s1.d1',
name: 'Weight',
dimensionType: 'DATA_ELEMENT',
valueType: 'NUMBER',
},
'p1s2.d2': {
id: 'p1s2.d2',
name: 'Weight',
dimensionType: 'DATA_ELEMENT',
valueType: 'NUMBER',
},
}

const withLayout = (layout: {
columns: string[]
rows: string[]
filters: string[]
}) => ({
metadata: dimMetadata,
partialStore: {
reducer: { visUiConfig: visUiConfigSlice.reducer },
preloadedState: {
visUiConfig: { ...visUiConfigInitialState, layout },
},
},
})

it('suffixes a dimension against the whole layout, across axes', async () => {
const { result } = await renderHookWithAppWrapper(
() => useDimensionSuffix('p1s1.d1'),
withLayout({
columns: ['p1s1.d1'],
rows: [],
filters: ['p1s2.d2'],
})
)
expect(result.current).toBe('P1 Stage1')
})

it('adds a dimension not yet in the layout to the comparison set', async () => {
const { result } = await renderHookWithAppWrapper(
() => useDimensionSuffix('p1s2.d2'),
withLayout({ columns: ['p1s1.d1'], rows: [], filters: [] })
)
expect(result.current).toBe('P1 Stage2')
})

it('returns no suffix when nothing else shares the stage or program', async () => {
const { result } = await renderHookWithAppWrapper(
() => useDimensionSuffix('p1s1.d1'),
withLayout({ columns: ['p1s1.d1'], rows: [], filters: [] })
)
expect(result.current).toBeUndefined()
})
})
Loading
Loading