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: implement option set selection for data items (DHIS2-17872) #3324

Closed
wants to merge 5 commits into from
Closed
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
2 changes: 1 addition & 1 deletion cypress/elements/dimensionModal/dataDimension.js
Original file line number Diff line number Diff line change
@@ -168,7 +168,7 @@ export const clickEDIEditButton = (item) =>
.getBySel(optionContentEl)
.contains(item)
.parent()
.findBySel('data-dimension-transfer-option-edit-button')
.findBySel('data-dimension-transfer-option-edit-calculation-button')
.click()

export const expectSelectableDataItemsAmountToBe = (amount) =>
16 changes: 14 additions & 2 deletions i18n/en.pot
Original file line number Diff line number Diff line change
@@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2024-03-01T08:28:43.727Z\n"
"PO-Revision-Date: 2024-03-01T08:28:43.727Z\n"
"POT-Creation-Date: 2025-01-24T13:38:26.612Z\n"
"PO-Revision-Date: 2025-01-24T13:38:26.612Z\n"

msgid "All items"
msgstr "All items"
@@ -967,6 +967,18 @@ msgstr "No outliers found"
msgid "There were no outliers found for the selected data items and options."
msgstr "There were no outliers found for the selected data items and options."

msgid "Invalid visualization type"
msgstr "Invalid visualization type"

msgid ""
"The visualization type cannot be used when option set data items are using "
"individual output mode. Change output mode to combined, or use another "
"visualization type."
msgstr ""
"The visualization type cannot be used when option set data items are using "
"individual output mode. Change output mode to combined, or use another "
"visualization type."

