Skip to content
Draft
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
54 changes: 27 additions & 27 deletions cypress/integration/dataTable.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe('data table', () => {
// Check number of columns
cy.getByDataTest('bottom-panel')
.findByDataTest('dhis2-uicore-datatablecellhead')
.should('have.length', 10)
.should('have.length', 7)

// Filter by Org unit
cy.getByDataTest('data-table-column-filter-search-Org unit')
Expand All @@ -97,8 +97,8 @@ describe('data table', () => {
.should('have.length', 7)

// Confirm that the sort order is initially ascending by Name
checkTableCell({ row: 0, column: 2, expectedContent: 'Bargbe' })
checkTableCell({ row: 6, column: 2, expectedContent: 'Upper Bambara' })
checkTableCell({ row: 0, column: 1, expectedContent: 'Bargbe' })
checkTableCell({ row: 6, column: 1, expectedContent: 'Upper Bambara' })

// Sort by name (descending)
cy.getByDataTest('data-table-column-sort-button-Org unit').click()
Expand All @@ -110,8 +110,8 @@ describe('data table', () => {
cy.get('[data-testid="virtuoso-scroller"]').scrollTo('top')

// Confirm that the rows are sorted by Name descending
checkTableCell({ row: 0, column: 2, expectedContent: 'Upper Bambara' })
checkTableCell({ row: 6, column: 2, expectedContent: 'Bargbe' })
checkTableCell({ row: 0, column: 1, expectedContent: 'Upper Bambara' })
checkTableCell({ row: 6, column: 1, expectedContent: 'Bargbe' })

// Filter by Value (numeric)
cy.getByDataTest('data-table-column-filter-search-Value')
Expand All @@ -131,8 +131,8 @@ describe('data table', () => {
cy.get('[data-testid="virtuoso-scroller"]').scrollTo('top')

// Check that the rows are sorted by Value ascending
checkTableCell({ row: 0, column: 5, expectedContent: '35' })
checkTableCell({ row: 4, column: 5, expectedContent: '76' })
checkTableCell({ row: 0, column: 3, expectedContent: '35' })
checkTableCell({ row: 4, column: 3, expectedContent: '76' })

// Right-click a row and select "View profile"
cy.getByDataTest('bottom-panel')
Expand Down Expand Up @@ -196,7 +196,7 @@ describe('data table', () => {
// Check number of columns
cy.getByDataTest('bottom-panel')
.findByDataTest('dhis2-uicore-datatablecellhead')
.should('have.length', 13)
.should('have.length', 9)

cy.getByDataTest('bottom-panel')
.findByDataTest('dhis2-uicore-datatablecellhead')
Expand All @@ -210,8 +210,8 @@ describe('data table', () => {
.type(`${ouName}{enter}`)

// Check that all the rows have Org unit Moyowa
checkTableCell({ row: 0, column: 3, expectedContent: ouName })
checkTableCell({ row: 2, column: 3, expectedContent: ouName })
checkTableCell({ row: 0, column: 1, expectedContent: ouName })
checkTableCell({ row: 2, column: 1, expectedContent: ouName })

cy.getByDataTest('bottom-panel')
.findByDataTest('dhis2-uicore-tablebody')
Expand Down Expand Up @@ -256,8 +256,8 @@ describe('data table', () => {

// Confirm that the rows are sorted by Age in years ascending
// (the first click on a new column always sorts ascending)
checkTableCell({ row: 0, column: 10, expectedContent: '6' })
checkTableCell({ row: 1, column: 10, expectedContent: '32' })
checkTableCell({ row: 0, column: 7, expectedContent: '6' })
checkTableCell({ row: 1, column: 7, expectedContent: '32' })

// Right-click a row: Event layers have no profile to view
cy.getByDataTest('bottom-panel')
Expand Down Expand Up @@ -318,7 +318,7 @@ describe('data table', () => {
cy.getByDataTest('layers-toggle-button').click()

// Confirm that the sort order is initially ascending by Name
checkTableCell({ row: 0, column: 2, expectedContent: 'Bendu CHC' })
checkTableCell({ row: 0, column: 1, expectedContent: 'Bendu CHC' })

// First click on a new column always sorts ascending
cy.getByDataTest('data-table-column-sort-button-Value').click()
Expand All @@ -327,29 +327,29 @@ describe('data table', () => {
cy.get('[data-testid="virtuoso-scroller"]').scrollTo('top')

// Check that first row has Tihun CHC with value 28.63
checkTableCell({ row: 0, column: 2, expectedContent: 'Tihun CHC' })
checkTableCell({ row: 0, column: 5, expectedContent: '28.63' })
checkTableCell({ row: 0, column: 1, expectedContent: 'Tihun CHC' })
checkTableCell({ row: 0, column: 3, expectedContent: '28.63' })

// Check that row 5 has Gbamgbama CHC with value 117.98
checkTableCell({ row: 5, column: 2, expectedContent: 'Gbamgbama CHC' })
checkTableCell({ row: 5, column: 5, expectedContent: '117.98' })
checkTableCell({ row: 5, column: 1, expectedContent: 'Gbamgbama CHC' })
checkTableCell({ row: 5, column: 3, expectedContent: '117.98' })

// Check that row 6 has no value (undefined)
checkTableCell({ row: 6, column: 5, expectedContent: '' })
checkTableCell({ row: 6, column: 3, expectedContent: '' })

// Sort descending by Value
cy.getByDataTest('data-table-column-sort-button-Value').click()

// Reset scroll position after sorting - see comment above
cy.get('[data-testid="virtuoso-scroller"]').scrollTo('top')

checkTableCell({ row: 0, column: 2, expectedContent: 'Gbamgbama CHC' })
checkTableCell({ row: 0, column: 5, expectedContent: '117.98' })
checkTableCell({ row: 0, column: 1, expectedContent: 'Gbamgbama CHC' })
checkTableCell({ row: 0, column: 3, expectedContent: '117.98' })

checkTableCell({ row: 5, column: 2, expectedContent: 'Tihun CHC' })
checkTableCell({ row: 5, column: 5, expectedContent: '28.63' })
checkTableCell({ row: 5, column: 1, expectedContent: 'Tihun CHC' })
checkTableCell({ row: 5, column: 3, expectedContent: '28.63' })

checkTableCell({ row: 6, column: 5, expectedContent: '' })
checkTableCell({ row: 6, column: 3, expectedContent: '' })

// Third click on the same column cycles back to natural (unsorted)
// order - there's no dedicated Index column/button any more
Expand All @@ -359,7 +359,7 @@ describe('data table', () => {
cy.get('[data-testid="virtuoso-scroller"]').scrollTo('top')

// Check that row 0 range value is empty
checkTableCell({ row: 0, column: 7, expectedContent: '' })
checkTableCell({ row: 0, column: 5, expectedContent: '' })

// Sort by range, which is a string
cy.getByDataTest('data-table-column-sort-button-Range').click()
Expand All @@ -368,12 +368,12 @@ describe('data table', () => {
cy.get('[data-testid="virtuoso-scroller"]').scrollTo('top')

// Check that row 0 range value has value '0-40'
checkTableCell({ row: 0, column: 7, expectedContent: '0 – 40' })
checkTableCell({ row: 0, column: 5, expectedContent: '0 – 40' })

// Check that row 5 range value has value '90 - 120'
checkTableCell({ row: 5, column: 7, expectedContent: '90 – 120' })
checkTableCell({ row: 5, column: 5, expectedContent: '90 – 120' })

// Check that row 6 range value is empty
checkTableCell({ row: 6, column: 7, expectedContent: '' })
checkTableCell({ row: 6, column: 5, expectedContent: '' })
})
})
41 changes: 25 additions & 16 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -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: 2026-09-07T10:22:12.476Z\n"
"PO-Revision-Date: 2026-09-07T10:22:12.476Z\n"
"POT-Creation-Date: 2026-09-07T15:11:13.032Z\n"
"PO-Revision-Date: 2026-09-07T15:11:13.032Z\n"

msgid "2020"
msgstr "2020"
Expand Down Expand Up @@ -155,21 +155,12 @@ msgstr "Operator"
msgid "Date"
msgstr "Date"

msgid "Select all visible rows"
msgstr "Select all visible rows"

msgid "Reverse selection of visible rows"
msgstr "Reverse selection of visible rows"

msgid "Sort by Selected"
msgstr "Sort by Selected"

msgid "Sort by {{column}}"
msgstr "Sort by {{column}}"

msgid "Edit layer"
msgstr "Edit layer"

msgid "Data table"
msgstr "Data table"

msgid "Select a year, month, day or hour"
msgstr "Select a year, month, day or hour"

Expand Down Expand Up @@ -242,6 +233,15 @@ msgstr "to match the rows under it, or type to search"
msgid "Select matches"
msgstr "Select matches"

msgid "Select all visible rows"
msgstr "Select all visible rows"

msgid "Reverse selection of visible rows"
msgstr "Reverse selection of visible rows"

msgid "Sort by Selected"
msgstr "Sort by Selected"

msgid "Selected"
msgstr "Selected"

Expand All @@ -251,6 +251,9 @@ msgstr "Not selected"
msgid "All"
msgstr "All"

msgid "Sort by {{column}}"
msgstr "Sort by {{column}}"

msgid "Drill up one level"
msgstr "Drill up one level"

Expand Down Expand Up @@ -323,6 +326,9 @@ msgstr "Search all columns"
msgid "Highlight color"
msgstr "Highlight color"

msgid "Choose a data table to view"
msgstr "Choose a data table to view"

msgid "{{filtered}} of {{total}} rows"
msgstr "{{filtered}} of {{total}} rows"

Expand Down Expand Up @@ -815,6 +821,9 @@ msgstr "Layer is invalid"
msgid "Set layer opacity"
msgstr "Set layer opacity"

msgid "Clear filters applied in this layer’s table"
msgstr "Clear filters applied in this layer’s table"

msgid "More actions"
msgstr "More actions"

Expand Down Expand Up @@ -2080,8 +2089,8 @@ msgstr "GroupSet used for styling was not found"
msgid "Id"
msgstr "Id"

msgid "Org unit Id"
msgstr "Org unit Id"
msgid "Org unit id"
msgstr "Org unit id"

msgid "Org unit"
msgstr "Org unit"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"@dhis2/analytics": "^29.5.5",
"@dhis2/app-runtime": "^3.17.3",
"@dhis2/app-service-datastore": "^1.0.0-beta.3",
"@dhis2/maps-gl": "git+https://github.com/d2-ci/maps-gl.git#6758ac621ff7ed582ad458bb4c9f90900358cedc",
"@dhis2/maps-gl": "git+https://github.com/d2-ci/maps-gl.git#7c0b414689f82daaeb8bcb55b4c6fcedabb7257e",
"@dhis2/ui": "^10.17.0",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/modifiers": "^9.0.0",
Expand Down
74 changes: 74 additions & 0 deletions src/actions/__tests__/dataTable.spec.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import * as types from '../../constants/actionTypes.js'
import {
closeDataTable,
openDataTable,
toggleDataTable,
setActiveDataTableLayer,
resizeDataTable,
setMapBounds,
toggleShowOnlyFeaturesInView,
setSelectionFilter,
setHighlightColor,
setDataTableColumnConfig,
setActiveTimelinePeriod,
} from '../dataTable.js'

Expand All @@ -14,6 +21,14 @@ describe('closeDataTable', () => {
})
})

describe('openDataTable', () => {
it('creates a DATA_TABLE_OPEN action', () => {
expect(openDataTable()).toEqual({
type: types.DATA_TABLE_OPEN,
})
})
})

describe('toggleDataTable', () => {
it('creates a DATA_TABLE_TOGGLE action', () => {
expect(toggleDataTable('layer1')).toEqual({
Expand All @@ -23,6 +38,15 @@ describe('toggleDataTable', () => {
})
})

describe('setActiveDataTableLayer', () => {
it('creates a DATA_TABLE_ACTIVE_LAYER_SET action', () => {
expect(setActiveDataTableLayer('layer1')).toEqual({
type: types.DATA_TABLE_ACTIVE_LAYER_SET,
id: 'layer1',
})
})
})

describe('resizeDataTable', () => {
it('creates a DATA_TABLE_RESIZE action', () => {
expect(resizeDataTable(300)).toEqual({
Expand All @@ -32,6 +56,56 @@ describe('resizeDataTable', () => {
})
})

describe('setMapBounds', () => {
it('creates a MAP_BOUNDS_CHANGED action', () => {
const bounds = [
[0, 0],
[1, 1],
]
expect(setMapBounds(bounds)).toEqual({
type: types.MAP_BOUNDS_CHANGED,
bounds,
})
})
})

describe('toggleShowOnlyFeaturesInView', () => {
it('creates a TOGGLE_SHOW_ONLY_IN_VIEW action', () => {
expect(toggleShowOnlyFeaturesInView()).toEqual({
type: types.TOGGLE_SHOW_ONLY_IN_VIEW,
})
})
})

describe('setSelectionFilter', () => {
it('creates a SELECTION_FILTER_SET action', () => {
expect(setSelectionFilter(['selected'])).toEqual({
type: types.SELECTION_FILTER_SET,
value: ['selected'],
})
})
})

describe('setHighlightColor', () => {
it('creates a HIGHLIGHT_COLOR_SET action', () => {
expect(setHighlightColor('#ff0000')).toEqual({
type: types.HIGHLIGHT_COLOR_SET,
color: '#ff0000',
})
})
})

describe('setDataTableColumnConfig', () => {
it('creates a DATA_TABLE_COLUMN_CONFIG_SET action', () => {
const config = { pinnedKeys: ['name'] }
expect(setDataTableColumnConfig('layer1', config)).toEqual({
type: types.DATA_TABLE_COLUMN_CONFIG_SET,
layerId: 'layer1',
config,
})
})
})

describe('setActiveTimelinePeriod', () => {
it('creates an ACTIVE_TIMELINE_PERIOD_SET action', () => {
const period = { id: '202301', name: 'January 2023' }
Expand Down
9 changes: 9 additions & 0 deletions src/actions/dataTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@ export const closeDataTable = () => ({
type: types.DATA_TABLE_CLOSE,
})

export const openDataTable = () => ({
type: types.DATA_TABLE_OPEN,
})

export const toggleDataTable = (id) => ({
type: types.DATA_TABLE_TOGGLE,
id,
})

export const setActiveDataTableLayer = (id) => ({
type: types.DATA_TABLE_ACTIVE_LAYER_SET,
id,
})

export const resizeDataTable = (height) => ({
type: types.DATA_TABLE_RESIZE,
height,
Expand Down
5 changes: 4 additions & 1 deletion src/components/app/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import cx from 'classnames'
import React, { useEffect, useState } from 'react'
import { useSelector } from 'react-redux'
import { useLayersLoader } from '../../hooks/useLayersLoader.js'
import { isDataTableOpen } from '../../util/dataTable.js'
import BottomPanel from '../datatable/BottomPanel.jsx'
import DownloadModeMenu from '../download/DownloadMenubar.jsx'
import DownloadSettings from '../download/DownloadSettings.jsx'
Expand Down Expand Up @@ -35,7 +36,9 @@ const App = () => {
const [interpretationsRenderCount, setInterpretationsRenderCount] =
useState(1)

const dataTableOpen = useSelector((state) => !!state.dataTable)
const dataTableOpen = useSelector((state) =>
isDataTableOpen(state.dataTable)
)
const downloadModeOpen = useSelector((state) => !!state.ui.downloadMode)
const detailsPanelOpen = useSelector(
(state) => state.ui.rightPanelOpen && !state.orgUnitProfile
Expand Down
Loading
Loading