From 79571e18fe446dfedd6ff01ee0326c4acd0225ce Mon Sep 17 00:00:00 2001 From: Bruno Raimbault Date: Fri, 26 Jun 2026 12:27:26 +0200 Subject: [PATCH 1/2] fix: respect user display name preference across metadata queries --- src/components/OrgUnitsProvider.jsx | 9 +- src/components/app/FileMenu.jsx | 3 +- src/components/app/useLoadMap.js | 5 +- src/components/edit/LayerEdit.jsx | 3 +- .../edit/thematic/ThematicDialog.jsx | 8 +- src/components/map/MapView.jsx | 3 +- src/components/map/layers/EventLayer.jsx | 9 +- .../map/layers/TrackedEntityLayer.jsx | 7 +- .../map/layers/TrackedEntityPopup.jsx | 10 ++- src/components/plugin/LayerLoader.jsx | 3 + src/loaders/eventLoader.js | 14 ++- src/util/coordinatesName.js | 6 +- src/util/helpers.js | 85 +++++++++---------- src/util/requests.js | 6 +- 14 files changed, 84 insertions(+), 87 deletions(-) diff --git a/src/components/OrgUnitsProvider.jsx b/src/components/OrgUnitsProvider.jsx index 5eb97cf23a..1b195f7317 100644 --- a/src/components/OrgUnitsProvider.jsx +++ b/src/components/OrgUnitsProvider.jsx @@ -1,13 +1,14 @@ import { useDataEngine } from '@dhis2/app-runtime' import PropTypes from 'prop-types' import React, { useContext, useState, useEffect, createContext } from 'react' +import { useCachedData } from './cachedDataProvider/CachedDataProvider.jsx' // Fetches the root org units associated with the current user with fallback to data capture org units const ORG_UNITS_QUERY = { roots: { resource: 'organisationUnits', - params: () => ({ - fields: ['id', 'displayName~rename(name)', 'path'], // TODO organisationUnits has shortName + params: ({ nameProperty }) => ({ + fields: ['id', `${nameProperty}~rename(name)`, 'path'], userDataViewFallback: true, }), }, @@ -27,9 +28,11 @@ const OrgUnitsProvider = ({ children }) => { const [orgUnits, setOrgUnits] = useState() const [error, setError] = useState() const engine = useDataEngine() + const { nameProperty } = useCachedData() useEffect(() => { engine.query(ORG_UNITS_QUERY, { + variables: { nameProperty }, onComplete: ({ levels, roots }) => setOrgUnits({ levels: levels.organisationUnitLevels, @@ -37,7 +40,7 @@ const OrgUnitsProvider = ({ children }) => { }), onError: setError, }) - }, [engine]) + }, [engine, nameProperty]) return ( { const dispatch = useDispatch() const engine = useDataEngine() const { serverVersion } = useConfig() - const { systemSettings, currentUser } = useCachedData() + const { systemSettings, currentUser, nameProperty } = useCachedData() const defaultBasemap = systemSettings.keyDefaultBaseMap //alerts const saveAlert = useAlert(ALERT_MESSAGE_DYNAMIC, ALERT_OPTIONS_DYNAMIC) @@ -151,6 +151,7 @@ const FileMenu = ({ onFileMenuAction }) => { engine, defaultBasemap, withSubscribers: true, + nameProperty, }) const latestMap = { diff --git a/src/components/app/useLoadMap.js b/src/components/app/useLoadMap.js index 73f19150d2..9ab830e336 100644 --- a/src/components/app/useLoadMap.js +++ b/src/components/app/useLoadMap.js @@ -30,7 +30,7 @@ export const useLoadMap = () => { const basemapInvalidAlertRef = useRef( useAlert(ALERT_MESSAGE_DYNAMIC, ALERT_CRITICAL) ) - const { systemSettings, basemaps } = useCachedData() + const { systemSettings, basemaps, nameProperty } = useCachedData() const defaultBasemap = systemSettings.keyDefaultBaseMap const engine = useDataEngine() const dispatch = useDispatch() @@ -50,6 +50,7 @@ export const useLoadMap = () => { id: params.mapId, engine, defaultBasemap, + nameProperty, }) engine.mutate(dataStatisticsMutation, { @@ -86,7 +87,7 @@ export const useLoadMap = () => { previousParamsRef.current = params }, - [basemaps, defaultBasemap, dispatch, engine] + [basemaps, defaultBasemap, dispatch, engine, nameProperty] ) useEffect(() => { diff --git a/src/components/edit/LayerEdit.jsx b/src/components/edit/LayerEdit.jsx index f3dd1e23fc..e2044fe847 100644 --- a/src/components/edit/LayerEdit.jsx +++ b/src/components/edit/LayerEdit.jsx @@ -46,7 +46,7 @@ const getLayerNames = () => ({ const LayerEdit = ({ layer, addLayer, updateLayer, cancelLayer }) => { const [isValidLayer, setIsValidLayer] = useState(false) - const { systemSettings, periodsSettings, currentUser } = useCachedData() + const { systemSettings, periodsSettings } = useCachedData() const orgUnits = useOrgUnits() const onValidateLayer = () => setIsValidLayer(true) @@ -106,7 +106,6 @@ const LayerEdit = ({ layer, addLayer, updateLayer, cancelLayer }) => { {...layer} systemSettings={systemSettings} periodsSettings={periodsSettings} - currentUser={currentUser} orgUnits={orgUnits} validateLayer={isValidLayer} onLayerValidation={onLayerValidation} diff --git a/src/components/edit/thematic/ThematicDialog.jsx b/src/components/edit/thematic/ThematicDialog.jsx index c1c03693c3..4cb8180843 100644 --- a/src/components/edit/thematic/ThematicDialog.jsx +++ b/src/components/edit/thematic/ThematicDialog.jsx @@ -38,6 +38,7 @@ import { getPeriodsFromFilters, getDimensionsFromFilters, } from '../../../util/analytics.js' +import { useCachedData } from '../../cachedDataProvider/CachedDataProvider.jsx' import NumericLegendStyle from '../../classification/NumericLegendStyle.jsx' import { Tab, Tabs, Checkbox } from '../../core/index.js' import DimensionFilter from '../../dimensions/DimensionFilter.jsx' @@ -71,7 +72,6 @@ const ThematicDialog = ({ noDataLegend, unclassifiedLegend, periodsSettings, - currentUser, validateLayer, onLayerValidation, legendSet, @@ -82,6 +82,7 @@ const ThematicDialog = ({ legendIsolated, }) => { const dispatch = useDispatch() + const { nameProperty } = useCachedData() const countFeaturesWithoutCoordinates = useSelector( (state) => state.layerEdit.countFeaturesWithoutCoordinates ) @@ -385,9 +386,7 @@ const ThematicDialog = ({
{ const { baseUrl } = useConfig() const engine = useDataEngine() - const { currentUser } = useCachedData() - const nameProperty = currentUser.keyAnalysisDisplayProperty + const { nameProperty } = useCachedData() const splitViewLayers = getSplitViewLayers(layers) const isSplitView = splitViewLayers.length > 0 diff --git a/src/components/map/layers/EventLayer.jsx b/src/components/map/layers/EventLayer.jsx index 46efe4e011..381bfdf4b2 100644 --- a/src/components/map/layers/EventLayer.jsx +++ b/src/components/map/layers/EventLayer.jsx @@ -234,9 +234,6 @@ class EventLayer extends Layer { programStage, eventCoordinateField, }) { - const displayNameProp = - nameProperty === 'name' ? 'displayName' : 'displayShortName' - let displayItems = [] const programStageResponse = await engine.query( @@ -244,7 +241,7 @@ class EventLayer extends Layer { { variables: { id: programStage.id, - nameProperty: displayNameProp, + nameProperty, }, } ) @@ -275,7 +272,7 @@ class EventLayer extends Layer { { variables: { id: program.id, - nameProperty: displayNameProp, + nameProperty, }, } ) @@ -316,7 +313,7 @@ class EventLayer extends Layer { programStage, eventCoordinateField, engine, - displayNameProp, + nameProperty, }) this.setState({ displayItems, eventCoordinateFieldName }) diff --git a/src/components/map/layers/TrackedEntityLayer.jsx b/src/components/map/layers/TrackedEntityLayer.jsx index 96f3f0841c..731a707457 100644 --- a/src/components/map/layers/TrackedEntityLayer.jsx +++ b/src/components/map/layers/TrackedEntityLayer.jsx @@ -172,15 +172,12 @@ class TrackedEntityLayer extends Layer { const { trackedEntityType, program } = this.props // Get relationshipType object from loader if we want to retrieve attributes from secondary dataset - const displayNameProp = - nameProperty === 'name' ? 'displayName' : 'displayShortName' - const { trackedEntityType: data } = await engine.query( TRACKED_ENTITY_TRACKED_ENTITY_TYPE_ATTRIBUTES_QUERY, { variables: { id: trackedEntityType.id, - nameProperty: displayNameProp, + nameProperty, }, } ) @@ -192,7 +189,7 @@ class TrackedEntityLayer extends Layer { { variables: { id: program.id, - nameProperty: displayNameProp, + nameProperty, }, } ) diff --git a/src/components/map/layers/TrackedEntityPopup.jsx b/src/components/map/layers/TrackedEntityPopup.jsx index 8d88906f2c..337bf183d0 100644 --- a/src/components/map/layers/TrackedEntityPopup.jsx +++ b/src/components/map/layers/TrackedEntityPopup.jsx @@ -15,8 +15,8 @@ const TRACKED_ENTITIES_QUERY = { trackedEntities: { resource: `tracker/trackedEntities`, id: ({ id }) => id, - params: ({ program }) => ({ - fields: 'updatedAt,orgUnit,attributes[displayName~rename(name),value,attribute],relationships', + params: ({ program, nameProperty }) => ({ + fields: `updatedAt,orgUnit,attributes[${nameProperty}~rename(name),value,attribute],relationships`, program: program?.id, }), }, @@ -85,6 +85,7 @@ const TrackedEntityPopup = ({ variables: { id: feature.properties.id, program, + nameProperty, }, lazy: true, }) @@ -118,7 +119,10 @@ const TrackedEntityPopup = ({ }) const orgUnitsNamesMap = {} for (const id of orgUnitIds) { - const result = await refetchOrgUnit({ id, nameProperty }) + const result = await refetchOrgUnit({ + id, + nameProperty, + }) orgUnitsNamesMap[id] = result?.orgUnit?.name } setOrgUnitNames(orgUnitsNamesMap) diff --git a/src/components/plugin/LayerLoader.jsx b/src/components/plugin/LayerLoader.jsx index d209e6f129..028ed95d4d 100644 --- a/src/components/plugin/LayerLoader.jsx +++ b/src/components/plugin/LayerLoader.jsx @@ -31,6 +31,7 @@ const LayerLoader = ({ config, onLoad }) => { const { systemSettings: { keyAnalysisDigitGroupSeparator }, currentUser, + nameProperty, } = useCachedData() const { keyAnalysisDisplayProperty, id: userId } = currentUser const periodTypeData = useDataOutputPeriodTypes() @@ -48,6 +49,7 @@ const LayerLoader = ({ config, onLoad }) => { config, engine, keyAnalysisDisplayProperty, // name/shortName + nameProperty, // displayName/displayShortName keyAnalysisDigitGroupSeparator, // NONE/SPACE/COMMA userId, baseUrl, @@ -66,6 +68,7 @@ const LayerLoader = ({ config, onLoad }) => { userId, baseUrl, keyAnalysisDisplayProperty, + nameProperty, keyAnalysisDigitGroupSeparator, serverVersion, ]) diff --git a/src/loaders/eventLoader.js b/src/loaders/eventLoader.js index 65a24360f1..e9d7cbff33 100644 --- a/src/loaders/eventLoader.js +++ b/src/loaders/eventLoader.js @@ -49,7 +49,7 @@ const unknownErrorAlert = { const eventLoader = async ({ config: layerConfig, engine, - keyAnalysisDisplayProperty, + nameProperty, keyAnalysisDigitGroupSeparator, analyticsEngine, periodTypeData, @@ -59,16 +59,12 @@ const eventLoader = async ({ ...layerConfig, keyAnalysisDigitGroupSeparator, } - const displayNameProp = - keyAnalysisDisplayProperty === 'name' - ? 'displayName' - : 'displayShortName' try { await loadEventLayer({ config, engine, - displayNameProp, + nameProperty, analyticsEngine, periodTypeData, loadExtended, @@ -98,7 +94,7 @@ const eventLoader = async ({ const loadEventLayer = async ({ config, engine, - displayNameProp, + nameProperty, analyticsEngine, periodTypeData, loadExtended, @@ -162,7 +158,7 @@ const loadEventLayer = async ({ const analyticsRequest = await getAnalyticsRequest(config, { analyticsEngine, - nameProperty: displayNameProp, + nameProperty, engine, }) let alert @@ -292,7 +288,7 @@ const loadEventLayer = async ({ programStage, eventCoordinateField, engine, - displayNameProp, + nameProperty, }) if (eventCoordinateFieldName) { config.legend.coordinateFields = [eventCoordinateFieldName] diff --git a/src/util/coordinatesName.js b/src/util/coordinatesName.js index 0fcdef009d..e24b43c154 100644 --- a/src/util/coordinatesName.js +++ b/src/util/coordinatesName.js @@ -9,7 +9,7 @@ export const loadEventCoordinateFieldName = async ({ programStage, eventCoordinateField, engine, - displayNameProp, + nameProperty, }) => { if (!eventCoordinateField) { return @@ -22,7 +22,7 @@ export const loadEventCoordinateFieldName = async ({ const { programStage: programStageData } = await engine.query( EVENT_PROGRAM_STAGE_DATA_ELEMENTS_QUERY, { - variables: { id: programStage.id, nameProperty: displayNameProp }, + variables: { id: programStage.id, nameProperty }, } ) const { programStageDataElements } = programStageData @@ -40,7 +40,7 @@ export const loadEventCoordinateFieldName = async ({ { variables: { id: program.id, - nameProperty: displayNameProp, + nameProperty, }, } ) diff --git a/src/util/helpers.js b/src/util/helpers.js index f35a90078e..07a472b9c2 100644 --- a/src/util/helpers.js +++ b/src/util/helpers.js @@ -38,50 +38,47 @@ const getBaseFields = (withSubscribers) => { return baseFields } -const analysisFields = () => { - const nameProperty = `displayName~rename(name)` - return [ - '*', - `columns[dimension,filter,items[dimensionItem~rename(id),dimensionItemType,${nameProperty}]]`, - `rows[dimension,filter,items[dimensionItem~rename(id),dimensionItemType,${nameProperty}]]`, - `filters[dimension,filter,items[dimensionItem~rename(id),dimensionItemType,${nameProperty}]]`, - 'organisationUnits[id,path]', // Added to retrieve org unit paths - 'dataDimensionItems', - `program[id,${nameProperty}]`, - 'programStage[id,displayName~rename(name)]', - 'legendSet[id,displayName~rename(name)]', - 'trackedEntityType[id,displayName~rename(name)]', - 'organisationUnitSelectionMode', - '!href', - '!publicAccess', - '!rewindRelativePeriods', - '!userOrganisationUnit', - '!userOrganisationUnitChildren', - '!userOrganisationUnitGrandChildren', - '!externalAccess', - '!access', - '!relativePeriods', - '!columnDimensions', - '!rowDimensions', - '!filterDimensions', - '!user', - '!organisationUnitGroups', - '!itemOrganisationUnitGroups', - '!userGroupAccesses', - '!indicators', - '!dataElements', - '!dataElementOperands', - '!dataElementGroups', - '!dataSets', - '!periods', - '!organisationUnitLevels', - '!sortOrder', - '!topLimit', - ] -} - -export const mapFields = (withSubscribers = false) => { - const fields = analysisFields() +const analysisFields = (nameProperty = 'displayName') => [ + '*', + `columns[dimension,filter,items[dimensionItem~rename(id),dimensionItemType,${nameProperty}~rename(name)]]`, + `rows[dimension,filter,items[dimensionItem~rename(id),dimensionItemType,${nameProperty}~rename(name)]]`, + `filters[dimension,filter,items[dimensionItem~rename(id),dimensionItemType,${nameProperty}~rename(name)]]`, + 'organisationUnits[id,path]', // Added to retrieve org unit paths + 'dataDimensionItems', + `program[id,${nameProperty}~rename(name)]`, + 'programStage[id,displayName~rename(name)]', + 'legendSet[id,displayName~rename(name)]', + `trackedEntityType[id,${nameProperty}~rename(name)]`, + 'organisationUnitSelectionMode', + '!href', + '!publicAccess', + '!rewindRelativePeriods', + '!userOrganisationUnit', + '!userOrganisationUnitChildren', + '!userOrganisationUnitGrandChildren', + '!externalAccess', + '!access', + '!relativePeriods', + '!columnDimensions', + '!rowDimensions', + '!filterDimensions', + '!user', + '!organisationUnitGroups', + '!itemOrganisationUnitGroups', + '!userGroupAccesses', + '!indicators', + '!dataElements', + '!dataElementOperands', + '!dataElementGroups', + '!dataSets', + '!periods', + '!organisationUnitLevels', + '!sortOrder', + '!topLimit', +] + +export const mapFields = (withSubscribers = false, nameProperty) => { + const fields = analysisFields(nameProperty) return `${getBaseFields(withSubscribers).join(',')}, mapViews[${fields.join( ',' diff --git a/src/util/requests.js b/src/util/requests.js index b97840cf5a..8957dc67c1 100644 --- a/src/util/requests.js +++ b/src/util/requests.js @@ -4,8 +4,8 @@ import { mapFields } from './helpers.js' const MAP_QUERY = { resource: 'maps', id: ({ id }) => id, - params: ({ withSubscribers }) => ({ - fields: mapFields(withSubscribers), + params: ({ withSubscribers, nameProperty }) => ({ + fields: mapFields(withSubscribers, nameProperty), }), } @@ -32,6 +32,7 @@ export const fetchMap = async ({ engine, defaultBasemap, withSubscribers, + nameProperty, }) => engine .query( @@ -40,6 +41,7 @@ export const fetchMap = async ({ variables: { id, withSubscribers, + nameProperty, }, } ) From f8dfe38f4a4eb5f35f17a1aaccdeff6e7da45215 Mon Sep 17 00:00:00 2001 From: Bruno Raimbault Date: Fri, 26 Jun 2026 12:54:14 +0200 Subject: [PATCH 2/2] chore: sonarqube issue --- src/util/helpers.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/util/helpers.js b/src/util/helpers.js index 07a472b9c2..ba3db72010 100644 --- a/src/util/helpers.js +++ b/src/util/helpers.js @@ -77,7 +77,10 @@ const analysisFields = (nameProperty = 'displayName') => [ '!topLimit', ] -export const mapFields = (withSubscribers = false, nameProperty) => { +export const mapFields = ( + withSubscribers = false, + nameProperty = 'displayName' +) => { const fields = analysisFields(nameProperty) return `${getBaseFields(withSubscribers).join(',')}, mapViews[${fields.join(