msgid "or"
msgstr "or"

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@
"typescript": "^4.8.4"
},
"dependencies": {
"@dhis2/analytics": "^26.10.0",
"@dhis2/analytics": "git+https://github.com/d2-ci/analytics.git#a1c3fc308c817665f8c9443249c448661413db55",
"@dhis2/app-runtime": "^3.11.3",
"@dhis2/app-runtime-adapter-d2": "^1.1.0",
"@dhis2/app-service-datastore": "^1.0.0-beta.3",
6 changes: 6 additions & 0 deletions src/actions/ui.js
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ import {
REMOVE_UI_LAYOUT_DIMENSIONS,
SET_UI_ITEMS,
REMOVE_UI_ITEMS,
SET_UI_OPTION_SET_ITEM_BY_ITEM,
ADD_UI_PARENT_GRAPH_MAP,
SET_UI_ACTIVE_MODAL_DIALOG,
SET_UI_YEAR_ON_YEAR_SERIES,
@@ -128,6 +129,11 @@ export const acRemoveUiItemAttributes = (value) => ({
value,
})

export const acSetUiOptionSetItemByItem = (value) => ({
type: SET_UI_OPTION_SET_ITEM_BY_ITEM,
value,
})

export const acAddParentGraphMap = (value) => ({
type: ADD_UI_PARENT_GRAPH_MAP,
value,
43 changes: 38 additions & 5 deletions src/components/DimensionsPanel/Dialogs/DialogManager.js
Original file line number Diff line number Diff line change
@@ -49,6 +49,7 @@ import {
acSetUiItems,
acAddParentGraphMap,
acSetUiItemAttributes,
acSetUiOptionSetItemByItem,
} from '../../../actions/ui.js'
import { removeLastPathSegment, getOuPath } from '../../../modules/orgUnit.js'
import {
@@ -63,14 +64,14 @@ import {
sGetSettingsDisplayNameProperty,
} from '../../../reducers/settings.js'
import {
sGetUiItems,
sGetUiItemsByDimension,
sGetUiActiveModalDialog,
sGetUiParentGraphMap,
sGetUiType,
sGetAxisIdByDimensionId,
sGetDimensionIdsFromLayout,
sGetUiItemsByAttribute,
sGetUiOptionSetItemByItem,
} from '../../../reducers/ui.js'
import HideButton from '../../HideButton/HideButton.js'
import UpdateButton from '../../UpdateButton/UpdateButton.js'
@@ -134,16 +135,33 @@ export class DialogManager extends Component {
}, 1000)

selectUiItems = ({ dimensionId, items, itemAttribute }) => {
const itemIds = []

items.map((item) => {
itemIds.push(item.id)

if (item.optionSet?.id) {
this.props.setUiOptionSetItemByItem({
itemId: item.id,
optionSetItem: {
id: item.optionSet.id,
options: item.optionSet.options || [],
aggregation: item.optionSet.aggregation || undefined,
},
})
}
})

if (itemAttribute) {
this.props.setUiItemAttributes({
dimensionId,
attribute: itemAttribute,
itemIds: items.map((item) => item.id),
itemIds,
})
} else {
this.props.setUiItems({
dimensionId,
itemIds: items.map((item) => item.id),
itemIds,
})
}

@@ -184,6 +202,9 @@ export class DialogManager extends Component {
...(item.expression
? { expression: item.expression }
: {}),
...(item.optionSetId
? { optionSetId: item.optionSetId }
: {}),
}

return obj
@@ -198,7 +219,7 @@ export class DialogManager extends Component {
getSelectedItems = (dialogId) => {
const items = isScatterAttribute(dialogId)
? this.props.getItemsByAttribute(dialogId)
: this.props.selectedItems[dialogId]
: this.props.selectedItems(dialogId)
return (items || [])
.filter(
(id) =>
@@ -216,6 +237,12 @@ export class DialogManager extends Component {
expression: this.props.metadata[id].expression,
}
: {}),
...(this.props.metadata[id]?.optionSetId
? {
optionSetId: this.props.metadata[id].optionSetId,
optionSet: this.props.getOptionSetItemByItem(id),
}
: {}),
access: this.props.metadata[id]?.access,
}))
}
@@ -346,6 +373,7 @@ export class DialogManager extends Component {
itemAttribute: dialogId,
})
: dimensionProps.onSelect

const onCalculationSave = (calculation) => {
this.props.addMetadata({
[calculation.id]: {
@@ -550,12 +578,14 @@ DialogManager.propTypes = {
dxIds: PropTypes.array,
getAxisIdByDimensionId: PropTypes.func,
getItemsByAttribute: PropTypes.func,
getOptionSetItemByItem: PropTypes.func,
metadata: PropTypes.object,
parentGraphMap: PropTypes.object,
rootOrgUnits: PropTypes.array,
selectedItems: PropTypes.object,
setUiItemAttributes: PropTypes.func,
setUiItems: PropTypes.func,
setUiOptionSetItemByItem: PropTypes.func,
settings: PropTypes.object,
type: PropTypes.string,
}
@@ -575,14 +605,16 @@ const mapStateToProps = (state) => ({
dxIds: sGetUiItemsByDimension(state, DIMENSION_ID_DATA),
ouIds: sGetUiItemsByDimension(state, DIMENSION_ID_ORGUNIT),
rootOrgUnits: sGetRootOrgUnits(state),
selectedItems: sGetUiItems(state),
selectedItems: (dimensionId) => sGetUiItemsByDimension(state, dimensionId),
settings: sGetSettings(state),
type: sGetUiType(state),
getAxisIdByDimensionId: (dimensionId) =>
sGetAxisIdByDimensionId(state, dimensionId),
dimensionIdsInLayout: sGetDimensionIdsFromLayout(state),
getItemsByAttribute: (attribute) =>
sGetUiItemsByAttribute(state, attribute),
getOptionSetItemByItem: (itemId) =>
sGetUiOptionSetItemByItem(state, itemId),
})

export default connect(mapStateToProps, {
@@ -592,4 +624,5 @@ export default connect(mapStateToProps, {
addMetadata: acAddMetadata,
addParentGraphMap: acAddParentGraphMap,
setUiItemAttributes: acSetUiItemAttributes,
setUiOptionSetItemByItem: acSetUiOptionSetItemByItem,
})(DialogManager)
12 changes: 12 additions & 0 deletions src/modules/error.js
Original file line number Diff line number Diff line change
@@ -369,6 +369,18 @@ export class NoOutliersError extends VisualizationError {
}
}

export class VisualizationTypeOptionSetCombinationError extends VisualizationError {
constructor() {
super(
EmptyBox,
i18n.t('Invalid visualization type'),
i18n.t(
'The visualization type cannot be used when option set data items are using individual output mode. Change output mode to combined, or use another visualization type.'
)
)
}
}

export const genericErrorTitle = i18n.t('Something went wrong')

const getAvailableAxesDescription = (visType) => {
2 changes: 1 addition & 1 deletion src/modules/fields/nestedFields.js
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ const DIMENSION_ITEM = `dimensionItem~rename(${ID})`
const LEGEND_SET = `${ID},${NAME}`
const USER = `${NAME},userCredentials[username]`

const ITEMS = `${DIMENSION_ITEM},${NAME},dimensionItemType,expression,access`
const ITEMS = `${DIMENSION_ITEM},${NAME},dimensionItemType,expression,access,optionSetId`

const AXIS = `dimension,filter,legendSet[${LEGEND_SET}],items[${ITEMS}]`
const INTERPRETATIONS = 'id,created'
18 changes: 14 additions & 4 deletions src/modules/layoutValidation.js
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ import {
NoVerticalError,
NoHorizontalError,
DuplicateItemsError,
VisualizationTypeOptionSetCombinationError,
} from './error.js'
import { BASE_FIELD_YEARLY_SERIES } from './fields/baseFields.js'
import { ITEM_ATTRIBUTE_HORIZONTAL, ITEM_ATTRIBUTE_VERTICAL } from './ui.js'
@@ -94,16 +95,25 @@ const validatePieLayout = (layout) => {
)
}

// also used for Gauge
const validateSingleValueLayout = (layout) => {
validateDimension(
layoutGetDimension(layout, DIMENSION_ID_DATA),
new NoDataError(layout.type)
)
const dxLayoutDimension = layoutGetDimension(layout, DIMENSION_ID_DATA)

validateDimension(dxLayoutDimension, new NoDataError(layout.type))

validateDimension(
layoutGetDimension(layout, DIMENSION_ID_PERIOD),
new NoPeriodError(layout.type)
)

if (dxLayoutDimension.items.length && layout.ui.optionSetItemByItem) {
const dxDataItemOptionSet =
layout.ui.optionSetItemByItem[dxLayoutDimension.items[0]]

if (dxDataItemOptionSet?.aggregation === 'DISAGGREGATED') {
throw new VisualizationTypeOptionSetCombinationError()
}
}
}

const validateScatterLayout = (layout) => {
30 changes: 28 additions & 2 deletions src/reducers/ui.js
Original file line number Diff line number Diff line change
@@ -74,6 +74,7 @@ export const ADD_UI_LAYOUT_DIMENSIONS = 'ADD_UI_LAYOUT_DIMENSIONS'
export const REMOVE_UI_LAYOUT_DIMENSIONS = 'REMOVE_UI_LAYOUT_DIMENSIONS'
export const SET_UI_ITEMS = 'SET_UI_ITEMS'
export const REMOVE_UI_ITEMS = 'REMOVE_UI_ITEMS'
export const SET_UI_OPTION_SET_ITEM_BY_ITEM = 'SET_UI_OPTION_SET_ITEM_BY_ITEM'
export const ADD_UI_PARENT_GRAPH_MAP = 'ADD_UI_PARENT_GRAPH_MAP'
export const SET_UI_ACTIVE_MODAL_DIALOG = 'SET_UI_ACTIVE_MODAL_DIALOG'
export const SET_UI_YEAR_ON_YEAR_SERIES = 'SET_UI_YEAR_ON_YEAR_SERIES'
@@ -99,6 +100,7 @@ export const DEFAULT_UI = {
[DIMENSION_ID_ORGUNIT]: [],
[DIMENSION_ID_PERIOD]: [],
},
optionSetItemByItem: {},
yearOverYearSeries: [],
yearOverYearCategory: [],
itemAttributes: [],
@@ -556,20 +558,40 @@ export default (state = DEFAULT_UI, action) => {
...state.itemsByDimension,
[dimensionId]: itemIds,
},
// clean up optionSetItemByItem for dx
// this removes all stale objects for non-selected data items
...(dimensionId === DIMENSION_ID_DATA
? {
optionSetItemByItem: itemIds.reduce((obj, itemId) => {
obj[itemId] = state.optionSetItemByItem[itemId]
return obj
}, {}),
}
: {}),
}
}
case REMOVE_UI_ITEMS: {
const { dimensionId, itemIdsToRemove } = action.value

const dxItems = (state.itemsByDimension[dimensionId] || []).filter(
const itemIds = (state.itemsByDimension[dimensionId] || []).filter(
(id) => !itemIdsToRemove.includes(id)
)

return {
...state,
itemsByDimension: {
...state.itemsByDimension,
[dimensionId]: dxItems,
[dimensionId]: itemIds,
},
}
}
case SET_UI_OPTION_SET_ITEM_BY_ITEM: {
const { itemId, optionSetItem } = action.value
return {
...state,
optionSetItemByItem: {
...state.optionSetItemByItem,
[itemId]: optionSetItem,
},
}
}
@@ -734,6 +756,7 @@ export const sGetUiActiveModalDialog = (state) =>
sGetUi(state).activeModalDialog
export const sGetUiRightSidebarOpen = (state) => sGetUi(state).rightSidebarOpen
export const sGetAxes = (state) => sGetUi(state).axes
export const sGetUiOptionSetItems = (state) => sGetUi(state).optionSetItemByItem

// Selectors level 2

@@ -743,6 +766,9 @@ export const sGetAxisIdByDimensionId = (state, dimensionId) =>
export const sGetUiItemsByDimension = (state, dimension) =>
sGetUiItems(state)[dimension] || DEFAULT_UI.itemsByDimension[dimension]

export const sGetUiOptionSetItemByItem = (state, itemId) =>
sGetUiOptionSetItems(state)[itemId]

export const sGetDimensionItemsByAxis = (state, axisId) => {
const dimensions = (sGetUiLayout(state) || {})[axisId] || []

5 changes: 2 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
@@ -2051,10 +2051,9 @@
classnames "^2.3.1"
prop-types "^15.7.2"

"@dhis2/analytics@^26.10.0":
"@dhis2/analytics@git+https://github.com/d2-ci/analytics.git#a1c3fc308c817665f8c9443249c448661413db55":
version "26.10.0"
resolved "https://registry.yarnpkg.com/@dhis2/analytics/-/analytics-26.10.0.tgz#6be4f7ad13b4a64e63f330255f0bbc57aa118ff2"
integrity sha512-JmJFXsxnpoQ1KDoAYttygp7bsP76EpLVRaXDk5bGoye7lhG+Q2k0DW3q+hj9H3tIdwV1m3N1mIxzAMzG01QEtw==
resolved "git+https://github.com/d2-ci/analytics.git#a1c3fc308c817665f8c9443249c448661413db55"
dependencies:
"@dhis2/multi-calendar-dates" "^1.2.2"
"@dnd-kit/core" "^6.0.7"